Description
A simulated power analysis attack against AES. You are given power traces captured during AES encryption. Apply Hamming-weight correlation analysis on the SubBytes step to recover a single byte of the AES key.
Download and unzip the challenge files. Inside you get a NumPy .npy array of power traces plus the matching plaintext bytes.
Install NumPy. SciPy is optional for this warmup since np.corrcoef is enough.
wget https://artifacts.picoctf.net/c/500/PowerAnalysis_Warmup.zip && unzip PowerAnalysis_Warmup.zippip3 install numpySolution
Walk me through it- Step 1Understand the attack modelPower consumption tracks the Hamming weight of intermediate values. Target SBox[plaintext XOR key] for one key byte and the right guess shows the strongest correlation against the traces.
Learn more
AES encryption begins with
AddRoundKey(XOR plaintext with key) thenSubBytes(S-box lookup). The first-round intermediate the attack targets isv = SBox[p[i] XOR k[i]], where p is the known plaintext byte and k is the unknown key byte. CMOS power consumption rises roughly linearly with the number of 1-bits set on the data bus, i.e. the Hamming weight HW(v).The attack hinges on Pearson correlation between the predicted Hamming weight (one number per trace, for a fixed key guess) and the actual trace samples (one number per trace, per time index):
Toy Pearson sketch (5 traces, ignore time axis): plaintext bytes p: [0x12, 0xFE, 0x33, 0xA0, 0x77] WRONG guess k = 0x42 -> SBox[p^k] -> v_wrong, HW(v_wrong) random RIGHT guess k = 0x2B -> SBox[p^k] -> v_right, HW(v_right) tracks the real leakage Measured trace at the leak-time sample t*: power[t*] = [3.4, 5.1, 2.0, 6.7, 4.8] Compute corr(HW_guess, power[t*]) for every guess in 0..255. Wrong guesses: |corr| ~ 0.05 (noise-level, no relationship) Correct guess: |corr| ~ 0.6+ (linear relationship dominates) argmax over guesses -> recovers key byte.Pearson is invariant to scale and offset, which is exactly what we want: real power is in millivolts and predicted HW is a small integer, but only the linear relationship matters. Gaussian zero-mean noise weakens the signal slowly: more traces drive sample correlation toward the population value.
- Step 2Load traces and build the prediction matrixBuild a 256 x N matrix: rows are key guesses 0..255, columns are traces. Cell [k][i] is the Hamming weight of SBox[plaintext[i] XOR k]. Do not transpose this orientation later.python
python3 cpa_attack.pyLearn more
The AES S-Box is a fixed 256-byte lookup table. Hardcode it once at the top of the script:
SBOX = [ 0x63,0x7c,0x77,0x7b,0xf2,0x6b,0x6f,0xc5,0x30,0x01,0x67,0x2b,0xfe,0xd7,0xab,0x76, 0xca,0x82,0xc9,0x7d,0xfa,0x59,0x47,0xf0,0xad,0xd4,0xa2,0xaf,0x9c,0xa4,0x72,0xc0, 0xb7,0xfd,0x93,0x26,0x36,0x3f,0xf7,0xcc,0x34,0xa5,0xe5,0xf1,0x71,0xd8,0x31,0x15, 0x04,0xc7,0x23,0xc3,0x18,0x96,0x05,0x9a,0x07,0x12,0x80,0xe2,0xeb,0x27,0xb2,0x75, 0x09,0x83,0x2c,0x1a,0x1b,0x6e,0x5a,0xa0,0x52,0x3b,0xd6,0xb3,0x29,0xe3,0x2f,0x84, 0x53,0xd1,0x00,0xed,0x20,0xfc,0xb1,0x5b,0x6a,0xcb,0xbe,0x39,0x4a,0x4c,0x58,0xcf, 0xd0,0xef,0xaa,0xfb,0x43,0x4d,0x33,0x85,0x45,0xf9,0x02,0x7f,0x50,0x3c,0x9f,0xa8, 0x51,0xa3,0x40,0x8f,0x92,0x9d,0x38,0xf5,0xbc,0xb6,0xda,0x21,0x10,0xff,0xf3,0xd2, 0xcd,0x0c,0x13,0xec,0x5f,0x97,0x44,0x17,0xc4,0xa7,0x7e,0x3d,0x64,0x5d,0x19,0x73, 0x60,0x81,0x4f,0xdc,0x22,0x2a,0x90,0x88,0x46,0xee,0xb8,0x14,0xde,0x5e,0x0b,0xdb, 0xe0,0x32,0x3a,0x0a,0x49,0x06,0x24,0x5c,0xc2,0xd3,0xac,0x62,0x91,0x95,0xe4,0x79, 0xe7,0xc8,0x37,0x6d,0x8d,0xd5,0x4e,0xa9,0x6c,0x56,0xf4,0xea,0x65,0x7a,0xae,0x08, 0xba,0x78,0x25,0x2e,0x1c,0xa6,0xb4,0xc6,0xe8,0xdd,0x74,0x1f,0x4b,0xbd,0x8b,0x8a, 0x70,0x3e,0xb5,0x66,0x48,0x03,0xf6,0x0e,0x61,0x35,0x57,0xb9,0x86,0xc1,0x1d,0x9e, 0xe1,0xf8,0x98,0x11,0x69,0xd9,0x8e,0x94,0x9b,0x1e,0x87,0xe9,0xce,0x55,0x28,0xdf, 0x8c,0xa1,0x89,0x0d,0xbf,0xe6,0x42,0x68,0x41,0x99,0x2d,0x0f,0xb0,0x54,0xbb,0x16, ]For each candidate byte
k(0 to 255) and each tracei, computepredictions[k, i] = popcount(SBOX[plaintext[i] ^ k]). Final shape is (256, N): 256 rows (one per key guess), N columns (one per trace). Keep this orientation. Many CPA bugs come from accidentally transposing the matrix.See the canonical S-Box reference in the Rijndael S-box article if you want to double-check the table.
- Step 3Correlate predictions against the trace samplesFor each key guess, compute the Pearson correlation between its prediction row and every time sample of the traces. The guess with the largest |corr| anywhere wins.python
python3 -c " import numpy as np # predictions shape: (256, N), traces shape: (N, T) corrs = np.array([ np.corrcoef(predictions[k], traces.T)[0, 1:] for k in range(256) ]) # shape (256, T) best_k = int(np.argmax(np.max(np.abs(corrs), axis=1))) poi_sample = int(np.argmax(np.abs(corrs[best_k]))) peak = float(np.abs(corrs[best_k, poi_sample])) print(f'Key byte: {best_k:#04x}, POI sample: {poi_sample}, |corr|: {peak:.3f}') assert peak > 0.3, 'No clear peak. Attack did not converge - check trace alignment, plaintext mapping, or S-box.' "Learn more
Unpacking
np.corrcoef(predictions[k], traces.T)[0, 1:]:traces.Thas shape (T, N) so each row is one time sample across all traces.np.corrcoefstacks the prediction row on top of the T sample rows, builds a (T+1, T+1) correlation matrix, and we slice[0, 1:]to read the prediction's correlation against each of the T sample rows. The result is a length-T vector of correlations per key guess.poi_sample = np.argmax(np.abs(corrs[best_k]))recovers the point of interest: the time index where the SBox computation actually leaks. Confirming a real POI (rather than a flat noise floor) is the sanity check that the model fits.Noise sentinel: if no key guess produces a peak with
|corr| > 0.3the attack has not converged. Common causes are a wrong S-box, traces and plaintexts in different orders, misaligned traces (jitter), or simply too few traces for the SNR. The warmup is clean enough that a few hundred traces show peaks well above 0.5 for the right guess. - Step 4Submit the recovered key byteFormat the recovered byte as the challenge expects (hex, decimal, or wrapped) and submit.
Learn more
Power analysis attacks were first published by Paul Kocher in 1999 and remain a serious threat to hardware implementations of cryptography today. Smart cards, HSMs, and embedded cryptographic modules must employ countermeasures like randomized masking (adding a random value to intermediates to break the Hamming-weight correlation) and power supply filtering to resist CPA attacks.
This warmup covers the essential CPA workflow (prediction model, correlation, key recovery) which scales directly to recovering full AES keys in the harder Power Analysis challenges by repeating the attack per key byte.
Flag
picoCTF{...}
This challenge was not solved during the competition. Follow the steps above to reproduce the solution.