“End-to-end encrypted” gets applied to everything from messaging apps to cloud storage to SSH managers. It’s often used to mean “encrypted at rest,” which is weaker than it sounds. Here’s a precise definition and a technical walkthrough of what real E2EE looks like for SSH credentials.
What end-to-end encryption means, precisely
End-to-end encryption for stored credentials means: the encryption key is derived on the client, never transmitted to the server, and the server stores only ciphertext. Even with full access to the server’s database and filesystem, an attacker cannot recover plaintext credentials without the user’s master password.
This is different from server-side encryption, where the server encrypts data at rest using a key it manages. Server-side encryption protects against an attacker who steals the raw database files – but not against the service provider itself, a compromised key management system, or a legal order requiring decryption.
The three-layer structure
Layer 1: Key derivation from master password
The master password is never stored or transmitted. Instead, it’s used to derive a cryptographic key using PBKDF2 (Password-Based Key Derivation Function 2) with SHA-256, 100,000 iterations, and a random 16-byte salt stored server-side.
master_key = PBKDF2(
password = user_master_password,
salt = user_salt, // stored server-side, not secret
iterations = 100_000,
hash = SHA-256,
key_len = 32 bytes
)
The salt is not secret – it’s stored in your user record. Its purpose is to prevent precomputation attacks (rainbow tables). The iteration count makes brute-forcing expensive: 100,000 iterations of PBKDF2-SHA256 takes around 100ms on modern hardware. An attacker trying 10 billion passwords per second on dedicated hardware still takes years to exhaust a reasonable password space.
Layer 2: Data Encryption Key (DEK)
Rather than encrypting each credential directly with the master-password-derived key, a separate random DEK (Data Encryption Key) is generated once per user. All credentials are encrypted with this DEK. The DEK itself is encrypted (“wrapped”) with the master key and stored server-side.
This indirection solves a practical problem: if a user changes their master password, only the wrapped DEK needs to be re-encrypted – not every credential in the vault. Without a DEK, changing your password would require decrypting and re-encrypting every stored credential.
dek = random_bytes(32) // generated once, random
wrapped_dek = AES_GCM_encrypt(
key = master_key, // derived above
plain = dek
) // wrapped_dek stored server-side
Layer 3: Credential encryption
Each SSH credential (password or private key) is encrypted individually using AES-256-GCM with a fresh random 12-byte nonce. GCM mode provides both confidentiality (the data is encrypted) and integrity (any tampering is detectable).
nonce = random_bytes(12) // fresh per credential
ciphertext = AES_GCM_encrypt(
key = dek, // unwrapped on unlock
nonce = nonce,
plain = ssh_password_or_private_key
)
// stored: nonce + ciphertext + auth_tag
What the server actually stores
- user_salt – random, not secret, needed for key derivation
- wrapped_dek – DEK encrypted with master key; useless without master password
- verification_blob – used to check master password correctness without storing the password
- encrypted_credential – nonce + ciphertext + auth tag; useless without DEK
The server has no master key, no unwrapped DEK, and no plaintext credentials. A complete database breach exposes only data that cannot be decrypted without the user’s master password.
Where computation happens
In Termique, all cryptographic operations run in a protected native process separate from the renderer. Key material lives in managed state and never enters the JavaScript layer. The renderer invokes encrypt/decrypt via a local IPC call; it receives only the result, never the key.
This architecture means there’s no way to extract the key by inspecting JavaScript memory, intercepting renderer IPC calls, or using devtools. The key exists only inside the Rust process.
Private keys specifically
SSH private keys get an additional layer: after decryption from the vault, they are stored in the OS keychain (macOS Keychain, Windows Credential Manager, libsecret on Linux) rather than in memory for the session duration. The SSH agent signs authentication challenges using the keychain-stored key without exposing the key bytes to the app itself.
What this doesn’t protect against
E2EE of stored credentials does not protect against a compromised machine. If your device has malware with keylogger or process memory access, your master password and decrypted credentials can be captured at the moment of use. It also doesn’t protect against a weak master password that can be brute-forced offline from a stolen wrapped DEK and salt.
The threat model that E2EE addresses: a breach of the cloud service, a rogue employee, or a legal compulsion order against the service provider. Against those threats, a properly implemented E2EE scheme is as strong as the user’s master password.
Related articles