Initial commit

This commit is contained in:
hal8174 2025-07-05 20:28:12 +02:00
commit de7b7536ce
Signed by: hal8174
SSH key fingerprint: SHA256:JwuqS+eVfISfKr+DkDQ6NWAbGd1jFAHkPpCM1yCnlTs
8 changed files with 12532 additions and 0 deletions

1
.gitignore vendored Normal file
View file

@ -0,0 +1 @@
/target

145
Cargo.lock generated Normal file
View file

@ -0,0 +1,145 @@
# This file is automatically @generated by Cargo.
# It is not intended for manual editing.
version = 4
[[package]]
name = "aes"
version = "0.8.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b169f7a6d4742236a0a00c541b845991d0ac43e546831af1249753ab4c3aa3a0"
dependencies = [
"cfg-if",
"cipher",
"cpufeatures",
]
[[package]]
name = "alan-side-channel"
version = "0.1.0"
dependencies = [
"aes",
"rayon",
]
[[package]]
name = "cfg-if"
version = "1.0.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9555578bc9e57714c812a1f84e4fc5b4d21fcb063490c624de019f7464c91268"
[[package]]
name = "cipher"
version = "0.4.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "773f3b9af64447d2ce9850330c473515014aa235e6a783b02db81ff39e4a3dad"
dependencies = [
"crypto-common",
"inout",
]
[[package]]
name = "cpufeatures"
version = "0.2.17"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "59ed5838eebb26a2bb2e58f6d5b5316989ae9d08bab10e0e6d103e656d1b0280"
dependencies = [
"libc",
]
[[package]]
name = "crossbeam-deque"
version = "0.8.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9dd111b7b7f7d55b72c0a6ae361660ee5853c9af73f70c3c2ef6858b950e2e51"
dependencies = [
"crossbeam-epoch",
"crossbeam-utils",
]
[[package]]
name = "crossbeam-epoch"
version = "0.9.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5b82ac4a3c2ca9c3460964f020e1402edd5753411d7737aa39c3714ad1b5420e"
dependencies = [
"crossbeam-utils",
]
[[package]]
name = "crossbeam-utils"
version = "0.8.21"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28"
[[package]]
name = "crypto-common"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1bfb12502f3fc46cca1bb51ac28df9d618d813cdc3d2f25b9fe775a34af26bb3"
dependencies = [
"generic-array",
"typenum",
]
[[package]]
name = "either"
version = "1.15.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "48c757948c5ede0e46177b7add2e67155f70e33c07fea8284df6576da70b3719"
[[package]]
name = "generic-array"
version = "0.14.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a"
dependencies = [
"typenum",
"version_check",
]
[[package]]
name = "inout"
version = "0.1.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "879f10e63c20629ecabbb64a8010319738c66a5cd0c29b02d63d272b03751d01"
dependencies = [
"generic-array",
]
[[package]]
name = "libc"
version = "0.2.174"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1171693293099992e19cddea4e8b849964e9846f4acee11b3948bcc337be8776"
[[package]]
name = "rayon"
version = "1.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b418a60154510ca1a002a752ca9714984e21e4241e804d32555251faf8b78ffa"
dependencies = [
"either",
"rayon-core",
]
[[package]]
name = "rayon-core"
version = "1.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1465873a3dfdaa8ae7cb14b4383657caab0b3e8a0aa9ae8e04b044854c8dfce2"
dependencies = [
"crossbeam-deque",
"crossbeam-utils",
]
[[package]]
name = "typenum"
version = "1.18.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1dccffe3ce07af9386bfd29e80c0ab1a8205a2fc34e4bcd40364df902cfa8f3f"
[[package]]
name = "version_check"
version = "0.9.5"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0b928f33d975fc6ad9f86c8f283853ad26bdd5b10b7f1542aa2fa15e2289105a"

8
Cargo.toml Normal file
View file

@ -0,0 +1,8 @@
[package]
name = "alan-side-channel"
version = "0.1.0"
edition = "2024"
[dependencies]
aes = "0.8.4"
rayon = "1.10.0"

File diff suppressed because it is too large Load diff

File diff suppressed because it is too large Load diff

122
alan/attack.py Executable file
View file

@ -0,0 +1,122 @@
#!/usr/bin/env python3
import numpy as np
import matplotlib.pyplot as plt
import aes
D = 6000 # Number of power traces (Number of Samples)
T = 87 # Number of data points per power trace (Points in time)
KEY_GUESSES = np.arange(256, dtype=np.uint8)
def calculate_models(
ciphertexts: np.ndarray[np.ndarray[np.uint8]],
) -> np.ndarray[np.ndarray[np.ndarray[np.uint8]]]:
"""
Calculates for each sample ciphertext c the state just before the last
SubBytes operation of the AddKey step in the last round of AES.
In order to do this we calculate: sbox^-1 (c k_hyp)
and return an numpy array with this form
[
[ # first ciphertext sample c1
[c_1_0 0, c_1_0 1, ..., c_1_0 255], # first byte of c1
[c_1_1 0, c_1_1 1, ..., c_1_1 255], # second byte of c1
...,
[c_1_15 0, c_1_15 1, ..., c_1_15 255], # 16'th byte of c1
],
[ # second ciphertext sample c2
[c_2_0 0, c_2_0 1, ..., c_2_0 255],
[c_2_1 0, c_2_1 1, ..., c_2_1 255],
...,
[c_2_15 0, c_2_15 1, ..., c_2_15 255],
],
...,
[ # last ciphertext sample cD
[c_D_0 0, c_D_0 1, ..., c_D_0 255],
[c_D_1 0, c_D_1 1, ..., c_D_1 255],
...,
[c_D_15 0, c_D_15 1, ..., c_D_15 255],
],
]
Args:
ciphertexts:
An array of all samples (ciphertexts) as an np.ndarray of np.ndarrays
for each byte as np.uint8.
Returns:
models:
An np.ndarray with each model of the state before the SubBytes
operation of the AddKey step in the last round of AES.
- axis 0: Samples/Ciphertexts
- axis 1: Bytes per sample
- axis 2: Byte xor k_hyp
"""
# duplicate each ciphertext 256 times to xor with all possible keys.
models = np.repeat(ciphertexts, 256, axis=0).reshape((D, 256, 16))
# create a view of models with the inner axis swaped, so when we xor with
# KEY_GUESSES numpy can use broadcast.
models_view = np.swapaxes(models, 1, 2)
# c ⊕ k_hyp
np.bitwise_xor(models_view, KEY_GUESSES, out=models_view)
# apply reverse rbox to all bytes. (rsbox(c ⊕ k_hyp))
models = np.vectorize(lambda x: aes.core.rsbox(x))(models)
return models
def read_msgs(file_name: str) -> np.ndarray[np.ndarray[np.ndarray[np.uint8]]]:
msgs = np.empty((D, 3, 16), dtype=np.uint8)
with open(file_name, 'r') as fd:
for idx, (key, plain_text) in enumerate(
(line.strip().split(',') for line in fd)
):
msgs[idx][0] = np.frombuffer(bytes.fromhex(key), dtype=np.uint8) # key
msgs[idx][1] = np.frombuffer(bytes.fromhex(plain_text), dtype=np.uint8) # plain text
msgs[idx][2] = np.array( # ciphertext
aes.aes(int(key, 16), 128).enc_once(int(plain_text, 16)), dtype=np.uint8
)
return msgs
def read_traces(file_name: str) -> np.ndarray[np.ndarray[np.uint8]]:
return np.loadtxt(file_name, delimiter=",", dtype=np.uint8)
if __name__ == "__main__":
msgs = read_msgs("Task-3-example_traces/test_msgs.csv")
traces = read_traces("Task-3-example_traces/test_traces.csv")
models = calculate_models(msgs[:, 2])
np.set_printoptions(formatter={"int": hex})
last_round_key = aes.core.key_expansion(msgs[0][0].tolist())[-16:]
for bit in range(128):
# i'th row, and j'th col is the correlation coefficient of key_hyp = i and time sample j
r = np.corrcoef(
np.bitwise_and(models[:, :, bit//8], np.array([2**(bit % 8)], dtype=np.uint8)),
traces,
rowvar=False
)[:256, -87:]
guess = np.argmax(np.max(np.abs(r), axis=1))
# print(guess)
exit(0)
# tmp = np.sort(r.flatten())
# confidence = max(abs(tmp[0] - tmp[1]), abs(tmp[-2] - tmp[-1]))
# if confidence > 0.005:
# fig, axs = plt.subplots(1, 1, layout='constrained')
# axs.set_title(f"Bit {bit%8 + 1} of Byte {bit//8 + 1} (Confidence: {confidence:.6f})")
# axs.plot(r.transpose(), alpha=0.3, color='grey')
# axs.plot(r[last_round_key[bit//8]], color="blue")
# axs.plot(r[guess], color="red")
# axs.set_xlabel("Time Samples")
# axs.set_ylabel("Correlation")
# axs.grid(True)
# plt.show()

83
alan/multi-processing.py Normal file
View file

@ -0,0 +1,83 @@
#!/usr/bin/python3
import numpy as np
import matplotlib.pyplot as plt
from multiprocessing import Pool
import aes
D = 6000 # Number of power traces (Number of Samples)
T = 87 # Number of data points per power trace (Points in time)
KEY_GUESSES = np.arange(256, dtype=np.uint8)
def calculate_models(
ciphertext: np.ndarray[np.ndarray[np.uint8]],
) -> np.ndarray[np.ndarray[np.ndarray[np.uint8]]]:
# duplicate each ciphertext 256 times to xor with all possible keys.
models = np.repeat(ciphertext, 256).reshape(16,256)
# create a view of models with the inner axis swaped, so when we xor with
# KEY_GUESSES numpy can use broadcast.
models_view = np.swapaxes(models, 0, 1)
# c ⊕ k_hyp
np.bitwise_xor(models, KEY_GUESSES, out=models)
# apply reverse rbox to all bytes. (rsbox(c ⊕ k_hyp))
models = np.vectorize(lambda x: aes.core.rsbox(x))(models_view)
return models
def read_msgs(file_name: str) -> np.ndarray[np.ndarray[np.ndarray[np.uint8]]]:
msgs = np.empty((D, 3, 16), dtype=np.uint8)
with open(file_name, 'r') as fd:
for idx, (key, plain_text) in enumerate(
(line.strip().split(',') for line in fd)
):
msgs[idx][0] = np.frombuffer(bytes.fromhex(key), dtype=np.uint8) # key
msgs[idx][1] = np.frombuffer(bytes.fromhex(plain_text), dtype=np.uint8) # plain text
msgs[idx][2] = np.array( # ciphertext
aes.aes(int(key, 16), 128).enc_once(int(plain_text, 16)), dtype=np.uint8
)
return msgs
def read_traces(file_name: str) -> np.ndarray[np.ndarray[np.uint8]]:
return np.loadtxt(file_name, delimiter=",", dtype=np.uint8)
if __name__ == "__main__":
msgs = read_msgs("Task-3-example_traces/test_msgs.csv")
traces = read_traces("Task-3-example_traces/test_traces.csv")
with Pool() as pool:
models = pool.map(calculate_models, msgs[:, 2])
models = np.stack(models)
# np.set_printoptions(formatter={"int": hex})
last_round_key = aes.core.key_expansion(msgs[0][0].tolist())[-16:]
for bit in range(128):
# i'th row, and j'th col is the correlation coefficient of key_hyp i and time sample j
model = np.bitwise_and(models[:, :, bit//8], np.array([2**(bit % 8)], dtype=np.uint8))
r = np.corrcoef(
model,
traces,
rowvar=False
)[:256, -87:]
guess = np.argmax(np.max(np.abs(r), axis=1))
# tmp = np.sort(r.flatten())
# confidence = max(abs(tmp[0] - tmp[1]), abs(tmp[-2] - tmp[-1]))
# if confidence > 0.005:
# fig, axs = plt.subplots(1, 1, layout='constrained')
# axs.set_title(f"Bit {bit%8 + 1} of Byte {bit//8 + 1} (Confidence: {confidence:.6f})")
# axs.plot(r.transpose(), alpha=0.3, color='grey')
# axs.plot(r[last_round_key[bit//8]], color="blue")
# axs.plot(r[guess], color="red")
# axs.set_xlabel("Time Samples")
# axs.set_ylabel("Correlation")
# axs.grid(True)
# plt.show()

173
src/main.rs Normal file
View file

@ -0,0 +1,173 @@
#![feature(int_from_ascii)]
use std::{
io::{BufRead, Read},
path::Path,
};
use aes::{
Aes128,
cipher::{BlockEncrypt, KeyInit, generic_array::GenericArray},
};
use rayon::iter::{IntoParallelIterator, ParallelIterator};
static RSBOX: [u8; 256] = [
0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3, 0x9e, 0x81, 0xf3, 0xd7, 0xfb,
0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f, 0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb,
0x54, 0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b, 0x42, 0xfa, 0xc3, 0x4e,
0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24, 0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25,
0x72, 0xf8, 0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d, 0x65, 0xb6, 0x92,
0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda, 0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84,
0x90, 0xd8, 0xab, 0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3, 0x45, 0x06,
0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1, 0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b,
0x3a, 0x91, 0x11, 0x41, 0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6, 0x73,
0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9, 0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e,
0x47, 0xf1, 0x1a, 0x71, 0x1d, 0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0, 0xfe, 0x78, 0xcd, 0x5a, 0xf4,
0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07, 0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f,
0x60, 0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f, 0x93, 0xc9, 0x9c, 0xef,
0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5, 0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61,
0x17, 0x2b, 0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0c, 0x7d,
];
fn calculate_models(ciphertexts: &[[u8; 16]]) -> Vec<[[u8; 16]; 256]> {
ciphertexts
.iter()
.map(|c| {
let mut row = [[0; 16]; 256];
for i in 0..256 {
for j in 0..16 {
row[i][j] = RSBOX[(c[j] ^ (i as u8)) as usize];
}
}
row
})
.collect()
}
fn read_msgs(path: impl AsRef<Path>) -> Vec<[u8; 16]> {
let file = std::fs::File::open(path).unwrap();
let mut bufreader = std::io::BufReader::new(file);
let mut line = [0; 66];
let mut r = Vec::new();
while bufreader.read_exact(&mut line).is_ok() {
let mut key = [0; 16];
let mut msgs = [0; 16];
for i in 0..16 {
key[i] = u8::from_ascii_radix(&line[2 * i..2 * i + 2], 16).unwrap();
msgs[i] = u8::from_ascii_radix(&line[33 + 2 * i..33 + 2 * i + 2], 16).unwrap();
}
let key = GenericArray::from(key);
let mut block = GenericArray::from(msgs.clone());
let cypher = Aes128::new(&key);
cypher.encrypt_block(&mut block);
r.push(*block.as_ref());
}
r
}
const TRACES: usize = 87;
fn read_traces(path: impl AsRef<Path>) -> Vec<[u8; TRACES]> {
let file = std::fs::File::open(path).unwrap();
let bufreader = std::io::BufReader::new(file);
bufreader
.lines()
.map(|l| {
let l = l.unwrap();
let mut trace = [0; TRACES];
for (i, t) in l
.trim()
.split(',')
.map(|t| t.parse::<u8>().unwrap())
.enumerate()
{
trace[i] = t;
}
trace
})
.collect()
}
fn correlation(
bit: usize,
key_hypothesis: usize,
trace_index: usize,
cyphtertext: &[[[u8; 16]; 256]],
traces: &[[u8; TRACES]],
) -> f64 {
let mut x = 0i64;
let mut y = 0i64;
let mut xy = 0i64;
let mut xsqr = 0i64;
let mut ysqr = 0i64;
for i in 0..traces.len() {
let xi = (cyphtertext[i][key_hypothesis][bit / 8] & (1 << (bit % 8))) as i64;
let yi = traces[i][trace_index] as i64;
x += xi;
y += yi;
xy += xi * yi;
xsqr += xi * xi;
ysqr += yi * yi;
}
let n = traces.len() as i64;
let num = (n * xy - x * y) as f64;
let denom = f64::sqrt((n * xsqr - x * x) as f64) * f64::sqrt((n * ysqr - y * y) as f64);
num / denom
}
fn main() {
let start = std::time::Instant::now();
let cyphertext = read_msgs("./alan/Task-3-example_traces/test_msgs.csv");
println!("read msgs: {:?}", start.elapsed());
let start = std::time::Instant::now();
let models = calculate_models(&cyphertext);
println!("calculate models: {:?}", start.elapsed());
let start = std::time::Instant::now();
let traces = read_traces("./alan/Task-3-example_traces/test_traces.csv");
println!("read traces: {:?}", start.elapsed());
let start = std::time::Instant::now();
for bit in 0..128 {
let (max_index, max) = (0..256)
.into_par_iter()
.map(|key_hypothesis| {
let m = (0..traces[0].len())
.map(|trace_index| {
correlation(bit, key_hypothesis, trace_index, &models, &traces).abs()
})
.reduce(f64::max)
.unwrap_or(0.0);
(key_hypothesis, m)
})
.reduce(
|| (0, 0.0),
|a, b| {
if a.1 > b.1 { a } else { b }
},
);
println!("bit: {bit}, key_hypothesis: {max_index}, max: {max}");
}
println!("calculate correlations: {:?}", start.elapsed());
}