DifferentialPowerAttacks
A Julia library for correlation / differential power analysis attacks.
DifferentialPowerAttacks separates an attack into predictors, leakage models, trace data, and measured samples. It uses BatchStats internally, so traces can be processed in batches and independently accumulated attacks can be combined.
Currently only attacks on AES are contained in this library, but the library easily extends to other attacks that you can implement without having to embed into DifferentialPowerAttacks.
Examples
First, a simple first-round AES S-box attack on one key byte.
using DifferentialPowerAttacks
import DifferentialPowerAttacks: leak, predict
ntraces = 1000
nsamples = 40
data = rand(UInt8, 16, ntraces)
samples = rand(UInt8, nsamples, ntraces)
predictor = AesSbox{1}()
leakagemodel = HammingWeightLM()
secretkey = rand(UInt8)
# For this example, put an exact simulated leakage at sample 19.
samples[19, :] = [
leak(leakagemodel, 1, predict(predictor, input, secretkey))
for input in eachcol(data)
]
attack = run_attack(predictor, leakagemodel, data, samples)
scores = getScores(attack) |> vec
guess = UInt8(sortperm(scores, rev = true)[1] - 1)
@show guess, secretkeyTrace data is column-wise:
datais anndata x ntracesmatrix. Each column is the public input for one trace.samplesis annsamples x ntracesmatrix. Each column is the measured power trace for the correspondingdatacolumn.
Scores are indexed by key candidate plus one. For an 8-bit key byte, candidate 0x2a is stored at scores[0x2a + 1].
Predictors
Predictors map public trace input and a key candidate to an intermediate value. The currently available AES predictors are:
AesSbox{N}()predicts the first-round S-box output for input byteN.AesMcol{R,C}(T)predicts a 32-bit first-round MixColumns contribution for rowR, columnC, withT = makeT()fromAESInternals.AesSboxHD{R,C}()predicts a byte Hamming distance between first-round and second-round S-box outputs for rowR, columnC.
You can attack multiple compatible predictors at once:
predictors = (AesSbox{1}(), AesSbox{2}())
leakagemodels = (IdentityLM(), HammingWeightLM(), BitsLM())
attack = run_attack(predictors, leakagemodels, data, samples)
scores = getScores(attack)Predictors used together must have the same number of key candidates and the same predicted value type.
Leakage Models
Leakage models map predicted values to hypothetical leakage values.
The built-in leakage models are:
IdentityLM(): use the predicted value directly.HammingWeightLM(): usecount_ones(prediction).BitsLM(): emit one output per bit, 8 outputs forUInt8predictions and 32 outputs forUInt32predictions.
Leakage models used together must produce the same leakage element type for the predictor value type.
Batch And Aggregate Updates
You can process traces in chunks and combine the accumulated statistics.
import DifferentialPowerAttacks: CPAttack, add!
attack1 = run_attack(predictor, leakagemodel, data[:, 1:500], samples[:, 1:500])
attack2 = run_attack(predictor, leakagemodel, data[:, 501:1000], samples[:, 501:1000])
add!(attack1, attack2)
scores = getScores(attack1)You can also create an attack accumulator and feed more traces into it later.
attack = CPAttack(predictor, leakagemodel, nsamples)
run_attack(attack, data[:, 1:500], samples[:, 1:500])
run_attack(attack, data[:, 501:1000], samples[:, 501:1000])Embedding In Other Code
If you embed this library in a larger capture, parsing, or processing pipeline, you probably do not want run_attack(...) to own the full trace loop. That method is a convenience wrapper for matrix-shaped inputs. In application code it is usually cleaner to create CPAttack instances once and call add! whenever a trace or batch of traces is available.
using DifferentialPowerAttacks
import DifferentialPowerAttacks: CPAttack, add!
predictor = AesSbox{1}()
leakagemodel = HammingWeightLM()
nsamples = 40
attack = CPAttack(predictor, leakagemodel, nsamples)
for _ in 1 : 1000
input = rand(UInt8, 16)
trace = rand(UInt8, nsamples)
add!(attack, trace, input)
end
scores = getScores(attack) |> vecFor higher throughput, call add! with matrices instead of individual traces:
inputs = rand(UInt8, 16, 128)
traces = rand(UInt8, nsamples, 128)
add!(attack, traces, inputs)Progress And Serialization
There are two simple loggers.
logger = (ConsoleProgressLogger(10.0),)
attack = run_attack(predictor, leakagemodel, data, samples; logger = logger)ConsoleProgressLogger prints top key candidates while the attack runs. ScoresSerializer periodically serializes accumulated CPAttack snapshots.
logger = (ScoresSerializer("scores.bin", 10_000),)
attack = run_attack(predictor, leakagemodel, data, samples; logger = logger)Custom Predictors And Leakage Models
Users can define their own predictors and leakage models. A predictor should subtype Predictor{T} for its predicted value type and implement predict and nkeys.
import DifferentialPowerAttacks: Predictor, predict, nkeys
struct MyPredictor <: Predictor{UInt8} end
predict(::MyPredictor, input, keycandidate) = input[1] ⊻ UInt8(keycandidate)
nkeys(::MyPredictor) = 256A leakage model should subtype LeakageModel and implement leak, noutputs, and leaktype.
import DifferentialPowerAttacks: LeakageModel, leak, noutputs, leaktype
struct LowNibbleLM <: LeakageModel end
noutputs(::LowNibbleLM, ::Type{UInt8}) = 1
leaktype(::LowNibbleLM, ::Type{UInt8}) = UInt8
leak(::LowNibbleLM, _, prediction) = prediction & 0x0f