Name | Type | Description |
---|---|---|
d | BYTE[] |
Data on the card (up to, say 256 bytes). |
K | BYTE[32] |
256-bit symmetric encryption/decryption key for plugin's use. |
EK | BYTE[] → BYTE[] |
Encryption function with key K . |
DK | BYTE[] → BYTE[] |
Decryption function with key K . |
H | BYTE[] → BYTE[32] |
SHA-256 hash function. |
N | CHAR[0..6] |
PIN for card, up to 6 decimal digits - optional. |
P | CHAR[] |
KeePass database password - optional. |
S | BYTE[16] |
KeePass password salt, 128 bit. |
F | BYTE[] |
Content of the key file - optional. |
C | BYTE[] |
The key provided by the plugin (in CPasswordDlg::GetKeyFromProvider ). |
M | BYTE[32] |
The actual master key used to encrypt/decrypt the database. |
X | BYTE[32] → BYTE[32] |
KeePass' dictionary attac prevention: multiple rounds of AES, and a then SHA-256. |
Y | CHAR[1..6] → BYTE[32] |
The plugin's key expansion function, mapping a PIN to a cipher key. |
According to the documentation, observation and tracing through the source code, the master key M is derived in the following ways, depending on the several possibilities for the composing the master key.
Given only a password P: «If only a password is used (i.e. no key file), the password plus a 128-bit random salt are hashed using SHA- 256 to form the final key [...]»
M = X( H( P ))
The salt comes into play only in the key derivation function X .
Given only a key file with content F:
M = X( F ) if file F is exactly 32 bytes long;
M = X( H( F )) otherwise
As a special case, a key file containing 64 hex digits will be converted to binary (32 bytes) and processed just like the corresponding 32 byte key file.
Given a password P and a key file with content F: «When using both password and key file, the final key is derived as follows: SHA-256(SHA-256(password), key file contents), [...]»
M = X( H( H( P ) || F )) if F is 32 bytes long;
«If the key file doesn't contain exactly 32 bytes (256 bits), they are hashed with SHA-256, too, to form a 256-bit key.»
M = X( H( H( P ) || H( F ))) otherwise
What happens to the key C provided by a plugin is less well-documented. It turns out it is not the same way as for key files.
Given no password, but only a plugin-provided key C:
M = X( H( C ))
The plugin-provided key is will always be hashed, whatever the size.
Given a password P and a plugin-provided key C:
M = X( H( H( P ) || H( C )))
The master key derives from a hash of a 256-bit long concatenation of two hashes, first a hash over the password, second a hash over the plugin-provided key.
The similarity of the last equation (in item 5) and the equations in item 3 should allow us to exchange the plugin-provided key C and the content F of an appropriately generated key file - one of the project's main goals.
The fact that the key provided by the plugin will always be hashed, even if 32 bytes long, is a little nuisance and forces us to provide a 512 bits long value (64 bytes) in some cases: if the provided key is meant to be equivalent to a master key composed of a password P and a key file F, the key provider has to produce
C = H( P ) || H( _F )
which will then be hashed to match case 3 above.
If we can read at least 32 bytes of data d from the card and assume they have enough entropy, we can provide these bytes directly as the key to KeePass:
C = d
If the card is a "found" read-only memory card with low entropy, we might want to use the hash of more than 32 bytes of its content:
C = H( d )
Both approaches have the weakness that the (stolen or copied) card is enough for an adversary to unlock the KeePass database. We can counteract this by using a secret transformation of the card's content, using a key K in the plugin (either hard-coded or set by the user and stored eg in the config file):
C = EK( d )
Now the adversary would need access to both the card's content d and the key K, ie to the user's account on the system.
Furthermore, a PIN N (a password consisting of a few digits) can be used as a second component in the transformation of the raw content d. (However, the few digits of a PIN would not give much protection against exhaustive search; but it could deter a naughty coworker or family member):
C = EY( N )( EK( d ) )
Here N is used to derive the key Y( N ) of a second cipher round. Obviously, the order of the two encryptions could be reversed, or alternatively both N and K could be used to derive a single encryption key, using a different key derivation scheme Y':
C = EY'( N, K )( d )
I'm not sure if any of these variants is better than the others.
The plugin's cipher E as well as the it's key derivation function Y is not yet determined.
Using the knowledge about the master key construction, we can determine what the content F of a key file should be, so that the key file and the card can used interchangeably to unlock on and the same database: we want to end up with the same master key in both cases, ie when using the key file, we have:
M = X( F ) if length of F is 32 bytes;
M = X( H( F )) otherwise.
and when using the card, we have:
M = X( H( C ))
= X( H( EY( N )( EK( d ) )))
Thus we can either set F = H( C ) or we use H(F) = H( C ) = H( EY( N )( EK( d ) )), and thus set F = EY( N )( EK( d ) ).