Cryptography

AES Block Cipher

The cipher that encrypts almost everything you send

AES is a symmetric block cipher that encrypts 128-bit blocks through 10, 12, or 14 rounds of byte substitution, row shifts, column mixing, and key addition — fast, NIST-standardized, and hardware-accelerated to gigabytes per second.

  • Block size128 bits (16 bytes)
  • Key sizes128 / 192 / 256 bits
  • Rounds10 / 12 / 14
  • TypeSymmetric, substitution-permutation
  • Hardware throughput1–5 GB/s per core (AES-NI)

Interactive visualization

Press play, or step through manually. The visualization is yours to drive — try it before reading on.

Open visualization fullscreen ↗

Watch the 60-second explainer

A condensed visual walkthrough — narrated, captioned, under a minute.

How AES encrypts a block

AES — the Advanced Encryption Standard, a subset of the Rijndael cipher designed by Joan Daemen and Vincent Rijmen — was selected by NIST in 2001 to replace the aging 56-bit DES. It encrypts data in fixed 128-bit blocks. Whatever the key length, the block is always 16 bytes, arranged not as a flat array but as a 4×4 grid of bytes called the state, filled column by column.

Encryption is a substitution-permutation network: alternate confusing the bytes (substitution) and spreading them around (permutation) enough times that every output bit depends on every input bit and every key bit. AES does this in rounds. The number of rounds is fixed by key length: 10 rounds for AES-128, 12 for AES-192, 14 for AES-256. Each full round runs four transformations on the state:

  1. SubBytes — replace each byte using a fixed 256-entry lookup table, the S-box. This is the only non-linear step, and it is what makes AES resistant to algebraic attacks.
  2. ShiftRows — cyclically left-shift the rows of the 4×4 state: row 0 by 0 bytes, row 1 by 1, row 2 by 2, row 3 by 3. This moves bytes between columns so the next step mixes different ones.
  3. MixColumns — treat each column as a 4-term polynomial over GF(2⁸) and multiply it by a fixed matrix. One changed input byte now affects all four bytes of its column.
  4. AddRoundKey — XOR the state with a 128-bit round key derived from the cipher key by the key schedule.

The flow has two wrinkles. Before round 1, there is a standalone AddRoundKey (the "whitening" step) so the attacker never sees an unkeyed S-box output. And the final round drops MixColumns — it runs SubBytes, ShiftRows, AddRoundKey only. Dropping it changes nothing about security; it exists so that the decryption routine is structurally symmetric with encryption.

The trick: arithmetic in GF(2⁸)

SubBytes and MixColumns both operate in the finite field GF(2⁸) — the 256 possible byte values, with a custom multiplication. Addition in this field is just XOR. Multiplication is polynomial multiplication reduced modulo the irreducible polynomial x⁸ + x⁴ + x³ + x + 1 (the byte 0x11B).

This buys two things. The S-box is defined as the multiplicative inverse of a byte in the field, followed by an affine transform — a highly non-linear map with no fixed points. MixColumns multiplies each column by a matrix whose entries are the field values 1, 2, and 3, which is cheap (a multiply-by-2 is a shift and a conditional XOR with 0x1B) yet spreads one byte's influence across the whole column. Together they give AES its avalanche: flip one input bit and roughly half the 128 output bits flip.

The key schedule

AES never reuses the raw cipher key. The key expansion stretches the original key into one 128-bit round key per round plus the initial whitening key — 11 keys for AES-128, 13 for AES-192, 15 for AES-256. It works on 32-bit words. Every Nk-th word (Nk = 4, 6, or 8 words for the three key sizes) is transformed by rotating its bytes, passing them through the S-box, and XOR-ing a round constant Rcon; the rest are plain XORs of earlier words. The round constants are successive powers of 2 in GF(2⁸), which breaks the symmetry between rounds and stops slide attacks.

Decryption inverts each step — InvSubBytes, InvShiftRows, InvMixColumns — and applies the round keys in reverse order. AES is not a Feistel cipher, so encryption and decryption are different code paths (unlike DES, where they share one). The "equivalent inverse cipher" reorders operations so decryption looks like encryption with modified keys, which is handy for hardware that wants one datapath for both.

When to use AES

  • Bulk data encryption — disk encryption (BitLocker, FileVault, LUKS), TLS record payloads, VPN tunnels, database column encryption. Anywhere you have a shared key and lots of bytes.
  • After a key exchange — RSA or ECDH agree on a short symmetric key, then AES does the heavy lifting. This hybrid pattern is how every HTTPS connection works.
  • When you need authenticated encryption — use AES-GCM or AES-CCM, which produce both ciphertext and an authentication tag so tampering is detected.
  • When hardware support exists — on any chip from the last decade, AES-NI makes AES the fastest and the safest choice (constant-time, no cache leaks).

Do not use AES to encrypt a key for transport over an untrusted channel without a shared secret — that is what public-key crypto is for. Do not hand-roll a mode; reach for a vetted AEAD construction. And never use plain ECB mode for anything longer than a single block.

AES vs other ciphers

AESChaCha20DES / 3DESRSABlowfishTwofish
TypeSymmetric blockSymmetric streamSymmetric blockAsymmetricSymmetric blockSymmetric block
Block / unit size128 bits512-bit keystream block64 bitskey-size dependent64 bits128 bits
Key size128 / 192 / 25625656 / 168 effective2048–4096 typical32–448128–256
Hardware accelYes (AES-NI)No, but fast in SIMDRareNoNoNo
Speed (1 core)1–5 GB/s w/ AES-NI~1–2 GB/s software~30–100 MB/sthousands of ops/s~100 MB/s~100 MB/s
StatusNIST standard, unbrokenRFC 8439, unbrokenDES broken; 3DES retired 2023standard, but slowsupersededAES finalist, niche
Best forEverything with hardwareMobile / no-AES-NI CPUsLegacy onlyKey exchange / signaturesLegacy embeddedConservative alternative

The two ciphers worth actually choosing between today are AES and ChaCha20. On a server with AES-NI, AES-GCM wins. On a phone or microcontroller without hardware AES, ChaCha20-Poly1305 is faster and avoids the cache-timing pitfalls of table-based AES — which is exactly why TLS 1.3 standardized both and lets the client pick.

What the numbers actually say

  • Brute force is hopeless. AES-128 has 2¹²⁸ ≈ 3.4×10³⁸ keys. A machine testing a billion-billion (10¹⁸) keys per second would need on the order of 10¹³ years — about a thousand times the age of the universe — to cover half the keyspace.
  • The best known attack barely dents it. The strongest published key-recovery attack on full AES-128 (a biclique attack, 2011) costs about 2¹²⁶·¹ operations — roughly a 4× speedup over brute force. That is academically interesting and operationally meaningless.
  • AES-NI is the real story. One AESENC instruction does a full round in ~4 cycles. A 3 GHz core does an AES-128 block (10 rounds, pipelined across blocks) at well over 1 GB/s; multi-block CTR/GCM pipelines hit 4–5 GB/s. Software table lookups manage roughly 100–400 MB/s — a 10× gap.
  • Quantum margin. Grover's algorithm square-roots brute force, so AES-128 offers ~64 bits of quantum security and AES-256 ~128. AES-256 is the conservative pick for data that must stay secret for decades.

JavaScript: encrypt with the Web Crypto API

Do not implement AES from scratch in production — use the platform primitive. In the browser and Node, that is crypto.subtle, which uses AES-NI under the hood and runs in constant time.

// AES-256-GCM: authenticated encryption with a random 96-bit IV.
async function encrypt(plaintext, rawKey) {        // rawKey: 32 bytes
  const key = await crypto.subtle.importKey(
    'raw', rawKey, { name: 'AES-GCM' }, false, ['encrypt']);
  const iv = crypto.getRandomValues(new Uint8Array(12)); // NEVER reuse with same key
  const ct = await crypto.subtle.encrypt(
    { name: 'AES-GCM', iv }, key, new TextEncoder().encode(plaintext));
  // Prepend the IV so the decryptor can read it back.
  return new Uint8Array([...iv, ...new Uint8Array(ct)]);
}

async function decrypt(blob, rawKey) {
  const key = await crypto.subtle.importKey(
    'raw', rawKey, { name: 'AES-GCM' }, false, ['decrypt']);
  const iv = blob.slice(0, 12), ct = blob.slice(12);
  const pt = await crypto.subtle.decrypt({ name: 'AES-GCM', iv }, key, ct);
  return new TextDecoder().decode(pt);   // throws if the tag fails — tamper detected
}

Two non-negotiables hide in those eight lines. The IV (nonce) must be unique for every message under a given key — reusing a GCM nonce leaks the XOR of two plaintexts and the authentication key. And the decrypt call throws on a bad tag rather than returning garbage, so authentication is automatic.

Python: one round, from scratch

To actually see the round structure, here is a single AES round on a 4×4 state in pure Python — illustrative, not constant-time, never for real keys. SBOX and xtime (multiply-by-2 in GF(2⁸)) are the only primitives.

def xtime(b):                       # multiply a byte by 2 in GF(2^8)
    b <<= 1
    if b & 0x100:                   # overflow past 8 bits
        b ^= 0x11B                  # reduce mod x^8 + x^4 + x^3 + x + 1
    return b & 0xFF

def sub_bytes(state):
    return [SBOX[b] for b in state]               # non-linear substitution

def shift_rows(s):                  # state is column-major: s[r + 4*c]
    out = [0] * 16
    for r in range(4):
        for c in range(4):
            out[r + 4*c] = s[r + 4*((c + r) % 4)] # row r shifts left by r
    return out

def mix_columns(s):
    out = [0] * 16
    for c in range(4):
        col = s[4*c:4*c+4]
        out[4*c+0] = xtime(col[0]) ^ (xtime(col[1]) ^ col[1]) ^ col[2] ^ col[3]
        out[4*c+1] = col[0] ^ xtime(col[1]) ^ (xtime(col[2]) ^ col[2]) ^ col[3]
        out[4*c+2] = col[0] ^ col[1] ^ xtime(col[2]) ^ (xtime(col[3]) ^ col[3])
        out[4*c+3] = (xtime(col[0]) ^ col[0]) ^ col[1] ^ col[2] ^ xtime(col[3])
    return out

def add_round_key(state, round_key):
    return [b ^ k for b, k in zip(state, round_key)]   # XOR with the round key

def aes_round(state, round_key):     # one full middle round
    state = sub_bytes(state)
    state = shift_rows(state)
    state = mix_columns(state)
    return add_round_key(state, round_key)

Notice MixColumns uses only xtime and XOR: a multiply-by-3 is xtime(b) ^ b, because 3 = 2 + 1 and field addition is XOR. That is why AES is implementable in a few hundred bytes of code on an 8-bit microcontroller.

Modes of operation — the part people get wrong

A block cipher only encrypts one 16-byte block. To encrypt a file you need a mode that chains blocks together. The mode matters more for real-world security than the cipher itself.

  • ECB (Electronic Codebook) — each block encrypted independently. Identical plaintext blocks ⇒ identical ciphertext blocks, so structure leaks. The "ECB penguin" demo is the canonical warning. Effectively never use it.
  • CBC (Cipher Block Chaining) — XOR each plaintext block with the previous ciphertext block before encrypting. Hides patterns, but needs a random IV, is serial on encryption, and is vulnerable to padding-oracle attacks if you decrypt-then-check.
  • CTR (Counter) — encrypt a counter to make a keystream, then XOR with plaintext. Turns AES into a stream cipher: parallel, random-access, no padding. But it provides no integrity, and reusing a (key, counter) pair is catastrophic.
  • GCM (Galois/Counter Mode) — CTR plus a fast polynomial MAC (GHASH) for authentication. This is the default AEAD mode in TLS 1.3. Still nonce-sensitive: a repeated nonce breaks both confidentiality and authentication.
  • XTS — designed for disk sector encryption, where you can't store a per-sector nonce.

Variants worth knowing

AES-192 and AES-256. Same algorithm, larger keys, more rounds (12 and 14). AES-256 is the choice for long-lived secrets and post-quantum margin. The performance penalty is small — proportional to the extra rounds, roughly 20% and 40% slower than AES-128.

AES-GCM-SIV. A nonce-misuse-resistant AEAD (RFC 8452). If you accidentally reuse a nonce, you leak only whether two messages were identical — not the whole keystream. Worth it when nonce uniqueness is hard to guarantee.

AES-CCM. CTR for encryption plus CBC-MAC for authentication, in one pass. Used in WPA2/WPA3 Wi-Fi and Bluetooth because it needs no separate MAC key and suits constrained hardware.

Rijndael (the full cipher). AES standardized only the 128-bit block. Rijndael itself supports 128/192/256-bit blocks. The larger-block versions are rarely used but live on in some specialized constructions.

Deoxys-II and other CAESAR winners. AES-round-based AEADs that target nonce-misuse resistance and tiny hardware; relevant if you're designing protocols rather than using them.

Common bugs and edge cases

  • Reusing an IV/nonce. The single most damaging AES mistake. In CTR and GCM, a repeated nonce under the same key reveals the XOR of plaintexts and, in GCM, the authentication key itself. Always use a fresh random or strictly counter-based nonce.
  • Using ECB by default. Many libraries' "AES.encrypt" with no mode argument silently defaults to ECB. Always specify an authenticated mode.
  • Encrypting without authenticating. CBC and CTR give you confidentiality but no tamper detection. Pair them with a MAC (encrypt-then-MAC) or just use GCM.
  • Padding-oracle leaks. CBC with PKCS#7 padding, where an error message distinguishes "bad padding" from "bad MAC," lets an attacker decrypt byte by byte. AEAD modes sidestep this entirely.
  • Cache-timing attacks on software AES. Table-based S-box lookups leak the key through cache access patterns. Use a hardware (AES-NI) or bitsliced constant-time implementation, not a naive table version, for adversary-facing code.
  • Deriving keys from passwords directly. A user password is not a 256-bit key. Run it through a slow KDF (Argon2, scrypt, PBKDF2) first, or the cipher's strength is irrelevant.

Frequently asked questions

Why does AES use 10, 12, or 14 rounds instead of one?

One round mixes a byte with at most a few neighbours, so a single pass leaks structure. Each extra round widens the diffusion until one input bit affects all 128 output bits. AES-128 needs 10 rounds, AES-192 needs 12, and AES-256 needs 14 — more rounds compensate for the larger key and the deeper related-key attacks it has to resist.

Is AES symmetric or asymmetric encryption?

AES is symmetric: the same secret key both encrypts and decrypts. That makes it thousands of times faster than RSA or elliptic-curve cryptography, which is why real systems use RSA/ECDH only to exchange a short AES key, then encrypt the actual data with AES.

What is the AES S-box and why is it built that way?

The S-box is a fixed 256-entry lookup table that replaces each byte. It is the multiplicative inverse in the field GF(2^8) followed by an affine transform. The inverse gives strong non-linearity against differential and linear cryptanalysis; the affine map removes the inverse's fixed points so no byte maps to itself or its complement.

Why should you never use AES in ECB mode?

ECB encrypts every 16-byte block independently, so identical plaintext blocks become identical ciphertext blocks. Patterns survive — the famous 'ECB penguin' image is still recognizable after encryption. Use an authenticated mode like GCM, or at minimum a chaining/counter mode (CBC, CTR) with a unique IV per message.

How fast is AES, and why is it so fast?

Modern x86 and ARM chips include AES-NI instructions that run one round in hardware. With AES-NI a single core encrypts roughly 1 to 5 GB/s; without it, a software table lookup runs closer to 100 to 400 MB/s. The hardware path is also constant-time, which defeats the cache-timing attacks that plagued table-based software.

Can quantum computers break AES?

Grover's algorithm gives at most a square-root speedup on brute force, halving the effective key strength: AES-128 drops to about 64 bits of quantum security, AES-256 to about 128. So AES-256 stays comfortably safe against foreseeable quantum attacks, while AES-128 is the one usually upgraded for long-term post-quantum margins.