krishworkstech.com

AES, Explained from Scratch

AES, Explained from Scratch

AES

A walk through the AES block cipher for readers who know what addition and multiplication are, but who have never had cryptography patiently explained to them. By the end you will know exactly what AES does to your bytes, and why the result is hard to undo without the key.

1. What we are trying to build

Forget cryptography for a moment. Here is a puzzle.

 

You and a friend want a machine that takes any 16 bytes of input and produces 16 bytes of output, like this:

16 bytes in (plaintext) machine (the cipher) 16 bytes out (ciphertext) 16-byte secret key

You both share a secret — a 16-byte key that you and only you know. The machine has the following properties:

  1. It is reversible if you have the key. Feed the output back in with the same key (running the machine in reverse) and you get the original 16 bytes back.
  2. It is not reversible without the key. Someone who sees a million (input, output) pairs but doesn’t know the key cannot figure the key out, no matter how clever they are or how much computer time they have.
  3. It “looks random.” If you flip a single bit of the input or the key, roughly half the output bits change. There is no visible pattern relating inputs to outputs.

A machine that does all three things is called a block cipher. The specific block cipher we are going to build up to is called AES — the Advanced Encryption Standard, designed in the late 1990s and adopted by the US government in 2001. It is, by a comfortable margin, the most widely deployed cipher on earth. Every TLS connection your browser opens, every encrypted Wi-Fi packet, every iPhone disk, every LoRa payload in the sister article — all are AES at the bottom.

The plan for this document: spend the first few sections on prerequisites, then introduce AES’s four basic operations one at a time, then put them together into the full algorithm, then walk through a concrete numerical example so you can see every byte change. You won’t need any maths beyond grade-school arithmetic and a little patience.

2. Prerequisites: bytes, hex, and XOR

Bytes

byte is 8 bits. Each bit is 0 or 1, so a byte can hold any of 2⁸ = 256 different values, from 0 to 255. We will write bytes in hexadecimal because it is more compact than binary and aligns nicely with the structure of AES.

Hexadecimal uses 16 digits: 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, A, B, C, D, E, F. Each hex digit represents 4 bits (half a byte), so any byte fits in exactly two hex digits.

Decimal Binary Hex
0 0000 0000 00
1 0000 0001 01
10 0000 1010 0A
15 0000 1111 0F
16 0001 0000 10
100 0110 0100 64
255 1111 1111 FF

If you have not used hex before: it is just a different way of writing the same numbers. 0x4C means the value 76 in decimal, which is 01001100 in binary. They are all the same byte.

A 16-byte block, written in hex with spaces between bytes, looks like:

48 65 6C 6C 6F 2C 20 41 45 53 20 77 6F 72 6C 64

(That happens to be the ASCII for “Hello, AES world”.)

XOR — the one operation you need to be comfortable with

The whole of AES uses only three kinds of byte-level operation: lookup in a tablerearranging bytes, and XOR. We are about to lean on XOR heavily, so let us get familiar with it.

XOR stands for “exclusive or.” It is an operation that takes two bits and gives back one bit, with this truth table:

A B A ⊕ B
0 0 0
0 1 1
1 0 1
1 1 0

In words: the result is 1 if the two inputs are different, and 0 if they are the same.

For two whole bytes, we XOR each pair of bits independently:

     0x4C  =  0 1 0 0 1 1 0 0
     0xA9  =  1 0 1 0 1 0 0 1
     ─────────────────────────  ⊕
     0xE5  =  1 1 1 0 0 1 0 1

The only fact about XOR you really need to remember is its self-inverse property:

   A ⊕ B ⊕ B  =  A

If you XOR a value with anything, then XOR with the same thing again, you get the original value back. This is what makes XOR-based ciphers reversible: encrypting and decrypting are literally the same operation.

Try it: take any byte X, XOR with 0xA5, and then XOR the result with 0xA5 again. You will land back on X every time.

A first cipher made of nothing but XOR

The simplest cipher in the world is: take your message, XOR each byte with a corresponding byte of the secret key, send the result. To decrypt, the recipient XORs again with the same key.

   plaintext  P = 48 65 6C 6C 6F ...
   key        K = A1 B2 C3 D4 E5 ...
                  ──────────────── ⊕
   ciphertext C = E9 D7 AF B8 8A ...

If the key is truly random, never reused, and as long as the plaintext, this is a one-time pad — provably unbreakable. The catch is that you need a fresh chunk of random key for every byte of data you ever encrypt, and that is impractical in any real system. Reuse the key even slightly, and the cipher falls apart.

So we want a way to take a short, fixed-size key and use it to generate as much pseudo-random output as we like — and to do that in a way that depends so intricately on the input that an attacker cannot recover the key from the output. That is the gap AES fills.

3. Why simple ideas don't work

It is worth seeing why a few obvious approaches fail, because each failure motivates one piece of AES’s design.

Idea: a substitution cipher

Pick a permutation of the 256 byte values — that is, a lookup table that maps every byte to some other byte, with no two inputs mapping to the same output. Now to encrypt, look up each byte in the table. To decrypt, look up in the reverse table.

This is broken by frequency analysis. If the plaintext is English, the letter ‘e’ is the most common; whatever byte ‘e’ is mapped to will be the most common byte in the ciphertext. With enough ciphertext you can recover the entire table.

But — a good substitution table is still a useful ingredient. It hides the per-byte values nonlinearly, and that turns out to be essential. AES uses a substitution table called the S-box.

A concrete example. Suppose we use this tiny substitution table for the 26 capital letters (the rest map to themselves):

   plain :   A B C D E F G H I J K L M N O P Q R S T U V W X Y Z
   cipher:   Q W E R T Y U I O P A S D F G H J K L Z X C V B N M
Encrypt the message HELLO WORLD:
   H → I        W → V
   E → T        O → G
   L → S        R → K
   L → S        L → S
   O → G        D → R

   HELLO WORLD  →  ITSSG VGKSR

Two things to notice. First, the two Ls in HELLO become two Ss in the same positions. The pattern of repeats survives the substitution. Any English-speaking attacker who sees ITSSG knows the third and fourth letters are the same, which in English narrows the candidates to a small handful of common five-letter words (HELLO, MELLS, FILLS, BILLS, …). Second, with even a paragraph of ciphertext an attacker can count which cipher byte is most common (it will almost certainly be E‘s replacement, because E is the most common letter in English), which is second-most common, and so on, and reconstruct the entire table.

This is frequency analysis, and it has been breaking substitution ciphers since the 9th century, when al-Kindi described it in Baghdad. AES cannot rely on substitution alone for that reason — but a very carefully chosen substitution, applied repeatedly with other operations between, is exactly what we need.

Idea: a substitution cipher

Take the 16 bytes and shuffle their positions. The first byte goes where the seventh was, the seventh where the third was, and so on.

This doesn’t help at all on its own. The byte values are unchanged, so frequency analysis still works. But — a permutation is a useful ingredient for spreading information around. AES uses a permutation called ShiftRows.

A concrete example. Take the 16-byte block:

   position:    0  1  2  3  4  5  6  7  8  9 10 11 12 13 14 15
   byte:        H  E  L  L  O  _  A  E  S  _  W  O  R  L  D  !

(Treating _ as space.) Now permute positions: send byte 0 to position 7, byte 1 to position 12, byte 2 to position 3, and so on through some fixed scrambling table. The output is some shuffled version like:

   E  W  L  L  R  _  !  H  S  A  D  E  O  L  O  _

It looks different. But count the bytes — the output still contains exactly three Ls, two Os, two Es, two spaces, and one each of the rest. The multiset of bytes is identical between input and output. An attacker counting byte frequencies cannot tell the plaintext and ciphertext apart at all. Frequency analysis still wins. Worse, the same permutation always produces the same shuffle, so a single known plaintext-ciphertext pair reveals the entire permutation table to anyone who can compare them position-by-position.

A permutation alone is useless. But a permutation as a step inside something larger is essential for spreading information across the state — which is exactly what AES uses ShiftRows for.

Idea: mix bytes together with arithmetic

If byte 0 of the output is some combination of bytes 0, 1, 2, 3 of the input, then changing input byte 1 changes output byte 0. That is called diffusion: each output bit comes to depend on many input bits. AES uses a diffusion step called MixColumns.

A concrete example. Suppose we define a “mixing” step like this:

   new[0] = (old[0] + old[1] + old[2] + old[3]) mod 256
   new[1] = (         old[1] + old[2] + old[3]) mod 256
   new[2] = (                  old[2] + old[3]) mod 256
   new[3] =                              old[3]

This looks like it does something — each output byte depends on several input bytes. And it is invertible: we can recover the inputs from the outputs by working from the bottom up (old[3] = new[3], then old[2] = new[2] − old[3], and so on). Try it with input (10, 20, 30, 40):

   new[0] = 10 + 20 + 30 + 40 = 100
   new[1] =      20 + 30 + 40 =  90
   new[2] =           30 + 40 =  70
   new[3] =                40 =  40

But this mixing is linear: if we double every input, every output doubles. If we add 5 to every input, every output increases by 5 × (number of inputs it sums). A cryptanalyst with a few known plaintext-ciphertext pairs can write down linear equations and solve them with high-school algebra to recover the secret. Linear ciphers are not really ciphers; they are obfuscations.

The fix: combine linear mixing with a nonlinear step (the S-box, the substitution table). Mixing spreads information; the S-box destroys the algebra. Together they work; either alone is broken.

Idea: XOR a key into the mix at every stage

Every operation so far has been fixed — the same table, the same shuffle, the same mixing formula no matter what the key is. That is no good; the cipher needs to depend on the secret key. AES sprinkles the key in by XORing it into the state at every round. This step is called AddRoundKey.

A concrete example: why repeating a key is catastrophic. Suppose we naively encrypt by XOR alone, and we encrypt two different messages with the same 8-byte key:

   P₁ = "HELLO!!!"        K = "SeCrEt99"        C₁ = P₁ ⊕ K
   P₂ = "WORLD!!!"        same K                C₂ = P₂ ⊕ K

The attacker captures both ciphertexts (they were sent over the air). They compute:

   C₁ ⊕ C₂  =  (P₁ ⊕ K) ⊕ (P₂ ⊕ K)
            =   P₁ ⊕ P₂ ⊕ K ⊕ K
            =   P₁ ⊕ P₂ ⊕ 0
            =   P₁ ⊕ P₂

The key vanishes from the equation. The attacker now holds the XOR of the two plaintexts. If they can guess any part of either plaintext — say, they suspect one of the messages begins with the word “HELLO” — they can recover the corresponding bytes of the other message immediately. Given many messages encrypted under the same key, this attack quickly reconstructs the entire plaintext stream.

This is exactly how the US Army’s VENONA project broke Soviet diplomatic cables in the 1940s: KGB clerks accidentally reused one-time-pad key material, and from the resulting XOR-of-plaintexts the cryptanalysts pulled apart years of espionage traffic.

For AES, the lesson is that the same key is used many times — for many blocks of many messages — so XOR alone is not enough. AES interleaves AddRoundKey with the nonlinear S-box step, which breaks the simple cancellation above: if you XOR two AES ciphertexts together, the keys do not vanish, because they were stirred through S-boxes in between.

Idea: do it once, you're done

If you do each step exactly once, the math turns out to still be breakable — there are clever attacks that peel off one layer at a time. The fix is to repeat the four operations many times in sequence, with a different chunk of the key XORed in at each repetition. AES-128 does ten rounds.

A toy illustration. Imagine we run just one round of (SubBytes, ShiftRows, MixColumns, AddRoundKey). An attacker who can submit pairs of plaintexts that differ in a single byte observes which output bytes change. Because only one round has run, the change has only had one MixColumns step to spread — at most four output bytes are affected. From those four bytes and the known input difference, the attacker can write equations and solve for the round key with tractable effort. This kind of attack is called differential cryptanalysis, and it is the main reason AES designers chose ten rounds: each extra round multiplies the attacker’s work until the math becomes hopeless.

That, in outline, is the whole shape of AES: four operations, ten rounds, with the round keys derived from your secret key. Now let us look at the four operations in detail.

4. The state: a 4×4 grid of bytes

AES does not operate on the 16 bytes as a flat row. It arranges them into a 4×4 grid, called the state. Bytes are filled in column by column from the input:

Input: 16 bytes in a row b₀ b₁ b₂ b₃ b₄ b₅ b₆ b₇ b₈ b₉ b₁₀ b₁₁ b₁₂ b₁₃ b₁₄ b₁₅ become the 4×4 state, column by column b₀ b₄ b₈ b₁₂ b₁ b₅ b₉ b₁₃ b₂ b₆ b₁₀ b₁₄ b₃ b₇ b₁₁ b₁₅ col 0 col 1 col 2 col 3
Figure 1. Sixteen input bytes laid out as a 4×4 state, column by column. Each colour group of four bytes forms one column of the state.

For example, the 16 input bytes 48 65 6C 6C 6F 2C 20 41 45 53 20 77 6F 72 6C 64 become the state:

   ┌────┬────┬────┬────┐
   │ 48 │ 6F │ 45 │ 6F │
   ├────┼────┼────┼────┤
   │ 65 │ 2C │ 53 │ 72 │
   ├────┼────┼────┼────┤
   │ 6C │ 20 │ 20 │ 6C │
   ├────┼────┼────┼────┤
   │ 6C │ 41 │ 77 │ 64 │
   └────┴────┴────┴────┘

Every operation in AES transforms this state. After ten rounds the state holds the 16 bytes of ciphertext, read out the same way (column by column).

The 4×4 layout is not arbitrary. ShiftRows works on rows, MixColumns works on columns, and the geometry of “shifting rows then mixing columns” is what gives AES its excellent diffusion properties — every byte of the input ends up influencing every byte of the output within just a couple of rounds.

5. Operation 1 — SubBytes (the nonlinear scrambler)

The first operation in each round is SubBytes. It replaces every byte in the state with another byte, using a fixed lookup table.

The table is called the S-box. It is just a list of 256 bytes, indexed by an input byte (also 0 to 255). To look up the substitute for input byte X, you read entry X of the table.

Here is what the first few entries look like:

input output
0x00 0x63
0x01 0x7C
0x02 0x77
0x48 0x52
0xFF 0x16
input byte 0x48 lookup S-box (256 entries) 00 → 63 01 → 7C ... 48 → 52 ... FF → 16 out 52
Figure 2. SubBytes is a 256-entry lookup table. Each byte of the state is replaced independently with whatever the table says.

If your state contains the byte 0x48, after SubBytes it contains the byte 0x52. You do this for all 16 bytes of the state independently.

The S-box is the only nonlinear step in AES. The others (ShiftRows, MixColumns, AddRoundKey) are all linear operations — they have nice mathematical structure that makes them combine predictably. On its own, a linear cipher is breakable: an attacker can write down equations and solve them. The S-box destroys that structure by introducing a relationship between input and output that no neat formula describes.

The S-box was not chosen randomly. It was designed to satisfy specific properties — small changes in input produce unpredictable changes in output, the table has no fixed points (no byte maps to itself), and so on. The actual construction uses some mathematics called Galois fields, but you do not need to understand any of that to use AES; the S-box is just a fixed table you look things up in.

6. Operation 2 — ShiftRows (move things around)

The second operation in each round is ShiftRows. It is exactly what it sounds like: each row of the 4×4 state is shifted left by a fixed number of positions, with the bytes that fall off the left edge wrapping around to the right.

  • Row 0 is not shifted at all.
  • Row 1 shifts left by 1.
  • Row 2 shifts left by 2.
  • Row 3 shifts left by 3.
before ShiftRows a₀ a₁ a₂ a₃ b₀ b₁ b₂ b₃ c₀ c₁ c₂ c₃ d₀ d₁ d₂ d₃ after ShiftRows a₀ a₁ a₂ a₃ b₁ b₂ b₃ b₀ c₂ c₃ c₀ c₁ d₃ d₀ d₁ d₂ row 0: no shift row 1: shift left 1 row 2: shift left 2 row 3: shift left 3 No values change — only positions. Bytes that fall off the left wrap around to the right.
Figure 3. ShiftRows. Each row is rotated left by row-index positions. Information now crosses column boundaries — essential for the next step.

No byte values change here — only positions. That’s why we needed SubBytes to scramble values, and why we will need MixColumns to make each output depend on multiple inputs. ShiftRows on its own is just a reshuffling.

Why bother, then? Because of what comes next. MixColumns will mix bytes within the same column. If ShiftRows didn’t move bytes between columns, every column would be its own isolated little universe and information from byte 0 would never reach byte 5. ShiftRows is the bridge that lets MixColumns spread information across the whole state.

7. Operation 3 — MixColumns (mix bytes within each column)

The third operation is MixColumns. This is where each output byte becomes a combination of several input bytes — the diffusion step.

For each column of the state, we replace the four bytes with four new bytes computed like this:

   new[0] = (2 · old[0]) ⊕ (3 · old[1]) ⊕  old[2]     ⊕  old[3]
   new[1] =  old[0]      ⊕ (2 · old[1]) ⊕ (3 · old[2]) ⊕  old[3]
   new[2] =  old[0]      ⊕  old[1]      ⊕ (2 · old[2]) ⊕ (3 · old[3])
   new[3] = (3 · old[0]) ⊕  old[1]      ⊕  old[2]     ⊕ (2 · old[3])

The  is just XOR, which we know. The question is: what does 2 · x mean when x is a byte?

It is almost normal multiplication, with one twist. The rule is:

"2 · x": shift x one bit to the left. If the bit that fell off the top was 1, then XOR the result with 0x1B.

That’s it. Let’s try a couple of examples.

Example 1. Compute 2 · 0x57.

  • 0x57 in binary is 0101 0111.
  • Shift left by 1: 1010 1110 = 0xAE. The bit that fell off the top was 0.
  • No further adjustment needed.
  • Answer: 2 · 0x57 = 0xAE.

Example 2. Compute 2 · 0x83.

  • 0x83 in binary is 1000 0011.
  • Shift left by 1: 0000 0110 = 0x06. The bit that fell off the top was 1.
  • Because that bit was 1, XOR with 0x1B: 0x06 ⊕ 0x1B = 0x1D.
  • Answer: 2 · 0x83 = 0x1D.

The 3 · x is even easier: just compute 2 · x and then XOR with the original x. (Because 3 = 2 + 1 and in this strange arithmetic addition is XOR.)

Example. Compute 3 · 0x57.

  • 2 · 0x57 = 0xAE (from above).
  • 3 · 0x57 = 0xAE ⊕ 0x57 = 0xF9.

Why this weird multiplication? Because we want our “multiplication” to keep results inside the set of bytes (0–255). Normal multiplication would produce values larger than 255 — multiplying two bytes can give you up to 0xFE01. The shift-and-conditional-XOR rule keeps everything inside the byte range while still acting enough like multiplication that the four MixColumns formulas above have all the nice algebraic properties they need to be invertible.

If you’ve heard of “Galois field arithmetic” or “GF(2⁸)”, that’s the formal name for this kind of arithmetic. You do not need to learn the theory to use it; the rules above are everything you need to implement MixColumns correctly.

Why does MixColumns matter?

Because of MixColumns, each output byte in a column depends on all four input bytes of that column. Combined with the previous ShiftRows step (which pulled bytes from four different columns into each column), this means after one round each output byte already depends on four input bytes scattered across the state. After just two rounds, every output byte depends on every input byte.

That is the avalanche effect we wanted: change one input bit, and within two rounds the change has propagated to every byte of the state. After ten rounds, the dependency is so thorough that statistical attacks have nothing to grip onto.

8. Operation 4 — AddRoundKey (mix the secret in)

The fourth and final operation in each round is AddRoundKey. It is the simplest of the four: take the current state and XOR it with a 16-byte round key, byte by byte.

s₀ s₄ s₈ s₁₂ s₁ s₅ s₉ s₁₃ s₂ s₆ s₁₀ s₁₄ s₃ s₇ s₁₁ s₁₅ state k₀ k₄ k₈ k₁₂ k₁ k₅ k₉ k₁₃ k₂ k₆ k₁₀ k₁₄ k₃ k₇ k₁₁ k₁₅ round key = s₀⊕k₀ s₄⊕k₄ s₈⊕k₈ s₁₂⊕k₁₂ s₁⊕k₁ s₅⊕k₅ s₉⊕k₉ s₁₃⊕k₁₃ s₂⊕k₂ s₆⊕k₆ s₁₀⊕k₁₀ s₁₄⊕k₁₄ s₃⊕k₃ s₇⊕k₇ s₁₁⊕k₁₁ s₁₅⊕k₁₅ new state
Figure 4. AddRoundKey. The state and the round key are XORed byte by byte, in place. This is the only step that actually depends on the secret key.

This is where the secret key actually enters the calculation. Without AddRoundKey, AES would be a completely fixed, deterministic scrambling function — the same input would always give the same output. AddRoundKey is what makes the output depend on the key, and because XOR is the self-inverse operation we met earlier, it is also the only operation that absolutely has to use the key (the other three operations use no secrets — they are public).

Now: where do round keys come from?

9. Key expansion: from one key to eleven

AES-128 has 10 rounds. Each round needs a 16-byte round key, and there is also one extra AddRoundKey before the first round. So we need 11 round keys, total 176 bytes, derived from your single 16-byte secret key.

The procedure is called key expansion, or the key schedule. The full rules are slightly fiddly, but the idea is straightforward:

  1. The first round key is just your original key, unchanged.
  2. To produce each subsequent 4-byte word, you XOR the previous word with the word from 4 positions back.
  3. Every fourth word (i.e. at the start of each new round key) gets an extra “twist”: before the XOR, the previous word is rotated by one byte, has each of its bytes substituted through the S-box, and has a round constant XORed in. The round constants are a fixed sequence: 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36.

The extra twist every fourth word is what stops the key schedule being a simple linear function of the key. Without it, an attacker who recovered any round key could trivially derive the original. With it, round keys look unrelated to the original even though they’re deterministically computed from it.

You almost never need to understand the key schedule in detail to use AES; libraries do it for you. But it is worth knowing it exists, because it explains the otherwise-mysterious fact that “AES-128 has a 128-bit key but uses 11 different 128-bit keys internally.”

10. The full algorithm, assembled

We now have all the pieces. The full AES-128 encryption procedure is:

Plaintext (16 bytes) AddRoundKey with K₀ Rounds 1 — 9 (each identical) SubBytes ShiftRows MixColumns AddRoundKey with K₁ … K₉ Round 10 (final — no MixColumns) SubBytes ShiftRows AddRoundKey with K₁₀ Ciphertext (16 bytes)
Figure 5. AES-128 in full. An initial AddRoundKey, nine identical rounds, and one final round that skips MixColumns. Eleven round keys total, all derived from the single 16-byte secret key.

A few observations.

Why is there an AddRoundKey before round 1? Without it, the first SubBytes would happen on plaintext that has not been mixed with the key at all, which would let an attacker peel off the first substitution layer using known plaintext. The initial AddRoundKey ensures that even the very first nonlinear step is operating on key-dependent data.

Why does the last round skip MixColumns? It is a careful design choice. MixColumns is what makes decryption a different (and slightly more expensive) operation than encryption; by omitting it from the final round, the algorithm becomes structurally simpler to reverse. You can verify mathematically that omitting it loses no security — the final AddRoundKey already destroys whatever pattern MixColumns would have added.

Why ten rounds? Each round increases the difficulty of attacks exponentially. With four or five rounds there are known cryptanalytic attacks that work faster than brute force. With ten rounds, the best known attack on full AES-128 is only marginally better than brute forcing the 2¹²⁸ possible keys, which is to say, completely impractical. The 10-round count was chosen with a comfortable safety margin over the number of rounds at which any attack has ever been found.

11. A concrete trace: encrypting one block

Let’s make it real. We’ll use a well-known test vector from the AES specification, so you can reproduce it with any AES library and check your understanding.

The inputs.
   Plaintext (16 bytes):  00 11 22 33 44 55 66 77 88 99 AA BB CC DD EE FF
   Key       (16 bytes):  00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F
Step 1 — load into the state, column by column.
   ┌────┬────┬────┬────┐
   │ 00 │ 44 │ 88 │ CC │
   ├────┼────┼────┼────┤
   │ 11 │ 55 │ 99 │ DD │
   ├────┼────┼────┼────┤
   │ 22 │ 66 │ AA │ EE │
   ├────┼────┼────┼────┤
   │ 33 │ 77 │ BB │ FF │
   └────┴────┴────┴────┘

Step 2 — AddRoundKey with K₀ (the original key).

K₀ is just the input key, loaded the same way:

Check one: 0x11 ⊕ 0x01 = 0x10 ✓. 0xCC ⊕ 0x0C = 0xC0 ✓.

Step 3 — round 1, SubBytes.


                       state                    K₀                  state ⊕ K₀

                ┌────┬────┬────┬────┐    ┌────┬────┬────┬────┐    ┌────┬────┬────┬────┐
                │ 00 │ 44 │ 88 │ CC │    │ 00 │ 04 │ 08 │ 0C │    │ 00 │ 40 │ 80 │ C0 │
                ├────┼────┼────┼────┤    ├────┼────┼────┼────┤    ├────┼────┼────┼────┤
                │ 11 │ 55 │ 99 │ DD │    │ 01 │ 05 │ 09 │ 0D │    │ 10 │ 50 │ 90 │ D0 │
                ├────┼────┼────┼────┤ ⊕   ───┼────┼────┼────┤  = ├────┼────┼────┼────┤
                │ 22 │ 66 │ AA │ EE │    │ 02 │ 06 │ 0A │ 0E │    │ 20 │ 60 │ A0 │ E0 │
                ├────┼────┼────┼────┤    ├────┼────┼────┼────┤    ├────┼────┼────┼────┤
                │ 33 │ 77 │ BB │ FF │    │ 03 │ 07 │ 0B │ 0F │    │ 30 │ 70 │ B0 │ F0 │
                └────┴────┴────┴────┘    └────┴────┴────┴────┘    └────┴────┴────┴────┘

Look up every byte in the AES S-box:

                state                                S-box(state)
         ┌────┬────┬────┬────┐                ┌────┬────┬────┬────┐
         │ 00 │ 40 │ 80 │ C0 │                │ 63 │ 09 │ CD │ BA │
         ├────┼────┼────┼────┤                ├────┼────┼────┼────┤
         │ 10 │ 50 │ 90 │ D0 │       ───►     │ CA │ 53 │ 60 │ 70 │
         ├────┼────┼────┼────┤                ├────┼────┼────┼────┤
         │ 20 │ 60 │ A0 │ E0 │                │ B7 │ D0 │ E0 │ E1 │
         ├────┼────┼────┼────┤                ├────┼────┼────┼────┤
         │ 30 │ 70 │ B0 │ F0 │                │ 04 │ 51 │ E7 │ 8C │
         └────┴────┴────┴────┘                └────┴────┴────┴────┘

(If you want to verify a few: S-box[0x00] = 0x63, S-box[0x40] = 0x09, S-box[0xC0] = 0xBA. Any reference AES S-box table will confirm.)

Step 4 — round 1, ShiftRows.

Row 0 is unchanged; row 1 shifts left by 1; row 2 by 2; row 3 by 3.

   before ShiftRows:                 after ShiftRows:
   ┌────┬────┬────┬────┐             ┌────┬────┬────┬────┐
   │ 63 │ 09 │ CD │ BA │             │ 63 │ 09 │ CD │ BA │   row 0, no shift
   ├────┼────┼────┼────┤             ├────┼────┼────┼────┤
   │ CA │ 53 │ 60 │ 70 │     ───►    │ 53 │ 60 │ 70 │ CA │   row 1, left 1
   ├────┼────┼────┼────┤             ├────┼────┼────┼────┤
   │ B7 │ D0 │ E0 │ E1 │             │ E0 │ E1 │ B7 │ D0 │   row 2, left 2
   ├────┼────┼────┼────┤             ├────┼────┼────┼────┤
   │ 04 │ 51 │ E7 │ 8C │             │ 8C │ 04 │ 51 │ E7 │   row 3, left 3
   └────┴────┴────┴────┘             └────┴────┴────┴────┘

Step 5 — round 1, MixColumns.

For each column, apply the four formulas from §7. Let us compute one column in full — the first one, with bytes 63, 53, E0, 8C.

   new[0] = 2·63 ⊕ 3·53 ⊕ E0 ⊕ 8C
   new[1] =   63 ⊕ 2·53 ⊕ 3·E0 ⊕ 8C
   new[2] =   63 ⊕ 53 ⊕ 2·E0 ⊕ 3·8C
   new[3] = 3·63 ⊕ 53 ⊕ E0 ⊕ 2·8C

Compute the “doublings” we need:

  • 2·63: 0x63 = 0110 0011. Shift left → 1100 0110 = 0xC6. Top bit was 0, no XOR. So 2·63 = 0xC6.
  • 2·53: 0x53 = 0101 0011. Shift left → 1010 0110 = 0xA6. Top bit was 0. So 2·53 = 0xA6.
  • 2·E0: 0xE0 = 1110 0000. Shift left → 1100 0000 = 0xC0. Top bit was 1, XOR with 0x1B → 0xC0 ⊕ 0x1B = 0xDB. So 2·E0 = 0xDB.
  • 2·8C: 0x8C = 1000 1100. Shift left → 0001 1000 = 0x18. Top bit was 1, XOR with 0x1B → 0x18 ⊕ 0x1B = 0x03. So 2·8C = 0x03.

And the “triples” (each is “double” XOR original):

  • 3·63 = 0xC6 ⊕ 0x63 = 0xA5
  • 3·53 = 0xA6 ⊕ 0x53 = 0xF5
  • 3·E0 = 0xDB ⊕ 0xE0 = 0x3B
  • 3·8C = 0x03 ⊕ 0x8C = 0x8F

Now plug in:

   new[0] = 2·63 ⊕ 3·53 ⊕ E0 ⊕ 8C
          = C6   ⊕ F5   ⊕ E0 ⊕ 8C
          = (C6 ⊕ F5) ⊕ (E0 ⊕ 8C)
          = 33        ⊕ 6C
          = 5F

   new[1] = 63 ⊕ 2·53 ⊕ 3·E0 ⊕ 8C
          = 63 ⊕ A6   ⊕ 3B   ⊕ 8C
          = (63 ⊕ A6) ⊕ (3B ⊕ 8C)
          = C5        ⊕ B7
          = 72

   new[2] = 63 ⊕ 53 ⊕ 2·E0 ⊕ 3·8C
          = 63 ⊕ 53 ⊕ DB   ⊕ 8F
          = (63 ⊕ 53) ⊕ (DB ⊕ 8F)
          = 30        ⊕ 54
          = 64

   new[3] = 3·63 ⊕ 53 ⊕ E0 ⊕ 2·8C
          = A5   ⊕ 53 ⊕ E0 ⊕ 03
          = (A5 ⊕ 53) ⊕ (E0 ⊕ 03)
          = F6        ⊕ E3
          = 15

So column 0 becomes (5F, 72, 64, 15). The same calculation repeated for columns 1, 2, 3 fills out the new state. You now have your first round’s MixColumns output.

Step 6 — round 1, AddRoundKey with K₁.

K₁ comes from the key schedule. The first round key after the original key works out to:

   K₁ = D6 AA 74 FD D2 AF 72 FA DA A6 78 F1 D6 AB 76 FE

(Verifiable against any AES reference implementation.)

You XOR the MixColumns output with K₁, and that gives you the state after round 1.

Steps 7 to 14 — rounds 2 through 9.

Exactly the same four operations: SubBytes, ShiftRows, MixColumns, AddRoundKey, each with the appropriate round key K₂, K₃, …, K₉.

Step 15 — round 10 (final round, no MixColumns).

SubBytes, ShiftRows, AddRoundKey with K₁₀.

Step 16 — read out the ciphertext.

After round 10, the state is read column-by-column as 16 bytes. For the specific inputs we used, the result is:

   Ciphertext: 69 C4 E0 D8 6A 7B 04 30 D8 CD B7 80 70 B4 C5 5A

This is the standard NIST test vector for AES-128. If you implement AES from this article and get the same 16 output bytes, your implementation is correct.

12. Decryption: reversing the gears

Each of the four operations has an inverse:

  • InvSubBytes: a different lookup table, one that undoes SubBytes. (S-box[x] = y, so InvSubBox[y] = x.)
  • InvShiftRows: shift each row right by the same amount it was shifted left.
  • InvMixColumns: the same general shape as MixColumns but with different multiplication constants (9, 11, 13, 14 instead of 1, 2, 3 — these too are computable with shift-and-conditional-XOR rules, just slightly more involved).
  • InvAddRoundKey: identical to AddRoundKey, because XOR is its own inverse.

To decrypt, you run the rounds in reverse:

   AddRoundKey K₁₀
   InvShiftRows
   InvSubBytes

   AddRoundKey K₉
   InvMixColumns
   InvShiftRows
   InvSubBytes

   …  (rounds 8 down to 1, same pattern)

   AddRoundKey K₀

   Output: plaintext

There is also an equivalent re-ordering of the inverse rounds, called the equivalent inverse cipher, that uses the same structure as encryption (just with InvSubBytes / InvMixColumns substituted in). This is a pure implementation convenience — it lets you reuse the same code path for both directions.

The important point for understanding: AES is invertible by construction, because every step is invertible. Lookup tables can be inverted because the S-box is a permutation. Shifts can be undone by shifting the other way. MixColumns can be undone because its underlying multiplication has an inverse. AddRoundKey can be undone because XOR is self-inverse.

13. What makes AES secure?

This is the deepest question and the one we can only answer informally without diving into cryptanalysis papers. Here is the intuition.

Confusion. The relationship between the secret key and the ciphertext should be as complex as possible. SubBytes provides this: its nonlinearity means no neat algebraic expression connects key bits to ciphertext bits. An attacker cannot solve a system of equations to extract the key.

Diffusion. Every bit of the ciphertext should depend on every bit of the plaintext and every bit of the key. ShiftRows and MixColumns together provide this: after two rounds, each output byte depends on all 16 input bytes; after ten rounds, the dependency is so thorough that statistical correlations are vanishingly small.

Avalanche. Flipping one bit anywhere in the input or the key should change roughly half the output bits, in an unpredictable pattern. AES’s avalanche is excellent — measurable in the lab as about 64 bits of the 128-bit output changing on average per single-bit input change.

Resistance to known attacks. The cipher was designed with specific cryptanalytic attacks in mind — differential cryptanalysis, linear cryptanalysis — and the S-box and MixColumns coefficients were chosen to maximise resistance to those attacks. After 25 years of intense public scrutiny, the best published attacks on full AES-128 are barely better than brute force.

Key length. 2¹²⁸ possible keys is roughly 3.4 × 10³⁸. If you could test a trillion keys per second on each of a trillion machines working in parallel, you’d still need over ten million years to exhaust the key space. AES-256 pushes this even further, primarily as a hedge against future quantum computers (which can effectively halve the key length via Grover’s algorithm; AES-256 against a quantum attacker has the same difficulty as AES-128 against a classical one).

The honest summary: AES is secure because after 25 years of thousands of cryptographers trying to break it, nobody has come even close. That kind of confidence is rare in cryptography. It is what justifies AES’s ubiquity.

14. What we glossed over

For honesty’s sake, things this article skipped or simplified:

  • The S-box construction. We described the S-box as “a fixed table.” It is actually built from two steps: take the multiplicative inverse of the input byte in GF(2⁸), then apply a specific affine transformation. This construction is what gives the S-box its excellent cryptographic properties, but you do not need to understand it to use AES.
  • The MixColumns coefficients. We used 1, 2, 3 as the multiplication constants. Those come from the polynomial 3x³ + x² + x + 2, chosen so that MixColumns is a “Maximum Distance Separable” transformation — the smallest possible change in input spreads to the largest possible change in output.
  • AES-192 and AES-256. These variants use 24- and 32-byte keys and 12 and 14 rounds respectively. The core operations are identical; only the key schedule and round count differ.
  • Side-channel attacks. A naive software AES that uses table lookups can leak bits of the key through cache timing — an attacker who can observe how long different encryptions take can, with enough samples, recover the key. Constant-time implementations and hardware AES instructions avoid this.
  • Modes of operation. Encrypting more than 16 bytes requires a mode of operation — CBC, CTR, GCM, CCM — to safely combine multiple AES invocations. AES on its own only handles 16 bytes; the modes are what make it useful on real messages.

Each of these topics is the subject of its own articles, but you now have the foundations to read them with comprehension.

15. A 30-second summary, for when you've finished reading

AES takes 16 bytes of plaintext and a 16-byte key, expands the key into eleven 16-byte round keys, arranges the plaintext in a 4×4 grid, and applies ten rounds of four operations: substitute bytes through a lookup table (SubBytes), shift the rows of the grid (ShiftRows), mix the bytes in each column with a special arithmetic (MixColumns), and XOR in a round key (AddRoundKey). The four operations together produce strong confusion and diffusion; ten rounds is enough that no known attack does meaningfully better than trying every possible key, which takes longer than the universe is old. Decryption runs the same operations in reverse with the round keys in reverse order. Every step is invertible by design. The whole thing fits in a few hundred lines of C code and runs in microseconds on any modern processor.

That is AES.

A companion to the LoRa encryption retrospective — written for anyone who wants to know exactly what is happening inside the AES block they read about somewhere.

Scroll to Top
  • Schematic design
  • PCB and schematic source files
  • Assembling drawing files
  • Providing prototype/sample and production PCB service
  • Testing and validation of designed hardware
  • HIPAA
  • Azure Key
  • Management
  • ES, Checksum,
  • MD5sum
  • AWS
  • Azure
  • GCP
  • DigitalOcean
  • Kotlin
  • Python
  • Tensorflow
  • Computer Vision
  • ECG
  • SPO2
  • Heart Rate
  • Glucometer
  • Blood Pressure
  • UX UI Process
  • Figma and FigJam
  • Adobe Suite
  • Selenium Java
  • Postman
  • Swagger
  • Jmeter
  • SQL
  • Java Scripter
  • Test ng
  • Extents Reports
  • Flutter
  • Java
  • Kotlin
  • Swift
  • Dart
  • React JS
  • Python
  • NodeJS
  • Django
  • HTML, CSS, JS
RDBMS
  • PostgreSQL
  • Oracle
  • MySQL
  • MariaDB
No SQL Based
  • MongoDB
  • GCP
  • FirestoreDB
  • DynamoDB
  • Azure
  • CosmosDB
  • AWS