1. Home
  2. Cryptography & Network Security
  3. AES (Advanced Encryption Standard)

AES (Advanced Encryption Standard)

The cipher that protects almost everything online. A 4×4 grid of bytes goes through 10 rounds of SubBytes, ShiftRows, MixColumns and AddRoundKey.

Interactive 3DAdvanced16 min readCNSUpdated

Drag to rotate · Right-drag to pan · Click, then scroll to zoom · Space play · ←→ step

What's happening

Pseudocode

    Try this in the 3D model

    • Press Round 1, step by step. During ShiftRows, which row does not move?
    • In the MixColumns step, check the arithmetic for the top byte of column 0.
    • Press All 10 rounds. Does the ciphertext match the one in the AES standard?
    • Press Flip one bit. After which round do about half of the 128 bits differ?

    The cipher behind the padlock

    Every time your browser shows a padlock, your data is most likely encrypted with AES: HTTPS, Wi-Fi (WPA2 and WPA3), WhatsApp messages, BitLocker and FileVault disks, and VPNs all use it.

    In 1997 the old standard, DES, was in trouble: its 56-bit key could be broken by brute force, and in 1998 a machine built for under $250,000 did it in 56 hours. The US standards agency NIST held an open competition for a successor. In 2000 it picked Rijndael, by the Belgian cryptographers Joan Daemen and Vincent Rijmen. It became AES, published as the standard FIPS-197.

    AES is a symmetric block cipher: the same secret key encrypts and decrypts, and it works on fixed-size blocks.

    AES-128 AES-192 AES-256
    Block size 128 bits 128 bits 128 bits
    Key size 128 bits 192 bits 256 bits
    Rounds 10 12 14

    The state

    The 16 bytes of a block are written into a 4 × 4 grid called the state, column by column: bytes 0–3 form the first column, bytes 4–7 the second, and so on. Every step changes this grid.

    One round, four steps

    Claude Shannon said a good cipher needs confusion (the relation between key and ciphertext is complicated) and diffusion (every input bit affects many output bits). Each AES step provides one of them:

    Step What it does Purpose
    SubBytes Replaces every byte using the S-box, a fixed table of 256 entries Confusion: the only non-linear step
    ShiftRows Rotates row r by r places to the left Diffusion between columns
    MixColumns Multiplies each column by a fixed 4 × 4 matrix in GF(2⁸) Diffusion within a column
    AddRoundKey XORs the state with this round’s key Brings in the secret key

    The whole of AES-128:

    1. Round 0: AddRoundKey with the key itself.
    2. Rounds 1–9: SubBytes → ShiftRows → MixColumns → AddRoundKey.
    3. Round 10: SubBytes → ShiftRows → AddRoundKey (no MixColumns).

    Worked example: round 1 of the FIPS-197 example

    The standard encrypts the plaintext 32 43 f6 a8 88 5a 30 8d 31 31 98 a2 e0 37 07 34 with the key 2b 7e 15 16 28 ae d2 a6 ab f7 15 88 09 cf 4f 3c. Each line below is the state written column by column:

    Step State (column 0 · column 1 · column 2 · column 3)
    After round 0 19 3d e3 be · a0 f4 e2 2b · 9a c6 8d 2a · e9 f8 48 08
    SubBytes d4 27 11 ae · e0 bf 98 f1 · b8 b4 5d e5 · 1e 41 52 30
    ShiftRows d4 bf 5d 30 · e0 b4 52 ae · b8 41 11 f1 · 1e 27 98 e5
    MixColumns 04 66 81 e5 · e0 cb 19 9a · 48 f8 d3 7a · 28 06 26 4c
    Round key 1 a0 fa fe 17 · 88 54 2c b1 · 23 a3 39 39 · 2a 6c 76 05
    AddRoundKey a4 9c 7f f2 · 68 9f 35 2b · 6b 5b ea 43 · 02 6a 50 49

    After all 10 rounds the ciphertext is 39 25 84 1d 02 dc 09 fb dc 11 85 97 19 6a 0b 32.

    SubBytes

    The S-box is a 16 × 16 table. The first hex digit of a byte picks the row and the second picks the column: 19 is row 1, column 9, which holds d4. Behind the table is maths: take the byte’s multiplicative inverse in GF(2⁸), then apply a fixed bit-mixing (affine) transform.

    ShiftRows

    Drawn as rows, the state before and after ShiftRows:

    Row Before After Rotation
    0 d4 e0 b8 1e d4 e0 b8 1e none
    1 27 bf b4 41 bf b4 41 27 1 left
    2 11 98 5d 52 5d 52 11 98 2 left
    3 ae f1 e5 30 30 ae f1 e5 3 left

    MixColumns

    Each column is multiplied by this matrix:

    02 03 01 01
    01 02 03 01
    01 01 02 03
    03 01 01 02

    The arithmetic is in GF(2⁸), the same mod-2 polynomial arithmetic as a CRC: addition is XOR, and multiplying by 02 means shifting left one bit and XORing 1b if a bit falls off the top. Multiplying by 03 is ·02 XOR the byte itself. For column 0 = (d4, bf, 5d, 30):

    • 02 · d4 = b3 (d4 = 1101 0100; shifted it is 1010 1000 with a 1 falling off, and a8 ⊕ 1b = b3)
    • 03 · bf = 02 · bf ⊕ bf = 65 ⊕ bf = da
    • top byte = b3 ⊕ da ⊕ 5d ⊕ 30 = 04

    AddRoundKey and the key schedule

    AES-128 needs 11 round keys, one for round 0 and one for each round, made from the key by the key schedule. A round key is four 4-byte words. The first word of round key 1 is made like this:

    1. Take the last word of the key, 09 cf 4f 3c, and rotate it one byte: cf 4f 3c 09 (RotWord).
    2. Put each byte through the S-box: 8a 84 eb 01 (SubWord).
    3. XOR the round constant into the first byte: 8a ⊕ 01 = 8b (Rcon = 01, 02, 04, 08, …).
    4. XOR with the first word of the key, 2b 7e 15 16: the result is a0 fa fe 17.

    Each of the other three words is the previous word XOR the word four places back.

    Decryption

    Decryption runs the inverse steps in reverse order: InvShiftRows (rotate right), InvSubBytes (the inverse S-box), AddRoundKey (XOR is its own inverse) and InvMixColumns (multiply by the inverse matrix, with entries 0e, 0b, 0d, 09), using the round keys from last to first.

    The avalanche effect

    Change one bit of the plaintext and, after just two rounds, about half of the 128 bits of the state are different. The two ciphertexts look completely unrelated. Press Flip one bit in the model to watch the difference spread: SubBytes changes a whole byte, MixColumns spreads it over a column, and ShiftRows carries it into the other columns.

    Modes of operation

    AES encrypts exactly 16 bytes. A mode of operation decides how longer messages are handled:

    • ECB encrypts every block on its own. Equal blocks give equal ciphertext, so patterns leak (the famous “ECB penguin” picture). Never use it.
    • CBC XORs each block with the previous ciphertext block before encrypting.
    • CTR encrypts a counter and XORs the result with the message, turning AES into a stream cipher.
    • GCM is CTR plus an authentication tag that detects tampering. It is what HTTPS uses most.

    AES vs DES

    DES (1977) AES (2001)
    Block size 64 bits 128 bits
    Key size 56 bits 128, 192 or 256 bits
    Rounds 16 10, 12 or 14
    Structure Feistel network (half the block per round) Substitution–permutation network (the whole block per round)
    Status Broken by brute force (1998) Secure, the global standard

    Code

    AES-128 from scratch, following the steps above. It prints the ciphertext from the standard:

    def xtime(b):                       # multiply by 02 in GF(2^8)
        return ((b << 1) ^ 0x1B) & 0xFF if b & 0x80 else b << 1
    
    def gmul(a, b):                     # multiply two bytes in GF(2^8)
        r = 0
        while b:
            if b & 1:
                r ^= a
            a, b = xtime(a), b >> 1
        return r
    
    def make_sbox():
        sbox = []
        for x in range(256):
            inv = next((y for y in range(1, 256) if gmul(x, y) == 1), 0)   # inverse, 0 → 0
            s = inv
            for k in range(1, 5):                                          # affine transform
                s ^= ((inv << k) | (inv >> (8 - k))) & 0xFF
            sbox.append(s ^ 0x63)
        return sbox
    
    SBOX = make_sbox()
    RCON = [0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36]
    
    def expand_key(key):                # 16 bytes → 11 round keys of 16 bytes
        w = [list(key[i:i + 4]) for i in range(0, 16, 4)]
        for i in range(4, 44):
            t = w[i - 1][:]
            if i % 4 == 0:
                t = [SBOX[b] for b in t[1:] + t[:1]]                       # RotWord, SubWord
                t[0] ^= RCON[i // 4 - 1]
            w.append([a ^ b for a, b in zip(w[i - 4], t)])
        return [sum(w[4 * r:4 * r + 4], []) for r in range(11)]
    
    def shift_rows(s):                  # byte s[r + 4c] is row r, column c
        return [s[r + 4 * ((c + r) % 4)] for c in range(4) for r in range(4)]
    
    def mix_columns(s):
        out = []
        for c in range(4):
            a = s[4 * c:4 * c + 4]
            out += [gmul(a[0], 2) ^ gmul(a[1], 3) ^ a[2] ^ a[3],
                    a[0] ^ gmul(a[1], 2) ^ gmul(a[2], 3) ^ a[3],
                    a[0] ^ a[1] ^ gmul(a[2], 2) ^ gmul(a[3], 3),
                    gmul(a[0], 3) ^ a[1] ^ a[2] ^ gmul(a[3], 2)]
        return out
    
    def aes128_encrypt(block, key):
        keys = expand_key(key)
        s = [b ^ k for b, k in zip(block, keys[0])]                        # round 0
        for rnd in range(1, 11):
            s = shift_rows([SBOX[b] for b in s])                           # SubBytes, ShiftRows
            if rnd < 10:
                s = mix_columns(s)
            s = [b ^ k for b, k in zip(s, keys[rnd])]                      # AddRoundKey
        return bytes(s)
    
    pt = bytes.fromhex("3243f6a8885a308d313198a2e0370734")
    key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")
    print(aes128_encrypt(pt, key).hex())    # 3925841d02dc09fbdc118597196a0b32

    Writing your own AES is great for learning but dangerous in real software: hand-made code leaks secrets through timing and is easy to misuse. Real programs use a library and an authenticated mode such as GCM:

    # pip install cryptography
    import os
    from cryptography.hazmat.primitives.ciphers.aead import AESGCM
    
    key = AESGCM.generate_key(bit_length=128)
    nonce = os.urandom(12)                  # never reuse a nonce with the same key
    sealed = AESGCM(key).encrypt(nonce, b"attack at dawn", None)
    print(AESGCM(key).decrypt(nonce, sealed, None))     # b'attack at dawn'

    Common mistakes

    • Filling the state row by row. AES fills it column by column, so the second input byte is row 1, column 0.
    • Rotating rows to the right in ShiftRows. Encryption rotates left; decryption rotates right.
    • Using ordinary multiplication in MixColumns. 02 · d4 is not 1a8: the result must stay a byte, so it is reduced to b3 in GF(2⁸).
    • Doing MixColumns in the last round. Round 10 (AES-128) skips it.
    • Thinking a bigger key means a bigger block. AES-256 still encrypts 128-bit blocks; it just uses 14 rounds.

    Complexity at a glance

    Case / operationTimeWhy
    Encrypting one 16-byte blockO(1)Always 10, 12 or 14 rounds of 16 byte operations.
    Encrypting n bytesO(n)Modern CPUs have AES instructions (AES-NI), reaching several GB/s.
    Brute force on a 128-bit key2¹²⁸ ≈ 3.4 × 10³⁸ triesFar beyond any computer that could ever be built.
    Best known attack on full AES-128about 2¹²⁶Only 4× faster than brute force, so AES is still unbroken.

    Quick check

    Test yourself — pick an answer to see if you got it.

    1. What is the block size of AES?

    2. How many rounds does AES-256 use?

    3. Which AES step is the only non-linear one?

    4. In ShiftRows, by how many places does row 2 rotate?

    5. Which step is left out of the last round?

    6. What is 57 ⊕ 83 in hexadecimal (as in AddRoundKey)?

    Saved in this browser.
    Spotted a mistake or a bug in the 3D model?

    Report a mistake

    in AES (Advanced Encryption Standard). Thank you — every report makes the lesson better for the next reader.

    We'll also include a link to the step of the 3D model you're on and your browser type, so we can reproduce it.