| Crates.io | hana-vault |
| lib.rs | hana-vault |
| version | 3.0.0 |
| created_at | 2025-11-15 00:39:57.685693+00 |
| updated_at | 2026-01-25 11:26:24.456596+00 |
| description | A library used by the Hana SSH client for storing user data securely in encrypted SQLite. |
| homepage | https://docs.hanassh.com/ |
| repository | https://github.com/hana-ssh/hana-vault |
| max_upload_size | |
| id | 1933858 |
| size | 98,975 |
A secure, production-ready Rust library for storing SSH credentials, hosts, and sensitive data using an encrypted SQLite vault format.
In-Memory SQLite Database
Binary Format Encryption
Security Best Practices
use hana_vault::{Vault, Host, Credential, SecretKey};
fn main() -> Result<(), Box<dyn std::error::Error>> {
// Generate or derive a 256-bit key
// In production, derive from password using Argon2id
let key = SecretKey::random();
// Create a new vault
let vault = Vault::new(key.clone())?;
// Add a host with environment variables
let mut host = Host::new("Production Server".to_string(), "prod.example.com".to_string(), 22);
host.add_env_var("NODE_ENV".to_string(), "production".to_string());
host = host.with_startup_command("source ~/.profile".to_string());
host = host.with_username("deploy".to_string());
vault.add_host(&host)?;
// Add SSH key credentials
let cred = Credential::new_ssh_key(
"Deploy Key".to_string(),
hana_vault::CredentialType::OpenSsh,
"-----BEGIN OPENSSH PRIVATE KEY-----\n...".to_string(),
Some("ssh-ed25519 AAAAC3...".to_string()),
None, // Optional passphrase
);
vault.add_credential(&cred)?;
// Link credential to host
vault.link_credential_to_host(host.id, cred.id, true)?;
// Export as encrypted bytes (e.g., to save to file or send to server)
let encrypted_bytes = vault.export_to_bytes()?;
std::fs::write("vault.hev", &encrypted_bytes)?;
Ok(())
}
This library accepts a raw 256-bit key. In your application, derive it from a user password using Argon2id:
use argon2::{Argon2, Params, Version};
use hana_vault::{SecretKey, KEY_SIZE};
fn derive_key(password: &str, salt: &[u8]) -> SecretKey {
let params = Params::new(65536, 3, 4, Some(KEY_SIZE)).unwrap();
let argon2 = Argon2::new(argon2::Algorithm::Argon2id, Version::V0x13, params);
let mut key = [0u8; KEY_SIZE];
argon2.hash_password_into(password.as_bytes(), salt, &mut key).unwrap();
SecretKey::from_bytes(&key)
}
use hana_vault::{Vault, SecretKey};
// Load from encrypted file
let encrypted_bytes = std::fs::read("vault.hev")?;
let key = derive_key("password", &salt); // Your key derivation
let vault = Vault::load_from_bytes(&encrypted_bytes, key)?;
You can read the unique identifier of a vault without decrypting it (useful for system keyrings):
use hana_vault::Vault;
let encrypted_bytes = std::fs::read("vault.hev")?;
let vault_id = Vault::get_id_from_bytes(&encrypted_bytes)?;
println!("Vault ID: {}", vault_id);
use hana_vault::Host;
// Create a host
let mut host = Host::new("Web Server".to_string(), "web.example.com".to_string(), 22);
// Add startup command
host = host.with_startup_command("cd /var/www && source env.sh".to_string());
// Set custom encoding
host = host.with_encoding("UTF-8".to_string());
// Add environment variables
host.add_env_var("PATH".to_string(), "/usr/local/bin:/usr/bin".to_string());
host.add_env_var("PORT".to_string(), "3000".to_string());
vault.add_host(&host)?;
// List all hosts
let hosts = vault.list_hosts()?;
// Get specific host
let host = vault.get_host(host_id)?;
// Update host
host.port = 2222;
vault.update_host(&host)?;
// Delete host
vault.delete_host(host_id)?;
use hana_vault::{Credential, CredentialType};
// Username/Password credentials
let cred = Credential::new_username_password(
"Admin Login".to_string(),
"admin".to_string(),
"super_secret_password".to_string(),
);
vault.add_credential(&cred)?;
// RSA key credentials
let rsa_cred = Credential::new_ssh_key(
"RSA Key".to_string(),
CredentialType::Rsa,
"-----BEGIN RSA PRIVATE KEY-----\n...".to_string(),
Some("ssh-rsa AAAAB3...".to_string()),
Some("key_passphrase".to_string()),
);
vault.add_credential(&rsa_cred)?;
// Ed25519 key credentials
let ed25519_cred = Credential::new_ssh_key(
"Ed25519 Key".to_string(),
CredentialType::Ed25519,
"-----BEGIN OPENSSH PRIVATE KEY-----\n...".to_string(),
Some("ssh-ed25519 AAAAC3...".to_string()),
None,
);
vault.add_credential(&ed25519_cred)?;
// List all credentials
let credentials = vault.list_credentials()?;
// Update credential
cred.name = "Updated Name".to_string();
vault.update_credential(&cred)?;
// Delete credential
vault.delete_credential(cred_id)?;
// Link a credential to a host (set as default)
vault.link_credential_to_host(host_id, credential_id, true)?;
// Link another credential to the same host (not default)
vault.link_credential_to_host(host_id, another_credential_id, false)?;
// Get all credentials for a host
let host_creds = vault.get_host_credentials(host_id)?;
for (credential, is_default) in host_creds {
println!("{}: {} (default: {})", credential.id, credential.name, is_default);
}
// Unlink credential from host
vault.unlink_credential_from_host(host_id, credential_id)?;
The library uses an SQLite database with the following schema:
hosts - Host configurations (hostname, port, startup commands, encoding, etc.)host_env_vars - Environment variables for hostscredentials - Authentication credentials (all types)host_credentials - Many-to-many relationship between hosts and credentialsschema_version - Automatic migration trackingThe library includes an autonomous migration system that:
Migrations run automatically when you load a vault, ensuring it's always at the latest schema version.
Vaults encrypted with one key-derivation configuration are not guaranteed to be decryptable by builds that change the KDF parameters/algorithm. If you need to support older vault files, keep the previous KDF available for decryption and migrate by re-exporting.
Run the comprehensive test suite:
cargo test
Run tests with output:
cargo test -- --nocapture
Vault - Main vault structure for managing encrypted data
new(key) - Create new vault with encryption keyload_from_bytes(bytes, key) - Load from encrypted bytesexport_to_bytes() - Export as encrypted bytesget_id_from_bytes(bytes) - Get vault ID without decryptingSecretKey - 256-bit encryption key
random() - Generate a random keyfrom_bytes(bytes) - Create from raw bytes (e.g., after KDF)Host - Host configuration
new(name, hostname, port) - Create new hostwith_startup_command(cmd) - Set startup commandwith_encoding(encoding) - Set custom encodingadd_env_var(key, value) - Add environment variableCredential - Authentication credential
new_username_password(name, username, password) - Create password credentialnew_ssh_key(name, type, private_key, public_key, passphrase) - Create SSH key credentialCredentialType - Enum of supported authentication types
UsernamePasswordRsaOpenSshEd25519EcdsaCertificateCustomAll operations return Result<T, VaultError>:
use hana_vault::{Vault, VaultError, SecretKey};
let bytes = std::fs::read("vault.hev")?;
let key = SecretKey::random(); // Your derived key
match Vault::load_from_bytes(&bytes, key) {
Ok(vault) => println!("Vault loaded successfully"),
Err(VaultError::InvalidKey) => eprintln!("Wrong key"),
Err(VaultError::CorruptedData(msg)) => eprintln!("Data corruption: {}", msg),
Err(e) => eprintln!("Error: {}", e),
}
Encrypted vault files (.hev) have the following structure:
┌─────────────────────────────────────────┐
│ Header (68 bytes, unencrypted) │
│ ├─ Magic bytes (8 bytes): "HANAVLT1" │
│ ├─ Version (4 bytes): u32 │
│ ├─ UID (16 bytes): UUID v4 │
│ ├─ Checksum (32 bytes): SHA-256 │
│ └─ Data size (8 bytes): u64 │
├─────────────────────────────────────────┤
│ Encrypted Data │
│ ├─ Nonce (12 bytes) │
│ └─ Ciphertext (variable) │
│ └─ Encrypted SQLite database │
└─────────────────────────────────────────┘
Contributions are welcome! Please ensure:
cargo test)cargo fmt, cargo clippy)This project is licensed under the MIT License - see the LICENSE file for details.