Skip to content

How macOS keychain hashes are extracted

A technical walkthrough of how Hash Extractor parses Apple macOS keychain files to produce a crackable $keychain$ hash for hashcat and John the Ripper.

Published on 4 min read

Hash Extractor parses Apple macOS keychain files entirely in the browser — no upload, no server — and emits the $keychain$ hash that hashcat and John the Ripper understand. This post walks through exactly how that parsing works, from locating the DbBlob to assembling the final hash string.

The DbBlob and its magic

A macOS keychain file (.keychain or login.keychain-db) stores its cryptographic envelope in a structure called the DbBlob, which lives at the end of the file. The DbBlob begins with a 4-byte magic sequence:

FA DE 07 11

The parser scans the file from the end backwards, looking for the last occurrence of this magic. Scanning in reverse is deliberate: the DbBlob is always the last major structure in the file, so starting from the tail is both correct and efficient.

fn last_magic(data: &[u8]) -> Option<usize> {
    (0..=data.len() - MAGIC.len())
        .rev()
        .find(|&i| &data[i..i + MAGIC.len()] == MAGIC)
}

If the magic is not found at all, the file is either not a keychain or is too short to contain a DbBlob, and extraction is aborted.

Reading the fields

Once the blob's start position is known, four pieces of data are read at fixed offsets relative to that position:

OffsetSizeField
+84 bytes (u32 BE)ciphertext offset
+4420 bytesPBKDF2 salt
+648 bytesIV
cipheroff48 bytesciphertext (wrapped DB key)

Ciphertext offset (@ blob+8)

A 32-bit big-endian integer that encodes where the ciphertext starts, measured from the blob's start position. Its value varies depending on the keychain version, so it cannot be hardcoded — the parser reads it at runtime and uses it to locate the ciphertext.

PBKDF2 salt (@ blob+44)

A 20-byte salt used as input to PKCS#5 PBKDF2. It is randomly generated when the keychain is created and stored verbatim in the blob. The cracker combines this salt with each password candidate to re-derive the master key and attempt to unwrap the DB key.

IV (@ blob+64)

An 8-byte initialization vector for the 3DES-CBC decryption used to unwrap the DB key. It is fixed per keychain and must be present in the hash for the cracker to attempt decryption correctly.

Ciphertext (@ blob+cipheroff)

48 bytes of ciphertext representing the encrypted (wrapped) database key. The master key — derived purely from the user's login password and the PBKDF2 salt — is used to unwrap it via 3DES-CBC. Successfully unwrapping the DB key proves the candidate password was correct.

The key derivation chain

The cracking target is straightforward: the user's login password is the only secret. The master key is derived exclusively from that password and the salt using PKCS#5 PBKDF2 — no key file, no additional entropy. A cracker re-derives the master key for each password candidate, attempts to unwrap the 48-byte ciphertext, and checks whether the result is a valid key. A match means the password was found.

The $keychain$ hash format

All four fields are encoded as lowercase hex and concatenated with * delimiters:

$keychain$*<salt>*<iv>*<ciphertext>

Real example from the test suite

The parser's regression test constructs a synthetic DbBlob with known values and asserts the exact output. Using those same byte sequences:

  • salt (20 bytes, 0xA00xB3): a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3
  • iv (8 bytes, 0xB00xB7): b0b1b2b3b4b5b6b7
  • ciphertext (48 bytes, 0x100x3F): 101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f

The ciphertext offset stored at blob+8 is 92 (big-endian 00 00 00 5C), meaning the ciphertext begins immediately after the 92-byte fixed header region of this test blob.

The assembled hash:

$keychain$*a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3*b0b1b2b3b4b5b6b7*101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f

Breaking it down:

  • $keychain$ — format identifier
  • *a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3 — 20-byte PBKDF2 salt (40 hex chars)
  • *b0b1b2b3b4b5b6b7 — 8-byte IV (16 hex chars)
  • *101112...3e3f — 48-byte wrapped DB key ciphertext (96 hex chars)

Cracking the hash

The hashcat mode is -m 23100 ("Apple Keychain"). John the Ripper uses --format=keychain.

hashcat

echo '$keychain$*a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3*b0b1b2b3b4b5b6b7*101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f' > hash.txt
hashcat -m 23100 hash.txt /usr/share/wordlists/rockyou.txt

John the Ripper

echo '$keychain$*a0a1a2a3a4a5a6a7a8a9aaabacadaeafb0b1b2b3*b0b1b2b3b4b5b6b7*101112131415161718191a1b1c1d1e1f202122232425262728292a2b2c2d2e2f303132333435363738393a3b3c3d3e3f' > hash.txt
john --format=keychain --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

The PBKDF2 key derivation is deliberately slow, but macOS uses a relatively low iteration count compared to modern standards — meaning GPU-based cracking is feasible against weak or common passwords. Any password in rockyou or derivable via rule-based mutation is at risk; strong random passphrases remain practical to protect.

Why client-side matters

A keychain file is a password vault. Sending one to a remote service to extract a hash would expose the encrypted vault to a third party before you even begin cracking. 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 PBKDF2 salt, IV, and wrapped ciphertext that the cracker needs to test candidates locally.

The extraction logic mirrors keychain2john from the John the Ripper project. The hash it produces follows the same $keychain$*<salt>*<iv>*<ciphertext> format, so any existing hashcat or John workflow accepts it unchanged.

Related articles

A technical walkthrough of how Hash Extractor parses encrypted Apple disk images to produce a crackable $dmg$ hash for John the Ripper.
A technical walkthrough of how Hash Extractor parses a Manifest.plist BackupKeyBag to produce a crackable $itunes_backup$ hash for hashcat and 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.