How KeePass hashes are extracted
A technical walkthrough of how Hash Extractor parses KDB and KDBX 3.x files to produce a crackable $keepass$ hash for hashcat and John the Ripper.
Hash Extractor parses KeePass databases entirely in the browser — no upload, no server — and emits the $keepass$ hash that hashcat and John the Ripper understand. This post walks through exactly how that parsing works, from the first byte of the file to the final hash string.
File signatures
Every KeePass file starts with two 32-bit little-endian magic numbers at bytes 0–7. Detection also falls back to file extension (.kdbx or .kdb) when the magic is absent or the buffer is too short.
sig1 is always 0x9AA2D903. Any file that doesn't open with this value is not a KeePass database.
sig2 identifies the format family:
sig2 value | Format |
|---|---|
0xB54BFB65 | KDB — KeePass 1.x |
0xB54BFB67 | KDBX — KeePass 2.x / 3.x |
Bytes 8–9 are the minor version (u16 LE) and bytes 10–11 are the major version (u16 LE). The major version is what distinguishes KDBX 3.x from KDBX 4.x.
KDBX 4 and Argon2
If major >= 4, Hash Extractor returns an explicit "not supported yet" error. KDBX 4 switched from an AES-KDF to Argon2, which requires a different hash format. KDBX 3.x and KDB 1.x are fully supported today.
Parsing the KDBX 3.x dynamic header
After the 12-byte preamble (sig1 + sig2 + minor + major), KDBX 3 uses a TLV (type–length–value) header:
- id: 1 byte
- size: u16 LE
- data:
sizebytes
The loop continues until id == 0, which signals end of header. The fields that matter for cracking are:
| id | Field | Notes |
|---|---|---|
| 2 | CipherID | 16-byte UUID; AES = 31c1f2e6bf714350be5805216afc5aff |
| 4 | MasterSeed | 32 bytes |
| 5 | TransformSeed | 32 bytes |
| 6 | TransformRounds | u64 LE — the AES-KDF iteration count |
| 7 | EncryptionIV | 16 bytes |
| 9 | StreamStartBytes | 32 bytes |
Immediately after the end-of-header TLV, the first 32 bytes of ciphertext follow. The cracker derives the composite key by concatenating the user's master password hash (and optional key-file material) with the MasterSeed, then running AES-ECB over the TransformSeed for TransformRounds iterations to produce the transformed key. The final AES-CBC decryption of the payload is validated by comparing the first 32 decrypted bytes against StreamStartBytes. A match means the password was correct.
The $keepass$ hash format (KDBX 3.x)
The output format for KDBX 3.x is:
$keepass$*2*<rounds>*<algo>*<masterseed>*<transformseed>*<iv>*<streamstartbytes>*<firstblock>
The *2* version marker identifies this as a KDBX record. <algo> is 0 for AES (the only cipher UUID handled today) and 1 for anything else.
Here is a real example from the test suite, produced from a KeePassXC-generated database with password pw and 1,000,000 transform rounds:
$keepass$*2*1000000*0*
117b1836fc86c575b9cdf83df5926f0831525110a986ba406d2aa5a5aacc291c*
4465603aa9e00947f94bc87ee0035adab6b34146f45fe1d7c8282cd0cc0b2cad*
a1db42a3ed2f567fb41057519ede53dd*
975224214c2fe8ab42126d796925963177e2b1fcc28968579609787388e0860f*
467ed0b0b86951d26346e237f289775166b1acaab464261d12ee5e498159a825
(Shown split across lines for readability; the actual hash is one unbroken string.)
Fields in order: version 2, rounds 1000000, algorithm 0 (AES), master seed, transform seed, encryption IV, stream start bytes, first ciphertext block.
Parsing the KDB 1.x fixed-layout header
KeePass 1.x databases use a fixed header rather than a TLV loop. After the 8-byte signature pair, the layout is:
flags (u32 LE) — bit 2 = Rijndael/AES, bit 8 = Twofish
version (u32 LE) — skipped
finalSeed (16 bytes)
encIV (16 bytes)
numGroups (u32 LE) — skipped
numEntries (u32 LE) — skipped
contentsHash (32 bytes)
transfSeed (32 bytes)
rounds (u32 LE)
<encrypted data follows>
The KDB output format is:
$keepass$*1*<rounds>*<algo>*<finalseed>*<transfSeed>*<iv>*<contentsHash>*1*<datasize>*<encrypteddata>
The *1* version marker identifies this as a KDB record.
Cracking the hash
Both hash variants use hashcat mode 13400 and John's --format=KeePass.
hashcat
echo '$keepass$*2*1000000*0*117b1836...' > hash.txt
hashcat -m 13400 hash.txt /usr/share/wordlists/rockyou.txt
John the Ripper
echo '$keepass$*2*1000000*0*117b1836...' > hash.txt
john --format=KeePass --wordlist=/usr/share/wordlists/rockyou.txt hash.txt
A million AES rounds per candidate is intentionally expensive — a GPU cluster will do on the order of hundreds to a few thousand candidates per second depending on hardware. The default KeePassXC round count (1,000,000 as seen in the real-database regression test) is calibrated to slow brute-force significantly. Short or common passwords still fall quickly against rockyou; anything over ~10 random characters with good entropy is practically safe against offline attack even at high round counts.
Why client-side matters
KeePass databases are password vaults. Uploading one to a remote service to extract a hash would expose the encrypted vault to a third party. Hash Extractor compiles the parser to WebAssembly and runs it entirely in your browser: the file never leaves your machine, and the hash it produces contains no plaintext — only the parameters the cracker needs to test candidates locally.
The extraction logic mirrors keepass2john from the John the Ripper project. The hashes it produces are byte-for-byte identical to the reference tool's output — verified against a real KeePassXC database in the test suite — so any existing hashcat or John workflow works unchanged.