Advaned Encryption Standard

AES is a symmetric encryption algorithm, which means it uses the same key for both encryption and decryption. It is used in many protocols, including TLS, which establishes a secure HTTP connection, also known as HTTPS. As you read this article, AES is probably protecting the data exchanged between this website and your device.

At its core, AES is an SP-network, operating on a 4×4 matrix of 16 bytes called the state.

The algorithm consists of multiple rounds. A round consists of four state transformations, with each step designed to provide confusion and diffusion properties that a good encryption algorithm usually has.

AES Key SizeRounds
128-bit10
192-bit12
256-bit14

Let’s take a high-level overview of encryption and decryption, and then I will discuss each procedure in more detail.

Encryption

In short, the AES encryption process begins with key expansion; multiple round keys are derived from the main encryption key. After that, we simply perform rounds of operations: SubBytes, ShiftRows, MixColumns, and AddRoundKey.

Take a look at this diagram:

AES

Decryption

For decryption we do almost the same, but in reverse order, using inverse versions of the operations: InvSubBytes, InvShiftRows, InvMixColumns, and AddRoundKey.

Also, note that in decryption, AddRoundKey comes before InvMixColumns to maintain mathematical properties.

AES

Let’s now look in more detail at each procedure.

Key Expansion

Before processing anything, we need round keys. They can be computed from the main encryption key using a specific algorithm called AES Key Schedule.

We will produce N+1 keys, where N is the number of main rounds. For example, in AES-256 we have 14 main rounds; we will generate 15 keys. One extra key is needed for the initial round.

We split our main key into words. In this context, a word is 4 bytes. So, one round key is 4 words, and its size is 16 bytes.

Initial words are just words of the main encryption key.

New words are computed through a combination of XOR operations with a previous word, depending on position, modified by special transformations.

When generating every Nth word (where N is the main key length in words), we rotate bytes one position left (RotWord), substitute them using sbox (SubWord), and the result is XORed with a round constant.

Round constants
RoundRound Constant (RCON)
10x01
20x02
30x04
40x08
50x10
60x20
70x40
80x80
90x1B
100x36
110x6C
120xD8
130xAB
140x4D
150x9A

For AES-256, an additional SubWord operation occurs halfway through each expansion cycle.

We compute words until all round keys are completed.

Below are visual explanations of how the key schedule works for each key size. Sometimes it’s easier to explain using images than words.

AES-128 Key Expansion

AES-128 Key Expansion

AES-192 Key Expansion

AES-192 Key Expansion

AES-256 Key Expansion

AES-256 Key Expansion

Once the key is expanded, we can go through core transformations starting with the SubBytes.


SubBytes

This is a non-linear substitution step where each byte is replaced with another according to a predetermined lookup table called the S-box.

The S-box table has 256 unique values. These are all possible values a single byte can have.

AES

S-box table
ByteReplacer
0000000001100011
0000000101111100
0000001001110111
0000001101111011
0000010011110010
0000010101101011
0000011001101111
0000011111000101
0000100000110000
0000100100000001
0000101001100111
0000101100101011
0000110011111110
0000110111010111
0000111010101011
0000111101110110
0001000011001010
0001000110000010
0001001011001001
0001001101111101
0001010011111010
0001010101011001
0001011001000111
0001011111110000
0001100010101101
0001100111010100
0001101010100010
0001101110101111
0001110010011100
0001110110100100
0001111001110010
0001111111000000
0010000010110111
0010000111111101
0010001010010011
0010001100100110
0010010000110110
0010010100111111
0010011011110111
0010011111001100
0010100000110100
0010100110100101
0010101011100101
0010101111110001
0010110001110001
0010110111011000
0010111000110001
0010111100010101
0011000000000100
0011000111000111
0011001000100011
0011001111000011
0011010000011000
0011010110010110
0011011000000101
0011011110011010
0011100000000111
0011100100010010
0011101010000000
0011101111100010
0011110011101011
0011110100100111
0011111010110010
0011111101110101
0100000000001001
0100000110000011
0100001000101100
0100001100011010
0100010000011011
0100010101101110
0100011001011010
0100011110100000
0100100001010010
0100100100111011
0100101011010110
0100101110110011
0100110000101001
0100110111100011
0100111000101111
0100111110000100
0101000001010011
0101000111010001
0101001000000000
0101001111101101
0101010000100000
0101010111111100
0101011010110001
0101011101011011
0101100001101010
0101100111001011
0101101010111110
0101101100111001
0101110001001010
0101110101001100
0101111001011000
0101111111001111
0110000011010000
0110000111101111
0110001010101010
0110001111111011
0110010001000011
0110010101001101
0110011000110011
0110011110000101
0110100001000101
0110100111111001
0110101000000010
0110101101111111
0110110001010000
0110110100111100
0110111010011111
0110111110101000
0111000001010001
0111000110100011
0111001001000000
0111001110001111
0111010010010010
0111010110011101
0111011000111000
0111011111110101
0111100010111100
0111100110110110
0111101011011010
0111101100100001
0111110000010000
0111110111111111
0111111011110011
0111111111010010
1000000011001101
1000000100001100
1000001000010011
1000001111101100
1000010001011111
1000010110010111
1000011001000100
1000011100010111
1000100011000100
1000100110100111
1000101001111110
1000101100111101
1000110001100100
1000110101011101
1000111000011001
1000111101110011
1001000001100000
1001000110000001
1001001001001111
1001001111011100
1001010000100010
1001010100101010
1001011010010000
1001011110001000
1001100001000110
1001100111101110
1001101010111000
1001101100010100
1001110011011110
1001110101011110
1001111000001011
1001111111011011
1010000011100000
1010000100110010
1010001000111010
1010001100001010
1010010001001001
1010010100000110
1010011000100100
1010011101011100
1010100011000010
1010100111010011
1010101010101100
1010101101100010
1010110010010001
1010110110010101
1010111011100100
1010111101111001
1011000011100111
1011000111001000
1011001000110111
1011001101101101
1011010010001101
1011010111010101
1011011001001110
1011011110101001
1011100001101100
1011100101010110
1011101011110100
1011101111101010
1011110001100101
1011110101111010
1011111010101110
1011111100001000
1100000010111010
1100000101111000
1100001000100101
1100001100101110
1100010000011100
1100010110100110
1100011010110100
1100011111000110
1100100011101000
1100100111011101
1100101001110100
1100101100011111
1100110001001011
1100110110111101
1100111010001011
1100111110001010
1101000001110000
1101000100111110
1101001010110101
1101001101100110
1101010001001000
1101010100000011
1101011011110110
1101011100001110
1101100001100001
1101100100110101
1101101001010111
1101101110111001
1101110010000110
1101110111000001
1101111000011101
1101111110011110
1110000011100001
1110000111111000
1110001010011000
1110001100010001
1110010001101001
1110010111011001
1110011010001110
1110011110010100
1110100010011011
1110100100011110
1110101010000111
1110101111101001
1110110011001110
1110110101010101
1110111000101000
1110111111011111
1111000010001100
1111000110100001
1111001010001001
1111001100001101
1111010010111111
1111010111100110
1111011001000010
1111011101101000
1111100001000001
1111100110011001
1111101000101101
1111101100001111
1111110010110000
1111110101010100
1111111010111011
1111111100010110

ShiftRows

In the ShiftRows step, each row does a cyclic left shift:

  • R1: stays in place
  • R2: moves 1 byte left
  • R3: moves 2 bytes left
  • R4: moves 3 bytes left

AES


MixColumns

In the MixColumns step, each column is multiplied with a fixed 4x4 matrix over the Galois Field GF(2⁸).

This multiplication is not a standard dot product, but it’s a similar idea.

We represent each byte as a polynomial like this:

b7·x^7 + b6·x^6 + b5·x^5 + b4·x^4 + b3·x^3 + b2·x^2 + b1·x + b0

The “addition” during the matrix multiplication is performed using bitwise XOR, and the “multiplication” involves polynomial multiplication followed by a modular reduction using the irreducible polynomial x⁸+x⁴+x³+x+1.

This operation implements diffusion, where each cell depends on all other cells in the same column, making ciphertext hard to analyze.

Note: In the final round, we skip the MixColumn step to make decryption easier and also avoid extra computations.

AES


AddRoundKey

In this step, each byte of the state is matrix combined with the corresponding byte of the round key using the XOR operation.

By the way, there’s no need for a separate InvAddRoundKey step for decryption because applying the second XOR undoes the first one.

AES


Operation Modes

To encrypt data larger than a single block, modes of operation, such as ECB, CBC, CTR, and GCM, are used. These modes determine how to securely apply encryption repeatedly across multiple blocks.

NameAbbrevationDescription
Electronic CodebookECBEncrypts each block independently.
Cipher Block ChainingCBCEach block is mixed with previous ciphertext.
CounterCTREncrypts a counter value and mixes with a block.
Galois/Counter ModeGCMCombines CTR mode with integrity validation.

Here’s a visual example of CBC mode:

AES CBC mode

Padding Schemes

Plaintext isn’t always an exact multiple of AES’s block size, which is 16 bytes. To fill the remaining space, padding schemes are used. The well-known padding schemes are PKCS#7, ANSI X.923, and zero padding.

PKCS#7 adds N bytes of value N to complete a block. It always pads, even when data perfectly fits in 16 bytes, adding an extra full padding block.

ANSI X.923 sets the last padding byte to the length of padding and fills the rest with zeros. It also adds an additional padding block if no padding is needed.

Zero padding simply fills the empty space with zeros, though this can cause issues if the plaintext naturally ends with zeros.

Padding Schemes

Performance

AES was chosen as a standard not just because it performs good encryption, but also because it does it very fast.

Intel and AMD have even implemented direct CPU instructions for AES encryption.

Also, instead of performing polynomial multiplication in the MixColumns step on the fly, the results for all possible byte values can be precalculated and stored in lookup tables. Lookup table access time is usually O(1), which is constant time.

Thank you for reading this article about Advanced Encryption Standard. Have a good day!

You can check out the full AES-256-CBC implementation written by me in the C language in this repository.