Skip to content

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.

Published on 4 min read

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 valueFormat
0xB54BFB65KDB — KeePass 1.x
0xB54BFB67KDBX — 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: size bytes

The loop continues until id == 0, which signals end of header. The fields that matter for cracking are:

idFieldNotes
2CipherID16-byte UUID; AES = 31c1f2e6bf714350be5805216afc5aff
4MasterSeed32 bytes
5TransformSeed32 bytes
6TransformRoundsu64 LE — the AES-KDF iteration count
7EncryptionIV16 bytes
9StreamStartBytes32 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.

Related articles

A technical primer on why password-protected files are crackable, what the *2john format does, and how Hash Extractor pulls it off in WebAssembly.
A precise look at how the $pdf$ hash is built from an encrypted PDF's /Encrypt dictionary, and how to crack it with hashcat or John the Ripper.
Why 7z is the trickiest archive to crack: LZMA-compressed headers, AES coder property encoding, and the full $7z$ hash format explained.