aes-128-scratch
The implementation is in ~/final_aes128.py. An AES-128 block cipher from scratch with:
Core primitives: - SubBytes – Byte-wise substitution via the AES S-box (a 256-byte lookup table) - ShiftRows – Circular left shift of rows (row 0: 0, row 1: 1, row 2: 2, row 3: 3) - MixColumns – Column-wise matrix multiplication in GF(2⁸) using the polynomial x⁸ + x⁴ + x³ + x + 1 - AddRoundKey – XOR state with 4 words of the expanded key - Key expansion – 44 words from 4 initial key words using RotWord, SubWord, and Rcon
Key design decisions:
- State is stored column-major (4×4), matching FIPS 197 convention
- GF(2⁸) multiplication uses xtime (multiply by x) and a bit-loop for general multiplication
- encrypt_block / decrypt_block accept both bytes and list[int] for flexibility
Example usage:
from final_aes128 import encrypt_block, decrypt_block
key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")
pt = bytes.fromhex("6bc1bee22e409f96e93d7e117393172a")
ct = encrypt_block(pt, key) # -> list of 16 ints
dec = decrypt_block(ct, key) # -> back to original plaintext
Verified against multiple sources: | Test | Source | Status | |------|--------|--------| | FIPS 197 full example | FIPS 197 Table 7/8 | ✅ `3925841d02dc09fbdc118597196a0b32` | | `key=2b7e...` / `pt=6bc1...` | NIST AES Core | ✅ `3ad77bb40d7a3660a89ecaf32466ef97` | | `key=0001...` / `pt=0011...` | NIST AESAVS | ✅ `69c4e0d86a7b0430d8cdb78070b4c55a` | | `key=000...0` / `pt=000...0` | NIST AESAVS | ✅ `66e94bd4ef8a2c3b884cfa59ca342b2e` | | `key=fff...f` / `pt=fff...f` | OpenSSL `openssl enc -aes-128-ecb` | ✅ `bcbf217cb280cf30b2517052193ab979` | | Random round-trip (100 blocks) | Encrypt then decrypt | ✅ 100/100 pass | | Single-bit avalanche | 1 bit change → all 16 bytes differ | ✅ Strong diffusion | ---
{"model": "claude-sonnet-4-20250514", "problem_class": "aes-128-scratch", "result": "passed", "tests": 6}The implementation is in ~/final_aes128.py. An AES-128 block cipher from scratch with:
Core primitives: - SubBytes – Byte-wise substitution via the AES S-box (a 256-byte lookup table) - ShiftRows – Circular left shift of rows (row 0: 0, row 1: 1, row 2: 2, row 3: 3) - MixColumns – Column-wise matrix multiplication in GF(2⁸) using the polynomial x⁸ + x⁴ + x³ + x + 1 - AddRoundKey – XOR state with 4 words of the expanded key - Key expansion – 44 words from 4 initial key words using RotWord, SubWord, and Rcon
Key design decisions:
- State is stored column-major (4×4), matching FIPS 197 convention
- GF(2⁸) multiplication uses xtime (multiply by x) and a bit-loop for general multiplication
- encrypt_block / decrypt_block accept both bytes and list[int] for flexibility
Example usage:
from final_aes128 import encrypt_block, decrypt_block
key = bytes.fromhex("2b7e151628aed2a6abf7158809cf4f3c")
pt = bytes.fromhex("6bc1bee22e409f96e93d7e117393172a")
ct = encrypt_block(pt, key) # -> list of 16 ints
dec = decrypt_block(ct, key) # -> back to original plaintext
Verified against multiple sources: | Test | Source | Status | |------|--------|--------| | FIPS 197 full example | FIPS 197 Table 7/8 | ✅ `3925841d02dc09fbdc118597196a0b32` | | `key=2b7e...` / `pt=6bc1...` | NIST AES Core | ✅ `3ad77bb40d7a3660a89ecaf32466ef97` | | `key=0001...` / `pt=0011...` | NIST AESAVS | ✅ `69c4e0d86a7b0430d8cdb78070b4c55a` | | `key=000...0` / `pt=000...0` | NIST AESAVS | ✅ `66e94bd4ef8a2c3b884cfa59ca342b2e` | | `key=fff...f` / `pt=fff...f` | OpenSSL `openssl enc -aes-128-ecb` | ✅ `bcbf217cb280cf30b2517052193ab979` | | Random round-trip (100 blocks) | Encrypt then decrypt | ✅ 100/100 pass | | Single-bit avalanche | 1 bit change → all 16 bytes differ | ✅ Strong diffusion | ---
{"model": "claude-sonnet-4-20250514", "problem_class": "aes-128-scratch", "result": "passed", "tests": 6}