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, secretkey

Trace data is column-wise:

  • data is an ndata x ntraces matrix. Each column is the public input for one trace.
  • samples is an nsamples x ntraces matrix. Each column is the measured power trace for the corresponding data column.

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 byte N.
  • AesMcol{R,C}(T) predicts a 32-bit first-round MixColumns contribution for row R, column C, with T = makeT() from AESInternals.
  • AesSboxHD{R,C}() predicts a byte Hamming distance between first-round and second-round S-box outputs for row R, column C.

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(): use count_ones(prediction).
  • BitsLM(): emit one output per bit, 8 outputs for UInt8 predictions and 32 outputs for UInt32 predictions.

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) |> vec

For 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) = 256

A 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