Logo
Overview

SEED Correlation Power Analysis

May 20, 2024
8 min read

This post is not an introduction to the SEED block cipher itself. I assume the reader already knows the 16-round Feistel structure, the round F function, the G function, and the key schedule.

The goal here is narrower: given power traces and known plaintexts from an unprotected SEED implementation, recover enough round-key material with Correlation Power Analysis (CPA), then invert the key schedule to recover the master key.

References:

1. Attack Model

I assume the following setting.

  1. The implementation uses a fixed 128-bit SEED key.
  2. The plaintext for each encryption trace is known.
  3. The traces are roughly aligned.
  4. The implementation is not masked or otherwise protected against first-order CPA.

The SEED block is 128 bits. I write the plaintext state as four 32-bit words:

P=L0L1R0R1P = L_0 \parallel L_1 \parallel R_0 \parallel R_1

Each round applies the F function to the right half, then XORs the result into the left half and swaps the Feistel sides.

The trace below was collected from a SEED implementation on ChipWhisperer. The optimized SEED encryption itself is short compared with the dummy operation after it.

Power trace of the SEED implementation

Zooming into the SEED region, the repeated round activity is visible. This is a useful SPA sanity check before starting CPA.

Zoomed SEED round activity

2. SEED Round Structure

The high-level Feistel movement is important because round 2 CPA needs the state after round 1. The same colored blocks in the diagram represent the same state words moving between rounds.

SEED Feistel outline

Inside one round, the right half is split into two 32-bit words. I call them CC and DD in the F function:

C=R0,D=R1C = R_0,\qquad D = R_1

The round key also has two 32-bit words:

Ki=(Ki,0,Ki,1)K_i = (K_{i,0}, K_{i,1})

The F function mixes them with XOR, modular addition, and the nonlinear G function.

SEED F function

The useful CPA leakage points are the G calls. G is a 32-bit input, 32-bit output transformation built from byte S-box substitutions and linear mixing. We do not need to attack the entire 32-bit output at once; the first substituted bytes are enough to build byte-wise leakage hypotheses.

SEED G function

The compact form is:

G(X)=SS3(X3)SS2(X2)SS1(X1)SS0(X0)G(X) = SS_3(X_3) \oplus SS_2(X_2) \oplus SS_1(X_1) \oplus SS_0(X_0)

Compact G function formula

For CPA, the important part is the first nonlinear S-box layer. If a byte of the G input is known up to one guessed key byte, we can predict the S-box output, convert it to Hamming weight, and correlate it with the measured traces.

3. Why Four CPA Passes Are Needed

One SEED round key has two 32-bit words, Ki,0K_{i,0} and Ki,1K_{i,1}. The first G input in the F function is based on:

(CKi,0)(DKi,1)(C \oplus K_{i,0}) \oplus (D \oplus K_{i,1})

This simplifies to:

CDKi,0Ki,1C \oplus D \oplus K_{i,0} \oplus K_{i,1}

Therefore, the first CPA of a round does not recover Ki,0K_{i,0} or Ki,1K_{i,1} directly. It recovers their XOR:

Ki=Ki,0Ki,1K_i^{\oplus} = K_{i,0} \oplus K_{i,1}

After that, a second CPA target uses the next addition and G call to recover Ki,0K_{i,0}. Once Ki,0K_{i,0} is known, the other half is immediate:

Ki,1=KiKi,0K_{i,1} = K_i^{\oplus} \oplus K_{i,0}

To recover the first two round keys, I repeat this twice:

  1. Round 1 CPA for K1,0K1,1K_{1,0} \oplus K_{1,1}.
  2. Round 1 CPA for K1,0K_{1,0}.
  3. Round 2 CPA for K2,0K2,1K_{2,0} \oplus K_{2,1}, after computing the round 1 state.
  4. Round 2 CPA for K2,0K_{2,0}.

Each target is a 32-bit word, but the CPA is done one byte at a time. A direct 32-bit guess would require 2322^{32} hypotheses. Four independent byte CPA runs require only:

428=2104 \cdot 2^8 = 2^{10}

key-byte hypotheses.

4. Round 1 CPA: Recovering K1,0K1,1K_{1,0} \oplus K_{1,1}

For the first round, CC and DD are known from the plaintext:

C=R0,D=R1C = R_0,\qquad D = R_1

The first target is:

G(CDk)G(C \oplus D \oplus k)

where kk is a byte guess for K1,0K1,1K_{1,0} \oplus K_{1,1}.

Round 1 first CPA byte target

The diagram below shows the first G call in the round 1 F function. That is the attack point for the XOR-combined key.

Round 1 first attack point

For byte position bb, plaintext trace jj, and key-byte guess kk, the hypothetical leakage is:

Hj(b)(k)=HW(Sb(R0,j(b)R1,j(b)k))H_j^{(b)}(k) = \text{HW}\left(S_b\left(R_{0,j}^{(b)} \oplus R_{1,j}^{(b)} \oplus k\right)\right)

The S-box index SbS_b depends on the byte position and the implementation’s SEED S-box ordering.

The code shape is the same as the AES CPA post: build a hypothetical leakage vector for each key guess and correlate it against each sample point.

for (int byte_pos = 0; byte_pos < 4; byte_pos++) {
for (int key = 0; key < 256; key++) {
for (int trace_idx = 0; trace_idx < trace_count; trace_idx++) {
uint8_t iv = PT[trace_idx][byte_pos + 8]
^ PT[trace_idx][byte_pos + 12]
^ key;
if (byte_pos % 2 == 0) {
iv = S[1][iv];
} else {
iv = S[0][iv];
}
predicted_hw[trace_idx] = hamming_weight(iv);
}
correlate_against_trace_window(byte_pos, key, predicted_hw, traces);
}
}

The correlation peaks are clear.

First CPA correlation trace

The recovered 32-bit value is:

K1,0K1,1=0xBBB82E52K_{1,0} \oplus K_{1,1} = \texttt{0xBBB82E52}

First CPA result

This matches the known round-key values:

0x7C8F8C7E0xC737A22C=0xBBB82E52\texttt{0x7C8F8C7E} \oplus \texttt{0xC737A22C} = \texttt{0xBBB82E52}

5. Round 1 CPA: Recovering K1,0K_{1,0}

The second target is the next G call in the same F function. At this point we already know:

K1=K1,0K1,1K_1^{\oplus} = K_{1,0} \oplus K_{1,1}

So the first G output can be computed for every trace. The next input is based on:

(CK1,0)+G(CDK1)(mod232)(C \oplus K_{1,0}) + G(C \oplus D \oplus K_1^{\oplus}) \pmod{2^{32}}

This gives a leakage model that depends on K1,0K_{1,0}.

Round 1 second attack point

There is one complication: this is a 32-bit modular addition, so carry propagation matters. I recover the bytes from least significant to most significant so that the carry into the next byte can be modeled.

Carry propagation in the second CPA target

For a byte-wise CPA, the model is:

Vj(k)=(CjK1,0(k))+G(CjDjK1)(mod232)V_j(k) = \left(C_j \oplus K_{1,0}(k)\right) + G(C_j \oplus D_j \oplus K_1^{\oplus}) \pmod{2^{32}}

Then the leakage target is a byte of:

G(Vj(k))G(V_j(k))

The implementation model looks like this in C-style pseudocode.

for (int byte_pos = 3; byte_pos >= 0; byte_pos--) {
for (int key = 0; key < 256; key++) {
for (int trace_idx = 0; trace_idx < trace_count; trace_idx++) {
uint32_t cd_xor = pack_be32(&PT[trace_idx][8])
^ pack_be32(&PT[trace_idx][12]);
uint32_t first_g = G_Function(cd_xor ^ known_kxor);
uint8_t g_byte = select_byte(first_g, byte_pos);
uint8_t c_byte = PT[trace_idx][byte_pos + 8] ^ key;
uint8_t iv = add_with_known_carry(c_byte, g_byte, byte_pos);
if (byte_pos % 2 == 0) {
iv = S[1][iv];
} else {
iv = S[0][iv];
}
predicted_hw[trace_idx] = hamming_weight(iv);
}
correlate_against_trace_window(byte_pos, key, predicted_hw, traces);
}
}

The correlations are lower than the first CPA, but still clearly distinguishable from noise.

Second CPA correlation trace

The recovered left round key is:

K1,0=0x7C8F8C7EK_{1,0} = \texttt{0x7C8F8C7E}

Second CPA result

Then the right round key is:

K1,1=0xBBB82E520x7C8F8C7E=0xC737A22CK_{1,1} = \texttt{0xBBB82E52} \oplus \texttt{0x7C8F8C7E} = \texttt{0xC737A22C}

6. Round 2 CPA

For round 2, the plaintext alone is no longer enough. We first compute the round 1 output state using the recovered round 1 keys. That gives the round 2 input words CC and DD for every trace.

Then the same two-step attack applies.

The first round 2 target is:

G(CDk)G(C \oplus D \oplus k)

where CC and DD are now the round 2 right-half words.

Round 2 first attack point

The correlation trace is again strong because this target is the first G call of the round.

Third CPA correlation trace

The recovered XOR-combined key is:

K2,0K2,1=0x58ED0491K_{2,0} \oplus K_{2,1} = \texttt{0x58ED0491}

Third CPA result

This matches:

0xFF276CDB0xA7CA684A=0x58ED0491\texttt{0xFF276CDB} \oplus \texttt{0xA7CA684A} = \texttt{0x58ED0491}

The second round 2 target recovers K2,0K_{2,0} from the next addition and G call, just like round 1.

Round 2 left-key attack point

The recovered left round key is:

K2,0=0xFF276CDBK_{2,0} = \texttt{0xFF276CDB}

Fourth CPA result

Then:

K2,1=0x58ED04910xFF276CDB=0xA7CA684AK_{2,1} = \texttt{0x58ED0491} \oplus \texttt{0xFF276CDB} = \texttt{0xA7CA684A}

7. Interpreting the Correlation Traces

The four CPA traces overlaid on the SEED power trace show where each target leaks.

Four CPA targets overlaid on the SEED trace

The combined-key CPA targets, Ki,0Ki,1K_{i,0} \oplus K_{i,1}, produce stronger correlations around 0.8. The left-key CPA targets are weaker, around 0.5, because the model passes through modular addition and carry handling before the next S-box.

That difference is expected. A lower correlation is not a failure if the correct key still produces a clear peak above the wrong guesses.

At this point the recovered round-key material is:

RoundKi,0K_{i,0}Ki,1K_{i,1}
10x7C8F8C7E0xC737A22C
20xFF276CDB0xA7CA684A

8. Recovering the Master Key

The remaining step is to invert the SEED key schedule. SEED splits the 128-bit master key into four 32-bit words:

M=ABCDM = A \parallel B \parallel C \parallel D

The first two round-key equations are:

K1,0=G(A+CKC0)K_{1,0} = G(A + C - KC_0) K1,1=G(BD+KC0)K_{1,1} = G(B - D + KC_0)

For round 2, the key schedule rotates ABA \parallel B by one byte while CC and DD remain in place:

AB=(AB)8A' \parallel B' = (A \parallel B) \gg 8

The next equations are:

K2,0=G(A+CKC1)K_{2,0} = G(A' + C - KC_1) K2,1=G(BD+KC1)K_{2,1} = G(B' - D + KC_1)

SEED key schedule outline

By applying G1G^{-1} and correcting the round constants, we get:

ValueK1,0K_{1,0}K1,1K_{1,1}K2,0K_{2,0}K2,1K_{2,1}
Round key7C8F8C7EC737A22CFF276CDBA7CA684A
G1G^{-1} output69D29255962F71B1D29A179A3365EA6A
After constant correction080A0C0EF7F7F7F80F090B0DF6F6F6F7
MeaningA+CA + CBDB - DA+CA' + CBDB' - D

Because ABA' \parallel B' is only a one-byte rotation of ABA \parallel B, the system is almost fixed by the first two rounds. In this example, guessing the single byte that crosses the A/BA/B boundary is enough to reconstruct all four master-key words.

The recovered master key is:

00010203 04050607 08090A0B 0C0D0E0F

The important point is that CPA did not directly recover the master key. It recovered early round-key constraints. The SEED key schedule then turned those constraints back into the 128-bit master key.