|
libkeepass 0.2.0
KeePass key database importer/exporter library
|
This document describes how libkeepass encrypts the payload of a KDBX database. See key-derivation.md for how the cryptographic keys are produced and kdbx-parsing.md for the surrounding file format.
A KDBX file consists of two layers:
The payload is encrypted with a final key derived from the user key:
master_seed comes from the KDBX header.transformed_key is produced by the key-derivation function (AES-KDF for KDBX 3, AES-KDF or Argon2 for KDBX 4), see key-derivation.md.The library works on fixed 16-byte blocks. block_transform (src/cipher.cc:36) is a small helper that slices a stream into 16-byte blocks and feeds each through a BlockOperation lambda.
Two operating modes are used:
encrypt_ecb / decrypt_ecb** (src/cipher.cc:66, :81) — used only by the AES-KDF transform, never for the database payload itself.encrypt_cbc / decrypt_cbc** (src/cipher.cc:126, :169) — used for the actual payload in KDBX 3 and KDBX 4 (AES / Twofish).ECB and CBC in this code base always operate on full 16-byte blocks with OpenSSL padding disabled; CBC adds PKCS #7 padding manually:
encrypt_cbc: the last partial block is padded to 16 bytes with the pad length as the padding byte value. A full block is always appended (padding 0x10 ... 0x10) when the input length is an exact multiple of 16.decrypt_cbc: reads the pad length from the last byte of the final block, validates every padding byte, and strips it.| Cipher | Constructor | Notes |
|---|---|---|
AesCipher | AesCipher(key[32], iv[16]) | Wraps OpenSSL EVP_aes_256_ecb with padding disabled; used for single-block crypto. |
TwofishCipher | TwofishCipher(key[32], iv[16]) | Pure software implementation (16 rounds, Reed-Solomon key schedule, MDS matrix, PHT). |
ChaCha20Cipher | ChaCha20Cipher(key[32], iv[12]) | Pure software implementation (20 rounds), used as a stream cipher in KDBX 4. |
Salsa20Cipher | Salsa20Cipher(key[32], iv[8]) | Pure software implementation (20 rounds), used for the inner random stream in KDBX 3 / KDBX 4 Salsa20. |
ChaCha20 and Salsa20 are stream ciphers: Process() XORs each 64-byte block with the keystream and increments an internal counter (src/cipher.cc:678). Because ChaCha20 needs no padding, the KDBX 4 payload is XORed directly.
Stream buffers translate between the encrypted blocks on disk and a plain byte stream consumed by the XML parser. They are layered std::streambuf implementations.
hashed_istreambuf / hashed_ostreambuf split a stream into blocks. Each block is stored as:
GetBlockHash()).block_size == 0 and a zero hash signals end of stream; writing always ends with such a block.hmac_istreambuf / hmac_ostreambuf protect the ciphertext. Each block is framed as HMAC-SHA256 (32) || block_size (4) || data.
A fresh 64-byte HMAC key is derived per block:
The MAC input is the concatenation block_index (8 LE) || block_size (4 LE) || data. The base hmac_key itself is derived from the master seed and the transformed key (see key-derivation.md#hmac-key).
gzip_istreambuf / gzip_ostreambuf wrap zlib's inflate / deflate when the database's compression flag is gzip.
Import path (KdbxFile::Import3, src/kdbx_import.cc:91) — from ciphertext to XML:
The plaintext begins with the 32-byte content stream start bytes stored in the header. They are decrypted too and compared against the header value — this doubles as a fast password check (src/kdbx_import.cc:153).
Import path (KdbxFile::Import4, src/kdbx_import.cc) — from ciphertext to XML:
Differences to KDBX 3:
content_start_bytes check; the header HMAC already verifies integrity and the password.decrypt_cbc_stream, src/cipher.cc) instead of being copied in full first — see streaming.md.Cipher IDs in the KDBX 4 header (src/kdbx_header.cc:45):
| Cipher | UUID |
|---|---|
| AES | 31 c1 f2 e6 bf 71 43 50 be 58 05 21 6a fc 5a ff |
| ChaCha20 | d6 03 8a 2b 8b 6f 4c b5 a5 24 33 9a 31 db b5 9a |
| Twofish | ad 68 f2 9f 57 6f 4b b9 a3 6a d4 7a f9 65 34 6c |
Export mirrors the import layers in reverse order:
KdbxFile::Export3 (src/kdbx_export.cc:124): write header → derive final key → serialize XML → optional gzip → hashed_ostreambuf → prepend start bytes → encrypt_cbc → write.KdbxFile::Export4 (src/kdbx_export.cc:203): write header + stored hash + stored HMAC → serialize XML → optional gzip → encrypt (encrypt_cbc or ChaCha20) → wrap in hmac_ostreambuf → write.Both exporters write the header to an in-memory stream first so that the SHA-256 header hash (and the KDBX 4 HMAC) can be computed and stored with the file.
| Step | KDBX 3 | KDBX 4 |
|---|---|---|
| Header integrity | SHA-256 hash stored in <Meta><HeaderHash>, verified after XML parse | SHA-256 hash + HMAC-SHA256 stored right after the header, verified immediately |
| Payload framing | hashed blocks (SHA-256/block) | HMAC blocks (HMAC-SHA256/block) |
| Password check | start bytes comparison | header HMAC verification |