Multiple keys must be generated to get the final master key.
- Generate the composite key.
- Generate the transformed key from the composite key.
- Generate the master key from the transformed key.
Generation depends on supported credentials (see official info):
| Passphrase | Keyfile | Windows User Account (WUA) | |
|---|---|---|---|
.kdb |
✓ | ✓ | ✖ |
.kdbx |
✓ | ✓ | ✓ |
Hash with SHA-256.
If you have just one credential, you are done after step 1.
- hash all credentials needed (passphrase, content of the keyfile)
- concatenate all hashed credentials in this order: keyfile, passphrase
- hash the result of the concatenation
You cannot skip any step.
- hash all credentials needed (passphrase, key of the keyfile, WUA)
- concatenate all hashed credentials in this order: passphrase, keyfile, WUA
- hash the result of the concatenation
This part is the same for .kdb and .kdbx files.
You need three values from the header:
Hash with SHA-256.
-
create an
AES128-ECBcipher, takingTransform Seedas its seed -
initialize the transformed_key value with the composite_key value
transformed_key = composite_key -
use this cipher to encrypt the transformed_key
Transform Roundstimestransformed_key = AES.encrypt(transformed_key, Transform Rounds) -
hash the transformed_key
transformed_key = sha256(transformed_key) -
concatenate the
Master Seedto the transformed_keytransformed_key = concat(Master Seed, transformed_key) -
hash the transformed_key to get the final master key
final_master_key = sha256(transformed_key)
You now have the final master key, you can finally decrypt the database.
.kdb: the part of the file after the header..kdbx: the part after the End of Header field.
A .kdb or .kdbx file is composed of two things :
- a header containing various metadata.
- the database (in XML). Encrypted.
- Bytes
0-3: Primary identifier. Common across all versions.
0x9AA2D903
- Bytes
4-7: Secondary identifier. The KeePass version this file was created with.
0xB54BFB67is latest (.kdbx)0xB54BFB66is KeePass 2.x pre-release (alpha & beta) (.kdbx)0xB54BFB65is KeePass 1.x (.kdb)
.kdbhas fixed number of fields taking a fixed number of bytes in its header..kdbxhas a Type-length-value list of fields in its header.
| bytes | entry | description |
|---|---|---|
4 |
FLAGS |
see below |
4 |
VERSION |
it looks like each version of KeePass save the database with a different value for this field. |
16 |
MASTERSEED |
see below |
16 |
ENCRYPTIONIV |
see below |
4 |
NUMBEROFGROUPS |
contains the total number of groups in the database |
4 |
NUMBEROFENTRIES |
contains the total number of entries in the database |
32 |
CONTENTSHASH |
a SHA-256 hash of the database, used for integrity checking |
32 |
TRANSFORMSEED |
see below |
4 |
TRANSFORMROUNDS |
see below |
Contains the flag indicating the cipher used for the database.
1= SHA22= AES4= ARC48= Twofish
The Type takes 1 byte, the Length takes 2 bytes, and the Value takes Length bytes.
-
Bytes
8-9:WORD, file version (minor). -
Bytes
10-11:WORD, file version (major). -
Dynamic header: Each entry is [
BYTEbId,LWORDwSize,BYTE[wSize]bData].
| type | entry | description |
|---|---|---|
0 |
END |
|
1 |
COMMENT |
|
2 |
CIPHERID |
see below |
3 |
COMPRESSIONFLAGS |
see below |
4 |
MASTERSEED |
see below |
5 |
TRANSFORMSEED |
see below |
6 |
TRANSFORMROUNDS |
see below |
7 |
ENCRYPTIONIV |
see below |
8 |
PROTECTEDSTREAMKEY |
|
9 |
STREAMSTARTBYTES |
|
10 |
INNERRANDOMSTREAMID |
see below |
UUID for cipher. Outer encryption AES256, currently no others supported. For the default AES encryption, use AES128-CBC with PKCS#7-style padding.
Must be: 31c1f2e6bf714350be5805216afc5aff
0= not compressed1= compressed with GZip
Inner stream encryption type used to encrypt the password of an entry in the database.
0= none1= Arc4Variant2= Salsa20
seed that get concatenated then hashed with the transformed master key (see later how to) to form the final master key
the key used as seed for AES to generate the transformed master key from the user master key. The generation of the transformed master key consists in encrypting the user master key N rounds in total, N being the value of the field Key Encrypt Rounds fields value
number of rounds the user master key must be encrypted to generate the transformed master key
the Initialization vector used for content encryption.
If the compression flag was set, unzip the data after decrypting it.
Parse the data as XML.
-
Depending on
CIPHERID, set up a decryption context with key final_master_key andENCRYPTIONIV. This will yield raw_payload_area. -
check the first
Xbytes againstStream Start Bytesfor equality.X = StreamStartBytes.length -
the data is a succession of block (at least two) laid out like:
bytes entry description 4BLOCKIDan integer giving the block ID (first block is 0). 32BLOCKHASHthe hash of the Block Data. 4DATASIZEan integer giving the size of Block Data. DATASIZEBLOCKDATAthe data of this block. -
In the end, Block Data should be hashed and checked with its corresponding Block Hash, then all Block Data should be concatenated ordered by their ID (it should be the same order as the one you read them). After the concatenation, the obtained data should be a XML document.
-
Depending on INNERRANDOMSTREAMID, set up the inner stream context. 0 will mean all passwords in the XML will be in plain text, 1 that they are encrypted with Arc4Variant (not detailed here) and 2 that they will be encrypted with Salsa20. Since 2.0.x betas the official client defaults to Salsa20 but technically one should support Arc4 too.
-
Set up a Salsa20 context using key sha256(PROTECTEDSTREAMKEY) and fixed IV [0xE8,0x30,0x09,0x4B,0x97,0x20,0x5D,0x2A]. If your implementation gives us a choice, you should use 20 rounds for Salsa20 (technically possible to use 8 or 12).
-
Sequentially(!) look in the XML for "Value" nodes with the "Protected" attribute set to "True" (a suitable xpath might be "//Value[@Protected='True']").
-
Obtain their innerText and run it through base64_decode to obtain the encrypted password/data. Then, run it through salsa20 to obtain the cleartext data.
-
Optionally, check the header for integrity by taking sha256() hash of the whole header (up to, but excluding, the payload start bytes) and compare it with the base64_encode()d hash in the XML node (...).
The inner stream cipher is supposed to deliver the same pseudo-random byte sequence using key+fixed IV as seed. Because of this, strict care must be taken to not mess up the ordering of decryption.
Latest available snapshot: https://web.archive.org/web/20160528151935/http://blog.sharedmemory.fr/en/2014/04/30/keepass-file-format-explained/
Also check this: lgg/awesome-keepass#17 and https://github.com/lgg/awesome-keepass/