Skip to content

How Ansible Vault hashes are extracted

A technical walkthrough of how Hash Extractor parses Ansible Vault files to produce a crackable $ansible$ hash for hashcat and John the Ripper.

Published on 4 min read

Hash Extractor parses Ansible Vault files entirely in the browser — no upload, no server — and emits the $ansible$ hash that hashcat and John the Ripper understand. Ansible Vault is one of the most common ways DevOps teams encrypt secrets at rest: playbook variables, API keys, database passwords, cloud credentials. When you encounter a vault file during a penetration test or CTF, this is how you get the crackable hash out of it.

What an Ansible Vault file looks like

A vault file is plain text. It always starts with a header line, followed by a hex blob that may be wrapped across multiple lines:

$ANSIBLE_VAULT;1.1;AES256
33363835363338323737353832623536363935343131353339343763643234323538
36393732376266313737643465336631333138636136363531393535643966646131
...

The header format is $ANSIBLE_VAULT;<version>;<cipher>[;<vault_id>]. Hash Extractor reads the cipher field and rejects anything other than AES256 with an explicit unsupported error — that is the only cipher in the supported vault format.

Everything after the header line is the hex body. Whitespace (including newlines used for wrapping) is stripped before decoding.

The double-encoding quirk

This is the most unusual aspect of the format. The hex blob does not directly encode ciphertext. Decoding it yields ASCII text in the following shape:

<salt_hex>
<hmac_hex>
<ciphertext_hex>

Three hex strings separated by newline characters. The outer layer is hex-encoded, and each field inside is itself a hex string — the salt, HMAC, and ciphertext are all stored as ASCII hex, not raw bytes. This double-encoding is an artifact of the Python implementation that originally wrote vault files as human-readable text.

Once Hash Extractor strips whitespace from the body and hex-decodes it, it splits the result on \n to recover the three inner hex strings in that order: salt, then HMAC, then ciphertext.

How Ansible derives and verifies the key

Ansible uses PBKDF2-HMAC-SHA256 with 10,000 iterations to derive two keys from the password and the salt: an AES-256-CTR encryption key and an HMAC-SHA256 authentication key. The HMAC covers the ciphertext. To crack the vault, an attacker tests each password candidate by running PBKDF2 with the extracted salt, re-deriving the HMAC key, and checking it against the extracted HMAC — the ciphertext never needs to be decrypted.

The $ansible$ hash format

The output format is:

$ansible$0*0*<salt>*<ciphertext>*<hmac>

Note the field order: ciphertext comes before HMAC, even though the vault file stores them in the order salt → HMAC → ciphertext. This reordering matches what hashcat mode 16900 and ansible2john expect.

A real example from the test suite

The extraction logic is verified by a round-trip test in the source. Using the test values directly:

  • salt: aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa (64 hex chars = 32 bytes)
  • hmac: bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb (64 hex chars = 32 bytes)
  • ciphertext: cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc (96 hex chars = 48 bytes)

The inner ASCII blob would be those three strings joined by newlines. That blob is hex-encoded to produce the vault file's body. After extraction the output hash is:

$ansible$0*0*aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa*cccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccccc*bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb

Breaking that down field by field:

FieldValue
Prefix$ansible$0*0*
Saltaaaa...aa (64 chars)
Ciphertextcccc...cc (96 chars)
HMACbbbb...bb (64 chars)

The 0*0 flags are fixed in this format version.

Cracking with hashcat

hashcat mode 16900 handles $ansible$ hashes directly.

echo '$ansible$0*0*<salt>*<ciphertext>*<hmac>' > hash.txt
hashcat -m 16900 hash.txt /usr/share/wordlists/rockyou.txt

PBKDF2-HMAC-SHA256 at 10,000 iterations is deliberately slow. On a modern GPU you can expect roughly tens of thousands of candidates per second — fast enough that common or short passwords from a wordlist fall in seconds, slow enough that strong random passwords are effectively uncrackable offline.

John the Ripper

echo '$ansible$0*0*<salt>*<ciphertext>*<hmac>' > hash.txt
john --format=ansible --wordlist=/usr/share/wordlists/rockyou.txt hash.txt

Why this matters for DevOps assessments

Ansible Vault files are everywhere: committed to git repositories, stored in CI/CD secret managers, included in configuration archives. During an internal red team engagement or a cloud storage misconfiguration review, finding a .vault file or a group_vars/all/vault.yml is common. The encrypted file gives away nothing about the plaintext — but the hash gives a cracker everything it needs to test passwords offline without touching the target system again.

Vault passwords are often short environment names, team names, or reused corporate passwords, which makes wordlist attacks particularly effective against them.

Why client-side extraction matters

A vault file is an encrypted secrets store. Uploading it to a remote service to extract the hash exposes the ciphertext to a third party. Hash Extractor compiles the extraction logic to WebAssembly and runs it entirely in your browser: the file bytes never leave your machine. The hash produced contains no plaintext — only the parameters the cracker needs to test candidates locally.

The extraction logic mirrors ansible2john from the John the Ripper project. The hashes it produces follow the same $ansible$0*0*<salt>*<ciphertext>*<hmac> structure, so any existing hashcat -m 16900 or John --format=ansible workflow works unchanged.

Related articles

Why 7z is the trickiest archive to crack: LZMA-compressed headers, AES coder property encoding, and the full $7z$ hash format explained.
Inside the adb backup text header: how PBKDF2 salts, round counts, and a master-key blob become a crackable $ab$ hash.
A technical walkthrough of how Hash Extractor parses Ethereum keystore v3 JSON files to produce a crackable $ethereum$ hash for hashcat and John the Ripper.