Skip to content

Instantly share code, notes, and snippets.

@berlam
Forked from ddotlic/gist:05772087c7abffe35141
Last active February 23, 2025 06:00
Show Gist options
  • Select an option

  • Save berlam/2fc3146e7c8420cbbf99 to your computer and use it in GitHub Desktop.

Select an option

Save berlam/2fc3146e7c8420cbbf99 to your computer and use it in GitHub Desktop.
KeePass file format specification (.kdb, .kdbx).

Keys

Multiple keys must be generated to get the final master key.

  1. Generate the composite key.
  2. Generate the transformed key from the composite key.
  3. Generate the master key from the transformed key.

Composite Key

Generation depends on supported credentials (see official info):

Passphrase Keyfile Windows User Account (WUA)
.kdb
.kdbx

Hash with SHA-256.

.kdb

If you have just one credential, you are done after step 1.

  1. hash all credentials needed (passphrase, content of the keyfile)
  2. concatenate all hashed credentials in this order: keyfile, passphrase
  3. hash the result of the concatenation

.kdbx

You cannot skip any step.

  1. hash all credentials needed (passphrase, key of the keyfile, WUA)
  2. concatenate all hashed credentials in this order: passphrase, keyfile, WUA
  3. hash the result of the concatenation

Master Key

This part is the same for .kdb and .kdbx files.

You need three values from the header:

Hash with SHA-256.

  1. create an AES128-ECB cipher, taking Transform Seed as its seed

  2. initialize the transformed_key value with the composite_key value

    transformed_key = composite_key

  3. use this cipher to encrypt the transformed_key Transform Rounds times

    transformed_key = AES.encrypt(transformed_key, Transform Rounds)

  4. hash the transformed_key

    transformed_key = sha256(transformed_key)

  5. concatenate the Master Seed to the transformed_key

    transformed_key = concat(Master Seed, transformed_key)

  6. 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.

Layout

A .kdb or .kdbx file is composed of two things :

  • a header containing various metadata.
  • the database (in XML). Encrypted.

Identifiers

  1. Bytes 0-3: Primary identifier. Common across all versions.
  • 0x9AA2D903
  1. Bytes 4-7: Secondary identifier. The KeePass version this file was created with.
  • 0xB54BFB67 is latest (.kdbx)
  • 0xB54BFB66 is KeePass 2.x pre-release (alpha & beta) (.kdbx)
  • 0xB54BFB65 is KeePass 1.x (.kdb)

Headers

  • .kdb has fixed number of fields taking a fixed number of bytes in its header.
  • .kdbx has a Type-length-value list of fields in its header.

.kdb

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

Flags

Contains the flag indicating the cipher used for the database.

  • 1 = SHA2
  • 2 = AES
  • 4 = ARC4
  • 8 = Twofish

.kdbx

The Type takes 1 byte, the Length takes 2 bytes, and the Value takes Length bytes.

  1. Bytes 8-9: WORD, file version (minor).

  2. Bytes 10-11: WORD, file version (major).

  3. Dynamic header: Each entry is [BYTE bId, LWORD wSize, 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

Cipher ID

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

Compression Flags

  • 0 = not compressed
  • 1 = compressed with GZip

Inner stream encryption type

Inner stream encryption type used to encrypt the password of an entry in the database.

  • 0 = none
  • 1 = Arc4Variant
  • 2 = Salsa20

Common

Master Seed

seed that get concatenated then hashed with the transformed master key (see later how to) to form the final master key

Transform Seed

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

Transform Rounds

number of rounds the user master key must be encrypted to generate the transformed master key

Encryption IV

the Initialization vector used for content encryption.

Database

If the compression flag was set, unzip the data after decrypting it.

.kdb

Parse the data as XML.

.kdbx

  1. Depending on CIPHERID, set up a decryption context with key final_master_key and ENCRYPTIONIV. This will yield raw_payload_area.

  2. check the first X bytes against Stream Start Bytes for equality.

    X = StreamStartBytes.length

  3. the data is a succession of block (at least two) laid out like:

    bytes entry description
    4 BLOCKID an integer giving the block ID (first block is 0).
    32 BLOCKHASH the hash of the Block Data.
    4 DATASIZE an integer giving the size of Block Data.
    DATASIZE BLOCKDATA the data of this block.
  4. 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.

WIP/ TEMP

  1. 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.

  2. 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).

  3. Sequentially(!) look in the XML for "Value" nodes with the "Protected" attribute set to "True" (a suitable xpath might be "//Value[@Protected='True']").

  4. 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.

  5. 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 (...).

Notes

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.

Sources

@lgg
Copy link

lgg commented Aug 3, 2022

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment