Authenticated context (bound into the tag, not stored)

The tag also covers ver ‖ recordType ‖ slotId ‖ generation. These bytes are never written to the file — they come from where it lives and from meta.bin — so relocating, retyping, or rolling back a file breaks the MAC. Changing one here re-seals the envelope.

Two sub-keys are derived from one master key (SHA-256 of the passphrase) with distinct HMAC labels — so the encryption key and the MAC key are never the same.
encKey
macKey
Sealed envelope

Click any byte to flip it — or use an attack below — then watch the read-path verdict.

ver · 1 B
iv · 16 B
hmacTag · 32 B
cipher · AES-256-CBC
Try an attack

Each one is a real tampering from the post. They all fail closed under encrypt-then-MAC — but for different reasons.

Read path
Padding-oracle attacker

In decrypt-first mode the device leaks one bit per query — padding valid or not. That is enough to recover plaintext without the key. Switch to encrypt-then-MAC and the same attacker goes silent.

CBC malleability — the attacker's lever

In CBC, flipping a byte of the IV flips the matching plaintext byte of block 0 by the exact same amount — a controlled change. (Encrypt-then-MAC means you never get to use it.)

About This Tool

Every file in the vault is an encrypt-then-MAC envelope: [ver][iv][hmacTag][cipher]. This tool builds one for real in your browser — it derives separate encKey and macKey sub-keys, encrypts the secret with AES-256-CBC, and authenticates the version byte, IV, and ciphertext together with HMAC-SHA256.

It is the interactive companion to the article A Vault File That Fails Closed: Encrypt-then-MAC on an MCU.

How do I use it?

  1. Type a secret and a passphrase, and set the authenticated context (record type, slot, generation). The envelope re-seals live as you type.

  2. Click any byte to flip it, or pick one of the seven attacks: flip a cipher/IV byte, forge the tag, truncate the ciphertext, move the file to another slot, change its record type, or roll back its generation.

  3. Watch the read-path verdict. Under encrypt-then-MAC every attack fails closed — and the tool tells you why each one fails (a different reason for each).

  4. Flip the read path to decrypt-first and press Run the padding-oracle attack: a real Vaudenay attack recovers the last plaintext block byte-by-byte using only the padding-valid/invalid signal — no key. Switch back to encrypt-then-MAC and the same attacker goes silent.

What does it show?

  • Key separation — one passphrase becomes a master key, which fans out into distinct encKey and macKey sub-keys.

  • Context binding — the tag covers ver ‖ recordType ‖ slotId ‖ generation, so relocating, retyping, or rolling back a file breaks the MAC even though the ciphertext is pristine.

  • Verify-before-decrypt & fail-closed — the tag is checked first, so a tampered file never reaches the AES-CBC decrypt path, and any failure returns nothing.

  • The doom path — decrypt-first runs the cipher on attacker bytes before the MAC, exposing a genuine padding oracle.

  • CBC malleability — flipping an IV byte flips the matching plaintext byte of block 0 by the same amount: the attacker’s lever.

The construction, in formulas

Sealing (write path):

encKey = HMAC(master, "vault-enc")
macKey = HMAC(master, "vault-mac")
C      = AES-256-CBC-Encrypt(encKey, IV, PKCS7(P))
tag    = HMAC-SHA256(macKey, ver ‖ recordType ‖ slotId ‖ generation ‖ IV ‖ C)
file   = [ver] [IV] [tag] [C]

Reading under encrypt-then-MAC (verify, then decrypt):

tag' = HMAC-SHA256(macKey, context ‖ IV ‖ C)
if not constant_time_equal(tag', tag): reject   // fail closed, no plaintext
P    = unpad(AES-256-CBC-Decrypt(encKey, IV, C))

Why the order is the whole game — the work an attacker faces:

  • Forge a tag without macKey: a brute force of 2^256 (HMAC-SHA256) — infeasible.

  • Padding oracle (decrypt-first): ~1 bit per query, ≤ 256 queries per byte → a 16-byte block in ≤ ~4096 queries.

  • Encrypt-then-MAC removes the oracle outright: for a forged file the decrypt step is unreachable, so the attack drops from linear to impossible.

Why CBC malleability is the lever

In CBC, P_i = Decrypt(C_i) ⊕ C_(i-1) (with C_(-1) = IV). So ΔP_i = ΔC_(i-1): flipping a byte of the previous block — or of the IV, for block 0 — flips the same byte of the plaintext by the same amount, predictably. That controlled change is exactly what lets a padding oracle walk the plaintext out one byte at a time.

References & further reading

Privacy

All cryptography runs entirely in your browser via the Web Crypto API. Nothing you type is transmitted to any server.

Frequently asked questions

What does "encrypt-then-MAC" mean?

It means you encrypt the plaintext first, then compute a MAC over the resulting ciphertext (plus the version byte and IV). On read, the MAC is verified before any decryption is attempted.

Why verify the MAC before decrypting?

Checking the tag first means a tampered file never reaches the AES-CBC decrypt path, which closes off padding-oracle attacks. If the tag does not match, the read fails closed and no plaintext is produced.

Why use separate keys for encryption and the MAC?

Reusing one key for both AES and HMAC is unsafe, so the passphrase derives a master key that fans out into distinct encKey and macKey sub-keys. The encryption key and the MAC key are never the same value.

What happens when I flip a byte in the envelope?

Flipping any byte changes the data the HMAC covers, so the recomputed tag no longer matches the stored tag. The read fails closed — no decryption runs and no plaintext is returned.

Is my secret or passphrase sent anywhere?

No. All cryptography runs entirely in your browser via the Web Crypto API, and nothing you type is transmitted to any server.