Decrypting local account hashes from the SAM hive
From the hashed boot key to a user's NT hash: the F and V structures, the RC4 vs AES storage formats, and the per-RID DES layer that wraps every Windows password hash.
The SAM hive holds local accounts and their password hashes. With the
boot key in hand, decryption is a
two-stage unwrap: first derive a hashed boot key, then peel two cipher layers
off each individual hash.
Step 1 — the hashed boot key
The domain account record at SAM\Domains\Account has an F value. Its Key0
field (offset 0x68) starts with a revision byte that tells you the storage
format:
0x01— legacy (RC4).rc4Key = MD5(salt ‖ "QWERTY…" ‖ bootKey ‖ "DIGITS…"), then RC4-decrypt the stored key + checksum. A second MD5 verifies the result; a mismatch means a syskey startup password is set (or the SAM and SYSTEM hives are from different machines).0x02— modern (AES).hashedBootKey = AES-CBC(bootKey, data, salt)using the embedded 16-byte salt as the IV.
Either way you end up with 16 bytes that gate every per-user hash.
Step 2 — per-user hashes
Each account lives under SAM\Domains\Account\Users\<RID> (the RID is the
hex-named subkey). Its V value is a blob with a fixed header of pointers:
username offset/length, plus the offset/length of the encrypted LM and NT
hashes. Byte 2 of the hash structure again selects RC4 (0x01) or AES.
The first cipher layer uses the hashed boot key:
# legacy
rc4Key = MD5(hashedBootKey ‖ LE(rid) ‖ "NTPASSWORD\0")
key = RC4(rc4Key, encryptedHash)
# modern
key = AES-CBC(hashedBootKey, encryptedHash, salt)[:16]
Step 3 — the per-RID DES layer
Whatever the era, the inner-most layer is identical and dates back to NT 4: the 16-byte hash is split into two 8-byte halves, each decrypted with a DES key derived from the account's RID:
k = LE(rid) # 4 bytes
key1 = transform(k0 k1 k2 k3 k0 k1 k2)
key2 = transform(k3 k0 k1 k2 k3 k0 k1)
hash = DES_decrypt(key1, half1) ‖ DES_decrypt(key2, half2)
transform expands each 7-byte string into the 8-byte form DES expects by
inserting parity bits. The result is the raw NT (MD4-of-password) hash you would
feed to a cracker or pass-the-hash.
An account with no password yields the well-known empty NT hash
31d6cfe0d16ae931b73c59d7e0c089c0and LM hashaad3b435b51404eeaad3b435b51404ee.
Why this matters defensively
The whole chain is reversible offline because the key material lives in the same backup set as the hashes. That is the case for NoLMHash policy, LAPS for local-admin passwords, and disk encryption: they raise the cost of getting the hives, because once you have them the math is deterministic.