Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
duyuefeng0708
GitHub Repository: duyuefeng0708/Cryptography-From-First-Principle
Path: blob/main/foundations/03-galois-fields-aes/connect/aes-in-tls13.ipynb
483 views
unlisted
Kernel: SageMath 10.0

Connect: AES in TLS 1.3

Module 03 | Real-World Connections

Every HTTPS connection you make runs the field arithmetic from Module 03.

Introduction

TLS 1.3 (RFC 8446) is the protocol that secures virtually all web traffic. When your browser shows a padlock icon, TLS is running underneath.

TLS 1.3 mandates exactly five cipher suites. Three of them use AES:

Cipher SuiteEncryptionKey SizeAuth Tag
TLS_AES_128_GCM_SHA256AES-128-GCM128 bits128 bits
TLS_AES_256_GCM_SHA384AES-256-GCM256 bits128 bits
TLS_AES_128_CCM_SHA256AES-128-CCM128 bits128 bits

The AES we built in Module 03 --- with its GF(282^8) S-box, MixColumns matrix, and round structure --- is the engine inside all of these. Let's trace exactly where each Module 03 concept appears.

The TLS 1.3 Handshake (Simplified)

Before any encrypted data flows, client and server perform a handshake:

Client Server | | |--- ClientHello (key share) --------->| | | |<--- ServerHello (key share) ---------| |<--- {EncryptedExtensions} -----------| |<--- {Certificate} -------------------| |<--- {CertificateVerify} -------------| |<--- {Finished} ----------------------| | | |--- {Finished} ---------------------->| | | |<========= Application Data =========>| (AES-GCM encrypted records)

Messages in {} braces are already encrypted with AES-GCM. The handshake:

  1. Key exchange (ECDH or X25519) produces a shared secret

  2. Key derivation (HKDF-SHA256/384) derives AES keys and IVs from the shared secret

  3. Bulk encryption uses AES-GCM to encrypt all subsequent traffic

Step 3 is where Module 03 lives. Let's zoom in.

# === Simulating the TLS 1.3 record layer with our Module 03 AES === # First, rebuild our AES primitives from Module 03 R.<x> = GF(2)[] F.<alpha> = GF(2^8, modulus=x^8 + x^4 + x^3 + x + 1) def byte_to_gf(b): return sum(GF(2)((b >> i) & 1) * alpha^i for i in range(8)) def gf_to_byte(elem): p = elem.polynomial() return sum(int(p[i]) << i for i in range(8)) # Build S-box A_mat = matrix(GF(2), [ [1,0,0,0,1,1,1,1],[1,1,0,0,0,1,1,1],[1,1,1,0,0,0,1,1],[1,1,1,1,0,0,0,1], [1,1,1,1,1,0,0,0],[0,1,1,1,1,1,0,0],[0,0,1,1,1,1,1,0],[0,0,0,1,1,1,1,1] ]) c_vec = vector(GF(2), [(0x63 >> i) & 1 for i in range(8)]) SBOX = [0] * 256 for b in range(256): if b == 0: inv_bits = vector(GF(2), [0]*8) else: inv_byte = gf_to_byte(byte_to_gf(b)^(-1)) inv_bits = vector(GF(2), [(inv_byte >> i) & 1 for i in range(8)]) result_bits = A_mat * inv_bits + c_vec SBOX[b] = sum(int(result_bits[i]) << i for i in range(8)) def xtime(b): result = b << 1 if result & 0x100: result ^^= 0x11B return result & 0xFF def gf256_mul(a, b): result = 0; temp = a for i in range(8): if b & (1 << i): result ^^= temp temp = xtime(temp) return result print('Module 03 AES primitives loaded:') print(f' S-box[0x53] = 0x{SBOX[0x53]:02X}') print(f' gf256_mul(0x57, 0x83) = 0x{gf256_mul(0x57, 0x83):02X}') print(f' Field: GF(2^8) with m(x) = x^8 + x^4 + x^3 + x + 1')

How Module 03 Maps to TLS 1.3

Every AES round applies four operations. All of them are field theory:

SubBytes = GF(282^8) Inversion

Each byte of the AES state is replaced by its multiplicative inverse in GF(282^8), followed by an affine transformation. This is the S-box from notebook 03d.

In a TLS 1.3 session encrypting your HTTP request, SubBytes runs 10 times per block (AES-128 has 10 rounds), processing 16 bytes per round. That's 160 GF(282^8) inversions per 128-bit block of web traffic.

# SubBytes: the S-box in action on a TLS record # Imagine this is the first block of an HTTP GET request after TLS encryption example_block = [0x47, 0x45, 0x54, 0x20, 0x2F, 0x69, 0x6E, 0x64, 0x65, 0x78, 0x2E, 0x68, 0x74, 0x6D, 0x6C, 0x20] # (This would be "GET /index.html " in ASCII, but in TLS it's already XORed with key) print('Example: SubBytes on one block') print(f'Input: {" ".join(f"{b:02X}" for b in example_block)}') sub_result = [SBOX[b] for b in example_block] print(f'Output: {" ".join(f"{b:02X}" for b in sub_result)}') print() # Show the GF(2^8) inversion underneath b = example_block[0] # 0x47 inv_elem = byte_to_gf(b)^(-1) inv_byte = gf_to_byte(inv_elem) print(f'Detailed: byte 0x{b:02X}') print(f' As GF(2^8) element: {byte_to_gf(b)}') print(f' Inverse in GF(2^8): {inv_elem} = 0x{inv_byte:02X}') print(f' After affine map: 0x{SBOX[b]:02X}') print() print(f' In a TLS session: this operation runs 160 times per 128-bit block.')

MixColumns = GF(282^8) Matrix Multiplication

Each 4-byte column of the state is multiplied by a fixed 4×44 \times 4 MDS matrix over GF(282^8). This is the MixColumns operation from notebook 03e.

The matrix entries are {02,03,01,01}\{\texttt{02}, \texttt{03}, \texttt{01}, \texttt{01}\} and their rotations. Multiplication by 02\texttt{02} is the xtime operation, multiplication by 03\texttt{03} is xtime + XOR --- all GF(282^8) arithmetic.

# MixColumns: matrix multiplication over GF(2^8) MC = [[0x02, 0x03, 0x01, 0x01], [0x01, 0x02, 0x03, 0x01], [0x01, 0x01, 0x02, 0x03], [0x03, 0x01, 0x01, 0x02]] def mix_one_column(col): """Apply MixColumns to one 4-byte column.""" result = [0] * 4 for row in range(4): for k in range(4): result[row] ^^= gf256_mul(MC[row][k], col[k]) return result # Show MixColumns on a single column col_in = [0xDB, 0x13, 0x53, 0x45] # FIPS 197 test vector col_out = mix_one_column(col_in) print('MixColumns on one column (FIPS 197 test vector):') print(f' Input: [{" ".join(f"0x{b:02X}" for b in col_in)}]') print(f' Output: [{" ".join(f"0x{b:02X}" for b in col_out)}]') print() # Show the GF(2^8) multiplications in detail for row 0 print('Detail for output byte 0:') terms = [] for k in range(4): prod = gf256_mul(MC[0][k], col_in[k]) terms.append(prod) print(f' 0x{MC[0][k]:02X} * 0x{col_in[k]:02X} = 0x{prod:02X} (GF(2^8) multiplication)') print(f' XOR all: 0x{terms[0]:02X} ^ 0x{terms[1]:02X} ^ 0x{terms[2]:02X} ^ 0x{terms[3]:02X} = 0x{col_out[0]:02X}')

Key Schedule = S-box Again

The AES key schedule expands the 128-bit (or 256-bit) master key into round keys. It uses the S-box (SubWord) and round constants (Rcon), both of which are GF(282^8) operations.

The round constants are successive powers of xx in GF(282^8):

Rcon[i]=xi1modm(x)for i=1,2,\text{Rcon}[i] = x^{i-1} \mod m(x) \quad \text{for } i = 1, 2, \ldots
# AES Key Schedule round constants: powers of x in GF(2^8) print('AES Round Constants (Rcon) = powers of x in GF(2^8):') print() rcon_elem = byte_to_gf(1) # x^0 = 1 x_elem = byte_to_gf(0x02) # x in GF(2^8) for i in range(1, 11): rcon_byte = gf_to_byte(rcon_elem) print(f' Rcon[{i:2d}] = x^{i-1} = {rcon_elem} = 0x{rcon_byte:02X}') rcon_elem = rcon_elem * x_elem # multiply by x in GF(2^8) print() print('Each round constant is a power of x in GF(2^8), reduced mod m(x).') print('When x^7 overflows past degree 7, we reduce modulo x^8+x^4+x^3+x+1.') print() print('The key schedule also applies SubWord (= S-box on each byte of a word),') print('which is GF(2^8) inversion + affine map, the same as SubBytes.')

Where GF(282^8) Appears in a TLS 1.3 Connection

Let's count exactly how many GF(282^8) operations happen when your browser loads a typical web page.

# Counting GF(2^8) operations in a TLS 1.3 session # AES-128: 10 rounds, 16 bytes per block, 128-bit blocks rounds = 10 block_bytes = 16 # Per block: subbytes_per_round = block_bytes # 16 S-box lookups (= GF(2^8) inversions) mixcol_mults_per_round = 4 * 4 * 4 # 4 columns, 4x4 matrix, = 64 GF(2^8) multiplications # Rounds 1-9 have MixColumns; round 10 does not subbytes_per_block = subbytes_per_round * rounds mixcol_per_block = mixcol_mults_per_round * (rounds - 1) # no MixColumns in last round print('=== GF(2^8) Operations Per AES-128 Block ===') print(f' SubBytes: {subbytes_per_block} inversions ({rounds} rounds x {block_bytes} bytes)') print(f' MixColumns: {mixcol_per_block} multiplications ({rounds-1} rounds x {mixcol_mults_per_round} mults)') print(f' Total: {subbytes_per_block + mixcol_per_block} GF(2^8) field operations per block') print() # A typical web page: ~2 MB = 2,000,000 bytes page_bytes = 2_000_000 blocks = page_bytes // block_bytes total_ops = blocks * (subbytes_per_block + mixcol_per_block) print(f'=== Loading a 2 MB Web Page over TLS 1.3 ===') print(f' Blocks: {blocks:,}') print(f' GF(2^8) inversions (SubBytes): {blocks * subbytes_per_block:,}') print(f' GF(2^8) multiplications (MixColumns): {blocks * mixcol_per_block:,}') print(f' Total GF(2^8) operations: {total_ops:,}') print() print(f'Every one of these operations is arithmetic in the field you built in Module 03.')

Concept Map: Module 03 to TLS 1.3

Module 03 ConceptWhere It Appears in TLS 1.3
GF(2) arithmetic (03a)Every XOR in AES = GF(2) addition
GF(282^8) construction (03b)The field underlying all AES byte operations
GF(256) multiplication (03c)MixColumns matrix multiplication
GF(256) inversion (03c-03d)S-box = SubBytes = the core nonlinear step
Affine map over GF(2) (03d)Second half of the S-box construction
MDS matrix over GF(256) (03e)MixColumns diffusion layer
AES round composition (03f)Each TLS record is encrypted block-by-block
Irreducible polynomial (03b)x8+x4+x3+x+1x^8+x^4+x^3+x+1 is hardcoded in every TLS implementation
# Let's run a complete AES-128 encryption to see all the field operations in action # Using FIPS 197 Appendix B test vector def sub_bytes(state): return [[SBOX[state[r][c]] for c in range(4)] for r in range(4)] def shift_rows(state): result = [row[:] for row in state] for i in range(1, 4): result[i] = state[i][i:] + state[i][:i] return result def mix_columns(state): result = [[0]*4 for _ in range(4)] for col in range(4): for row in range(4): for k in range(4): result[row][col] ^^= gf256_mul(MC[row][k], state[k][col]) return result def add_round_key(state, rk): return [[state[r][c] ^^ rk[r][c] for c in range(4)] for r in range(4)] def bytes_to_state(data): state = [[0]*4 for _ in range(4)] for i in range(16): state[i % 4][i // 4] = data[i] return state def state_to_hex(state): return ' '.join(f'{state[i%4][i//4]:02X}' for i in range(16)) # Simplified key schedule (just round 0 and round 1 for demo) key = [0x2B, 0x7E, 0x15, 0x16, 0x28, 0xAE, 0xD2, 0xA6, 0xAB, 0xF7, 0x15, 0x88, 0x09, 0xCF, 0x4F, 0x3C] pt = [0x32, 0x43, 0xF6, 0xA8, 0x88, 0x5A, 0x30, 0x8D, 0x31, 0x31, 0x98, 0xA2, 0xE0, 0x37, 0x07, 0x34] rk0 = bytes_to_state(key) rk1 = bytes_to_state([0xA0, 0xFA, 0xFE, 0x17, 0x88, 0x54, 0x2C, 0xB1, 0x23, 0xA3, 0x39, 0x39, 0x2A, 0x6C, 0x76, 0x05]) state = bytes_to_state(pt) print(f'Plaintext: {state_to_hex(state)}') # Round 0: AddRoundKey only state = add_round_key(state, rk0) print(f'After round 0: {state_to_hex(state)} (AddRoundKey only)') # Round 1: full round state = sub_bytes(state) print(f'After SubBytes: {state_to_hex(state)} (GF(2^8) inversion x16)') state = shift_rows(state) print(f'After ShiftRows: {state_to_hex(state)} (byte permutation)') state = mix_columns(state) print(f'After MixCols: {state_to_hex(state)} (GF(2^8) matrix mult)') state = add_round_key(state, rk1) print(f'After round 1: {state_to_hex(state)} (XOR with round key)') print() print('This is exactly what runs inside TLS 1.3 for every 128-bit block of your web traffic.')

Summary

ConceptKey idea
SubBytes in TLSEvery AES block runs 160 GF(282^8) inversions (10 rounds, 16 bytes each) for the S-box
MixColumns in TLSMatrix multiplication over GF(282^8) with constants 0x01, 0x02, 0x03, running 9 times per block
Key scheduleUses the S-box again, plus round constants that are successive powers of xx in GF(282^8)
AddRoundKeyGF(2) vector addition (XOR), the step that mixes in the secret key
Scale of operationsA 2 MB web page requires millions of GF(282^8) field operations, all happening transparently
GCM authenticationTLS 1.3 wraps AES in GCM mode, adding a second Galois field, GF(21282^{128}), for authentication tags

The field GF(28)=GF(2)[x]/x8+x4+x3+x+1\text{GF}(2^8) = \text{GF}(2)[x] / \langle x^8 + x^4 + x^3 + x + 1 \rangle from Module 03 is not an abstraction. It is the exact algebraic structure that protects your passwords, banking sessions, and private messages every time you open a browser.


Back to Module 03: Galois Fields and AES