Skip to content

How Apple DMG hashes are extracted

A technical walkthrough of how Hash Extractor parses encrypted Apple disk images to produce a crackable $dmg$ hash for John the Ripper.

Published on 5 min read

Hash Extractor parses encrypted Apple disk images entirely in the browser — no upload, no server — and emits the $dmg$ hash that John the Ripper understands. This post walks through exactly how that parsing works, from magic-byte detection to the final hash string, for both on-disk layouts macOS has used.

Two layouts, one format name

Apple has used two distinct binary layouts for encrypted .dmg files. Both produce a $dmg$ hash, but the version number in the hash reflects which layout was found:

VersionMagic bytesPosition
v1cdsaencrLast 8 bytes of the file
v2encrcdsaFirst 8 bytes of the file

Detection checks v2 first (a starts_with on the buffer), then v1 (a tail comparison), and falls back to file extension when neither magic is found.

v1: the tail-anchored header

In the v1 layout the entire encryption header lives at the end of the file. The parser subtracts a fixed header size of 1276 bytes from the file length to find the header base h, then reads fields at these offsets relative to h:

OffsetSizeField
h + 484 bytes BEPBKDF2 iteration count (iter)
h + 524 bytes BESalt length (salt_len)
h + 56salt_len bytesSalt
h + 1364 bytes BEWrapped AES key length (waes_len)
h + 140waes_len bytesWrapped AES key
h + 4364 bytes BEWrapped HMAC-SHA1 key length (hmac_len)
h + 440hmac_len bytesWrapped HMAC-SHA1 key

The cdsaencr magic sits at the very end of the header (bytes h + 1268 through h + 1275), which is also the last 8 bytes of the file.

v1 hash format

$dmg$1*<saltlen>*<salt_hex>*<waes_len>*<waes_hex>*<hmac_len>*<whmac_hex>*<iter>

Breaking down a real v1 example

The unit test in dmg.rs constructs a minimal v1 image with:

  • iter = 1000
  • salt_len = 20, salt bytes 0x01..0x14
  • waes_len = 32, AES key bytes 0x00..0x1f
  • hmac_len = 48, HMAC key bytes 0x00..0x2f

That produces a hash beginning with:

$dmg$1*20*0102030405060708090a0b0c0d0e0f1011121314*32*...

and ending with *1000. Reading left to right:

  • 1 — v1 layout
  • 20 — 20-byte salt
  • 0102...1314 — the 20 salt bytes in hex
  • 32 — 32-byte wrapped AES key
  • (wrapped AES key hex)
  • (wrapped HMAC-SHA1 length)
  • (wrapped HMAC-SHA1 hex)
  • 1000 — PBKDF2 iterations

John the Ripper uses PBKDF2-HMAC-SHA1 with the provided salt and iteration count to derive a candidate key, then attempts to unwrap the AES key blob. A successful unwrap confirms the password.

v2: the front-anchored header with data chunks

The v2 layout places the encrcdsa magic at byte 0. Beyond the header fields, the cracker also needs two plaintext data chunks from the encrypted image to verify a candidate password — those chunks are read directly from the file at computed offsets.

Header fields

All integers are big-endian:

OffsetSizeField
568 bytesdatasize — total encrypted data size in bytes
648 bytesdataoffset — byte offset where encrypted data begins
1044 bytesKDF iteration count (iter)
1084 bytessalt_len
112up to 32 bytesSalt (capped at 32 bytes)
14832 bytesblob_enc_iv — IV for the encrypted key blob
1964 byteskeyblob_size
200up to 64 byteskeyblob_field (hex-encoded, zero-padded to keyblob_size * 2 chars)

Computing the data chunks

The cracker needs two AES blocks from the encrypted payload to test a decryption attempt. Their positions are derived from datasize and dataoffset:

cno      = ceil(datasize / 4096) - 2
data_size = datasize - cno * 4096

chunk1 starts at: dataoffset + cno * 4096   (length: data_size bytes)
chunk2 starts at: dataoffset                (length: 4096 bytes)

cno is the second-to-last 4096-byte block index. chunk2 is always the very first 4096-byte block of the encrypted data. Both are embedded verbatim in the hash as hex strings.

v2 hash format

$dmg$2*<saltlen>*<salt_hex>*32*<iv_hex>*<keyblob_size>*<keyblob_hex>*<cno>*<data_size>*<chunk1_hex>*1*<chunk2_hex>*<iter>

The 32 before <iv_hex> is the fixed IV length. The 1 before <chunk2_hex> is a literal constant in the format (chunk2 is always one 4096-byte block).

No hashcat mode

Unlike many other formats, there is no dedicated hashcat mode for $dmg$. John the Ripper with --format=dmg is the standard cracker for both v1 and v2 hashes.

Cracking a DMG hash with John

Once you have the hash from Hash Extractor:

echo '$dmg$1*20*0102030405060708090a0b0c0d0e0f1011121314*32*...*1000' > hash.txt
john --format=dmg --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

The same command works for v2 hashes — John detects the version from the $dmg$2* prefix automatically.

Why client-side WASM matters

Encrypted disk images can contain sensitive data. Uploading a .dmg to a remote service to extract its hash exposes the file before you have even started cracking. Hash Extractor runs the entire parser — Rust compiled to WebAssembly — inside your browser. The image bytes never leave your machine; only the hash string (which contains no plaintext data) is shown to you. This mirrors what dmg2john does on the command line, but with no toolchain to install and no file transfer.

Summary

v1v2
Magic positionEOFSOF
Magic valuecdsaencrencrcdsa
KDFPBKDF2-HMAC-SHA1PBKDF2-HMAC-SHA1
Data chunks in hashNoYes (chunk1 + chunk2)
Hash prefix$dmg$1*$dmg$2*
CrackerJohn --format=dmgJohn --format=dmg

Both layouts are handled automatically: drop a .dmg into Hash Extractor and it detects the magic, picks the correct parser, and emits a ready-to-crack hash string.

Related articles

A technical walkthrough of how Hash Extractor parses Apple macOS keychain files to produce a crackable $keychain$ hash for hashcat and 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.