FileZilla 3.26.0-rc1 has been released with support for encrypted passwords protected by a master password. This post explains how it works:
Threat model
Attacker category A:
- A passive attacker that can merely read all files stored on the user's computer.
Attacker category B:
- Passive attackers that can read active program memory
- Active attackers that can modify files
- Passive attackers monitoring input devices
- If user opts to use plaintext protocols: Passive attackers monitoring network traffic
Note that it is theoretically impossible to protect against attackers of category B unless expensive special-purpose hardware is used.
The master password functionality has been designed to protect the user's passwords against attacker category A. It does not and cannot protect against attackers of category B. Incidentally, this functionality is indistinguishable from not storing passwords at all from a security perspective.
Setup
When configuring a master password (
P_master), a random 32 byte salt (
S_m) is generated. From the password and the salt, a X25519 private key (
M_priv) is generated using PBKDF2_HMAC_SHA256:
M_priv' = PBKDF2_HMAC_SHA256(P_master, S_m, 32, 100000)
From
M_priv',
M_priv is obtained by clearing the three least significant bits of the first byte and the most significant bit of the last byte and by setting the second most significant bit of the last byte to 1.
The corresponding public key (
M_pub) is:
M_pub = X25519(M_priv, 9)
M_pub as well as
S_m are remembered for later use.
Encryption
When saving a password (
P), first a random 32 byte salt (
S_e) is generated as well as a random ephemeral X25519 private key (
E_priv) and its public counterpart (
E_pub). Using Elliptic-Curve Diffie Hellman, a shared secret (
R) is derived:
R = X25519(E_priv, M_pub)
From
R, M_pub, E_pub, S_e and S_m, both an AES key (
K) and a nonce (
IV) are derived:
K = SHA256(S_e || 0 || S || E_pub || M_pub || S_m)
IV = SHA256(S_e || 1 || S || E_pub || M_pub || S_m)
The password is then encrypted with AES256-CTR and stored together with
E_pub and
S_e:
C = E_pub || S_e || AES256-CTR(K, IV, P)
Decryption
When decrypting a password (
C), it is first split into
E_pub,
S_e and
C1 such that
C = E_pub || S_e || C1
Next, using
P_master and the stored
S_m, M_priv is calculated as during setup.
Using ECDH, the shared secret
R is recovered:
R = X25519(M_priv, E_pub)
K and
IV are calculated as before and the password decrypted:
P = AES256-CTR(K, IV, C1)
Literature
RFC 7748 for X25519 and its application for ECDH
RFC 2898 for PBKDF2
Wikipedia article about AES
Wikipedia article about Counter mode