← Back to the tool

Symmetric-key · Under the hood

AES-GCM encrypt & decrypt

What each button in the tool actually computes, step by step — with the real formulas.

Generating the key

Clicking "Generate AES-256 key" calls the browser's CSPRNG for 32 truly random bytes — the same entropy source covered in the Random Number Generation module. There's no separate "key schedule" step yet; AES derives its round keys from this value internally, only once you actually encrypt.

Encrypting: counter mode underneath

GCM builds a counter block J0 from the random 96-bit IV you see displayed, with a 32-bit counter appended starting at 1. Each 128-bit block of your plaintext is XORed with AES(K, J0 + i), and the counter increments for every block — exactly the CTR mode covered in the AES module. This is also why the ciphertext is exactly as long as the plaintext: GCM never pads.

Ci=PiAES(K, J0+i)C_i = P_i \oplus \mathrm{AES}(K,\ J_0 + i)

Authenticating: GHASH and the tag

While the ciphertext is produced, GCM separately runs it through GHASH — built entirely from multiplication in the finite field GF(2¹²⁸), keyed by H = AES(K, 0¹²⁸). Each ciphertext block is folded in one at a time (Horner's method), so the final result depends on every bit of the ciphertext, in order.

The 16-byte tag appended to your ciphertext is this GHASH output XORed with AES(K, J0) — the same counter-mode building block used for encryption, applied once more to seal the tag.

What one click of "Encrypt" actually runs

  1. 1

    Build J0

    The random IV plus a starting counter of 1.

  2. 2

    Encrypt via counter mode

    Each block XORed with AES(K, J0 + i) to produce ciphertext.

  3. 3

    Fold ciphertext through GHASH

    Every ciphertext block multiplied into a running value in GF(2¹²⁸).

  4. 4

    Seal the tag

    GHASH output XORed with AES(K, J0) — the 16 bytes shown as the auth tag.

Xi=(Xi1Ci)HX_i = (X_{i-1} \oplus C_i) \cdot H

GHASH folds in one ciphertext block at a time.

T=GHASH(H,C)AES(K,J0)T = \mathrm{GHASH}(H, C) \oplus \mathrm{AES}(K, J_0)

The same thing, as a block diagram

The encryption path (top) and the authentication path (bottom) run side by side — the ciphertext produced on top feeds directly into the GHASH chain on the bottom, and the tag is what comes out the other end.

GCM: counter-mode encryption + GHASH authentication

ENCRYPTIONJ0 + 1AES_KCiphertextPlaintextAUTHENTICATIONCiphertext× H (GHASH)AES_K(J0)Tag

Shown for a single plaintext block; longer messages chain more ciphertext blocks through the same GHASH multiplication before the final XOR that produces the tag.

Why the tamper button breaks decryption

"Tamper with 1 byte" flips 8 bits of ciphertext before decryption. Multiplication in GF(2¹²⁸) has the same avalanche property as a hash function — one changed input bit changes GHASH's output completely and unpredictably. Your browser recomputes the tag from the (now tampered) ciphertext, compares it to the 16 bytes that travelled alongside it, finds no match, and crypto.subtle.decrypt() throws rather than returning any plaintext at all, even a single correct byte.

Now try it

Back to AES-GCM encrypt & decrypt

Encrypt a real message with real AES-256-GCM, see the IV, ciphertext, and auth tag, then decrypt it back — or tamper with one byte and watch decryption fail.