Implementation of CLEFIA S-box Using Normal Basis — Inspired by A Very Compact Rijndael S-box
A few years ago, I explored one of my favorite cryptographic building blocks: the substitution box (S-box). Back then, I came across the elegant paper A Very Compact Rijndael S-box, which showed how to eliminate lookup tables by leveraging normal basis representation and logical operations in $F_{2^{8}}$. That approach deeply resonated with me—compact, efficient, and mathematically sound.
Recently, I decided to revisit that work—but this time with a new challenge: re-implementing the CLEFIA S-box using the same principles. CLEFIA, Sony’s lightweight block cipher, uses two $8 \times 8$ S-boxes ($S_0$ and $S_1$), and I focused on reconstructing S₁ using field inversion and affine transformations—all without tables.
This blog walks you through that journey—from understanding the S-box structure in CLEFIA, finding a suitable normal basis, and implementing the logic step by step.
🧩 CLEFIA S-box1 (S₁) Formula
The S₁ function in CLEFIA is defined as follows:
$$ S_1(x) = \begin{cases} g(f(x)^{-1}) & \text{if } f(x) \ne 0 \ g(0) & \text{if } f(x) = 0 \end{cases} $$
Where:
- $f(x)$ is an affine transformation in $F_{2^{8}}$
- Inversion is performed in $F_{2^{8}}$ defined by the irreducible polynomial:
$$ z^8 + z^4 + z^3 + z^2 + 1 $$
- $g(x)$ is another affine transformation over $F_{2^{8}}$
🔢 Affine Transformation Matrices
The affine transformations $f(x)$ and $g(x)$ can be represented as matrix operations over GF(2):
$$ f(x) = A_f \cdot x + b_f $$
$$ g(x) = A_g \cdot x + b_g $$
Where:
- $A_f$ and $A_g$ are $8 \times 8$ binary matrices.
- $b_f$ and $b_g$ are 8-bit binary vectors.
- $x$ is the 8-bit input vector.
For example, the matrix $A_f$ and vector $b_f$ might be defined as:
$$ A_f = \begin{bmatrix} 1 & 0 & 0 & 0 & 1 & 1 & 0 & 1 \\ 1 & 1 & 0 & 0 & 1 & 0 & 0 & 1 \\ 0 & 1 & 1 & 1 & 0 & 0 & 0 & 1 \\ 0 & 0 & 0 & 0 & 1 & 1 & 0 & 1 \\ 0 & 0 & 1 & 0 & 0 & 0 & 1 & 0 \\ 1 & 0 & 0 & 0 & 1 & 0 & 1 & 1 \\ 0 & 1 & 1 & 1 & 0 & 0 & 0 & 0 \\ 1 & 1 & 0 & 1 & 0 & 1 & 1 & 0 \end{bmatrix} ,b_f = \begin{bmatrix} 1 \\ 1 \\ 0 \\ 0 \\ 0 \\ 1 \\ 1 \\ 0 \end{bmatrix} $$
Similarly, define $A_g$ and $b_g$ as per the CLEFIA specification.
🔬 From Table to Logic: Why Normal Basis?
In normal basis:
- Squaring becomes a cyclic shift — perfect for efficient hardware or bit-sliced software
- Multiplication and inversion can be expressed as boolean formulas
- No lookup tables → smaller memory, better side-channel resistance
⚙️ SAGE/C Implementation Steps
I used the Normal-Basis project as a foundation and modified it to support CLEFIA’s field and transformations.
1. Define GF(2⁸) With CLEFIA Polynomial
1. Defining the Playground: Finite-Field Setup
The file const.sage establishes the mathematical landscape. It lists all 36 irreducible polynomials of degree 8 over GF(2), along with their exponent representations (IR_power) to ease polynomial evaluation. It also encodes affine transformation matrices for both AES and CLEFIA, which will be applied after inversion in the S-box pipeline.
IR = [
x^8 + x^4 + x^3 + x + 1,
x^8 + x^4 + x^3 + x^2 + 1,
...
x^8 + x^7 + x^6 + x^5 + x^4 + x^3 + 1
]
AF_AES = [
[1,1,1,1,1,0,0,0],
[0,1,1,1,1,1,0,0],
...
[1,1,1,1,0,0,0,1]
]
AF_CLEFIA = [
[0,0,0,0,1,0,1,0],
[0,1,0,0,0,0,0,1],
...
[0,1,0,0,0,1,0,0]
]
2. Generating Change-of-Basis Matrices with Sage
NBG.sage is the computational engine responsible for generating the matrices required to switch between different bases in GF(2⁸) operations.
It plays a key role in preparing data for efficient normal-basis arithmetic in S-box implementations.
This script:
- Imports constant definitions from
const.sage - Constructs log/antilog tables for GF(2⁸)
- Derives normal bases
- Produces change-of-basis matrices between:
- Affine basis
- Polynomial basis
- Normal basis
Key Steps
- Sort primitive elements for each irreducible polynomial.
- Build log/antilog tables across subfields for efficient multiplication and inversion.
- Produce conversion arrays:
X2A→ Normal basis → Affine basisA2X→ Affine basis → Normal basisX2S→ Normal basis → S-box basisS2X→ S-box basis → Normal basis
Function: matrix_generation
The matrix_generation function creates and outputs the transformation matrices needed for basis conversions.
def matrix_generation(X):
XB = hex_to_bin(X)
X2A = concatenate_horizontal(XB)
printout("X2A", X2A)
M = matrix(GF(2), XB)
X_1 = M.inverse()
A2X = concatenate_horizontal(X_1)
printout("A2X", A2X)
CM = matrix(GF(2), AF_CLEFIA)
MX = M * CM.transpose()
X2S = concatenate_horizontal(MX)
printout("X2S", X2S)
MX_1 = MX.inverse()
S2X = concatenate_horizontal(MX_1)
printout("S2X", S2X)
The calculate_basis function orchestrates table generation and final matrix output:
def calculate_basis(Root_IR, Y_1, Y_16, Z_1, Z_4, W_1, W_2):
F.<a> = GF(2^8, modulus=IR[Root_IR])
LT = generate_logtable(Root_IR)
ALT = generate_antilogtable(LT)
CM = change_matrix(Y_1, Y_16, Z_1, Z_4, W_1, W_2)
...
matrix_generation(CMH)
wzy_N_V(Y_1, Y_16, Z_1, Z_4, W_1, W_2, LT, ALT)
3. Precomputed Matrices for C Implementations
The generated matrices are stored in matrix.h and selected via a compile-time OPTION macro. Multiple configurations are commented, enabling experimentation with different normal bases.
#define OPTION 6
static int X2A[8] = { 0xb4, 0xae, 0xf6, 0xfe, 0xbe, 0xeb, 0x2b, 0x6d };
static int A2X[8] = { 0x1e, 0x18, 0x75, 0x48, 0x30, 0xa3, 0xb8, 0xff };
static int X2S[8] = { 0x31, 0x3f, 0xd4, 0x74, 0x15, 0x1f, 0x7c, 0x98 };
static int S2X[8] = { 0x74, 0xff, 0x44, 0x67, 0x12, 0xcc, 0x1e, 0xa3 };
4. C Implementations of Normal-Basis S-Boxes
Two C programs illustrate matrix and arithmetic usage: Sbox_NB_new.c — CLEFIA support using an extra transformation matrix Clefia_transform. sbox_NB_NW.c — AES-style S-box using normal-basis operations without the extra transformation.