◐ Off-By-One · answer catalog

python-aes-sbox-gf28-inverse

1 answer(s)pythonpython3

python-aes-sbox-gf28-inverse

📦 Source in repository (JSON)

Answer

The S-box is S[x] = affine(ginv(x)). Two classic bugs are addressed explicitly:

  1. Inverse of 0x00 — must be 0x00 (0 has no inverse; AES defines 0 → 0), never a division-by-zero or None.
  2. Reduction overflow past degree 7 — when a byte's high bit (0x80) is set, a << 1 creates a degree-8 term. Reduce by XORing with 0x1B (x⁸ ≡ x⁴+x³+x+1 mod 0x11B) — and check the bit before shifting, or the reduction fires too late.
REDUC = 0x11B  # x^8 + x^4 + x^3 + x + 1

def gmul(a: int, b: int) -> int:
    """GF(2^8) multiply, reducing mod 0x11B."""
    p = 0
    while b:
        if b & 1:
            p ^= a
        b >>= 1
        hi = a & 0x80                 # degree-8 overflow about to happen
        a = (a << 1) & 0xFF
        if hi:
            a ^= 0x1B                 # x^8 == 0x1B (mod 0x11B)
    return p

def gpow(a: int, e: int) -> int:
    r, base = 1, a
    while e:
        if e & 1:
            r = gmul(r, base)
        base = gmul(base, base)
        e >>= 1
    return r

def ginv(a: int) -> int:
    """Multiplicative inverse; Fermat: a^254. 0x00 -> 0x00 (not div-by-zero)."""
    if a == 0:
        return 0
    return gpow(a, 254)   # a^(2^8 - 2)

def _rotl8(x: int, n: int) -> int:
    return ((x << n) | (x >> (8 - n))) & 0xFF

def affine(x: int) -> int:
    """b'_i = b_i ^ b_(i+4) ^ b_(i+5) ^ b_(i+6) ^ b_(i+7) ^ c_i, c = 0x63."""
    return (x ^ _rotl8(x, 1) ^ _rotl8(x, 2) ^ _rotl8(x, 3) ^ _rotl8(x, 4) ^ 0x63) & 0xFF

def sbox() -> list[int]:
    return [affine(ginv(b)) for b in range(256)]

The affine rotation trick: b' = b ^ rotl(b,1) ^ rotl(b,2) ^ rotl(b,3) ^ rotl(b,4) ^ 0x63, which implements the 8×8 circulant bit-matrix plus constant.

Evidence & signatures

Ran `/tmp/aes_sbox.py` under Python 3.14 (pure Python, 3.11-compatible) — **523/523 tests passed**:

| Check | Result |
|---|---|
| `ginv(0x00) == 0x00` (edge case) | ✓ |
| `S[0x00] == 0x63` (boundary) | ✓ |
| `S[0xFF] == 0x16` (boundary) | ✓ |
| Overflow: `gmul(0x7F, 2) == 0xFE`, `gmul(0x80, 2) == 0x1B`, `gmul(0xFF, 2) == 0xE5` | ✓ |
| Order of x mod 0x11B = 51, `2^255 == 1` (reduction stays in field) | ✓ |
| `gmul` matches an independent reference (reduce-after-shift) on all 65,536 pairs | ✓ |
| `x * ginv(x) == 1` for all 255 nonzero bytes; `ginv` is an involution | ✓ |
| S-box is a bijection of 0..255 | ✓ |
| **Full 256-entry match vs. published FIPS-197 table** | ✓ |

Rows 0 and F: `63 7c 77 7b f2 6b 6f c5 30 01 67 2b fe d7 ab 76` and `8c a1 89 0d bf e6 42 68 41 99 2d 0f b0 54 bb 16` — matching the standard.

One notable finding during testing: `0x11B` is **irreducible but not primitive** — the class of `x` has order 51, not 255. A naive "x is a generator" sanity test fails even with correct code, so verification was done via the inverse property, the independent reduction cross-check, and the full published table instead.
{"model": "deepseek-v4-flash", "problem_class": "python-aes-sbox-gf28-inverse", "result": "passed", "tests": 523}
Generated from the verified corpus · MIT licensedBack to the catalog