Overview
ChipWhisperer is the leading open-source hardware security platform for performing side-channel attacks (power analysis, electromagnetic analysis) and fault injection (voltage glitching, clock glitching) against embedded systems. Developed by NewAE Technology, it enables security researchers to measure the power consumption of cryptographic operations and extract secret keys, or inject precise voltage glitches to bypass security checks and dump protected firmware. ChipWhisperer democratizes hardware security testing that previously required equipment costing tens of thousands of dollars.
The platform consists of hardware capture/glitch boards (ChipWhisperer-Lite, ChipWhisperer-Husky, ChipWhisperer-Pro) and a comprehensive Python framework (chipwhisperer package) with Jupyter notebook tutorials. It supports attacks against AES, RSA, ECC, and other cryptographic implementations, as well as fault attacks to bypass secure boot, extract keys from locked microcontrollers, and break authentication mechanisms. ChipWhisperer is widely used in academic research, security certifications (Common Criteria, FIPS), and penetration testing of IoT devices, smart cards, and embedded systems.
Installation
Python Package
# Install ChipWhisperer Python package
pip install chipwhisperer
# Install with Jupyter notebook support
pip install chipwhisperer[notebooks]
# Install from source (latest)
git clone https://github.com/newaetech/chipwhisperer.git
cd chipwhisperer
pip install -e .
pip install -r requirements.txt
# Install Jupyter notebooks
cd chipwhisperer/jupyter
pip install -r requirements.txt
USB Permissions (Linux)
# Add udev rules
sudo cp hardware/chipwhisperer/50-newae.rules /etc/udev/rules.d/
sudo udevadm control --reload-rules
sudo udevadm trigger
# Add user to dialout group
sudo usermod -aG dialout $USER
sudo usermod -aG plugdev $USER
Jupyter Notebooks
# Launch tutorial notebooks
cd chipwhisperer/jupyter
jupyter notebook
# Navigate to:
# courses/sca101/ - Side Channel Analysis 101
# courses/fault101/ - Fault Injection 101
# courses/sca201/ - Advanced SCA
Hardware Setup
Supported Hardware
| Board | Capture | Glitch | Channels | Sample Rate |
|---|
| ChipWhisperer-Lite | Yes | Yes | 1 | 105 MS/s |
| ChipWhisperer-Husky | Yes | Yes | 1 | 200 MS/s |
| ChipWhisperer-Pro | Yes | Yes | 1 | 105 MS/s |
| PhyWhisperer-USB | No | USB | - | - |
| ChipWhisperer-Nano | Yes | Yes | 1 | 20 MS/s |
Target Boards
| Target | Description |
|---|
| CW308 UFO Board | Universal target platform |
| CW308T-STM32F | STM32F0/F1/F3/F4 targets |
| CW308T-XMEGA | Atmel XMEGA target |
| CW308T-K82F | NXP Kinetis K82F target |
| CW308T-EFM32 | Silicon Labs EFM32 target |
| CW312 Bergen Board | Multi-target board |
| CW305 Artix FPGA | FPGA target for crypto |
Connection Setup
import chipwhisperer as cw
# Connect to ChipWhisperer-Lite
scope = cw.scope()
# Connect to specific board
scope = cw.scope(scope_type=cw.scopes.OpenADC)
# Connect to target
target = cw.target(scope, cw.targets.SimpleSerial)
# Program target firmware
cw.program_target(scope, cw.programmers.STM32FProgrammer,
"simpleserial-aes-CWLITEARM.hex")
Power Analysis Attacks
Simple Power Analysis (SPA)
import chipwhisperer as cw
import numpy as np
# Setup
scope = cw.scope()
target = cw.target(scope)
# Configure capture
scope.default_setup()
scope.adc.samples = 5000
scope.adc.offset = 0
# Capture single trace
scope.arm()
target.simpleserial_write('p', bytearray(16)) # Send plaintext
scope.capture()
trace = scope.get_last_trace()
response = target.simpleserial_read('r', 16)
# Plot trace
import matplotlib.pyplot as plt
plt.plot(trace)
plt.title("Power Trace during AES Encryption")
plt.xlabel("Sample")
plt.ylabel("Power")
plt.show()
Correlation Power Analysis (CPA)
import chipwhisperer as cw
import chipwhisperer.analyzer as cwa
import numpy as np
# Setup
scope = cw.scope()
target = cw.target(scope)
scope.default_setup()
scope.adc.samples = 5000
# Collect traces
num_traces = 2000
project = cw.create_project("aes_cpa")
for i in range(num_traces):
key = bytearray(16) # Fixed key
text = cw.ktp.get_next_plaintext()
trace = cw.capture_trace(scope, target, text, key)
if trace:
project.traces.append(trace)
project.save()
# Run CPA attack
attack = cwa.cpa(project)
attack.set_leak_model(cwa.leakage_models.sbox_output)
# Process results
results = attack.run()
# Get recovered key
recovered_key = attack.get_results().best_key()
print(f"Recovered key: {recovered_key.hex()}")
# Plot correlation
plot_data = attack.get_statistics()
plt.plot(plot_data.correlation[0])
plt.title("CPA Correlation - Key Byte 0")
plt.show()
Template Attack
# Build profiling traces (known key)
profiling_project = cw.create_project("profiling")
for i in range(5000):
text = cw.ktp.get_next_plaintext()
trace = cw.capture_trace(scope, target, text, known_key)
if trace:
profiling_project.traces.append(trace)
# Build templates
template_attack = cwa.profiling.TemplateAttack(profiling_project)
template_attack.generate_templates()
# Attack with unknown key
attack_project = cw.create_project("attack")
for i in range(50): # Very few traces needed
text = cw.ktp.get_next_plaintext()
trace = cw.capture_trace(scope, target, text, unknown_key)
if trace:
attack_project.traces.append(trace)
# Recover key
results = template_attack.attack(attack_project)
print(f"Key: {results.best_key().hex()}")
Fault Injection
Voltage Glitching
import chipwhisperer as cw
scope = cw.scope()
target = cw.target(scope)
# Configure glitch module
scope.glitch.clk_src = "clkgen"
scope.glitch.output = "enable_only"
scope.glitch.trigger_src = "ext_single"
# Glitch parameters
scope.glitch.width = 10.0 # Glitch width (% of clock period)
scope.glitch.offset = 10.0 # Offset from trigger
scope.glitch.repeat = 1 # Number of glitch pulses
scope.glitch.ext_offset = 100 # External trigger offset (clock cycles)
# Voltage glitch settings
scope.io.glitch_hp = True # High-power MOSFET
scope.io.glitch_lp = False # Low-power MOSFET
# Arm and trigger
scope.arm()
target.simpleserial_write('p', bytearray(16))
scope.capture()
# Check if glitch was successful
response = target.simpleserial_read('r', 16, timeout=100)
Glitch Sweep (Finding Parameters)
import chipwhisperer as cw
import chipwhisperer.common.results.glitch as glitch
scope = cw.scope()
target = cw.target(scope)
# Configure glitch
scope.glitch.clk_src = "clkgen"
scope.glitch.output = "enable_only"
scope.glitch.trigger_src = "ext_single"
# Sweep parameters
gc = glitch.GlitchController(groups=["success", "reset", "normal"])
gc.set_range("width", 1, 50)
gc.set_range("offset", 1, 50)
gc.set_range("ext_offset", 0, 500)
for glitch_settings in gc.glitch_values():
scope.glitch.width = glitch_settings[0]
scope.glitch.offset = glitch_settings[1]
scope.glitch.ext_offset = glitch_settings[2]
# Reset target
target.flush()
scope.arm()
# Trigger operation
target.simpleserial_write('p', bytearray(16))
ret = scope.capture()
if ret:
gc.add("reset", glitch_settings)
continue
response = target.simpleserial_read('r', 16, timeout=50)
if response is None:
gc.add("reset", glitch_settings)
elif check_success(response):
gc.add("success", glitch_settings)
print(f"SUCCESS: width={glitch_settings[0]}, offset={glitch_settings[1]}")
else:
gc.add("normal", glitch_settings)
# Plot results
gc.display_stats()
Clock Glitching
# Configure clock glitch (insert extra clock edges)
scope.glitch.clk_src = "clkgen"
scope.glitch.output = "clock_xor" # XOR with target clock
scope.glitch.trigger_src = "ext_single"
scope.glitch.width = 49.0 # ~50% duty cycle glitch
scope.glitch.offset = 0.0
scope.glitch.ext_offset = 200 # 200 clock cycles after trigger
scope.glitch.repeat = 1
Configuration
Scope Settings
# Clock configuration
scope.clock.clkgen_freq = 7370000 # 7.37 MHz
scope.clock.adc_src = "clkgen_x4" # 29.48 MS/s
# ADC configuration
scope.adc.samples = 5000
scope.adc.offset = 0
scope.adc.basic_mode = "rising_edge"
scope.adc.timeout = 2
# Trigger configuration
scope.trigger.triggers = "tio4" # Trigger source
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "clkgen" # Output clock to target
# Target voltage
scope.io.target_pwr = True # Enable 3.3V to target
Capture Settings for Different Targets
# AES on ARM Cortex-M (STM32)
scope.adc.samples = 5000
scope.clock.clkgen_freq = 7.37e6
scope.adc.offset = 0
# AES on XMEGA
scope.adc.samples = 3000
scope.clock.clkgen_freq = 7.37e6
# RSA (much longer operation)
scope.adc.samples = 24400
scope.clock.clkgen_freq = 7.37e6
# ECC
scope.adc.samples = 20000
Advanced Usage
Electromagnetic Analysis (EMA)
# Use H-field probe instead of shunt resistor
# Connect EM probe to scope input
scope.adc.preamp_gain = 40 # Increase gain for EM
scope.adc.samples = 10000
# Capture and analyze same as power analysis
# but with EM probe positioned over target IC
Preprocessing
from chipwhisperer.analyzer.preprocessing import resync_dtw, filter_type
# Resynchronize traces (remove jitter)
resync = resync_dtw.ResyncDTW(project)
resync.ref_trace = 0
resync.radius = 5
resync_project = resync.preprocess()
# Low-pass filter
filtered = filter_type.FilterType(project)
filtered.filter_type = "low"
filtered.cutoff = 0.1
filtered_project = filtered.preprocess()
Test Vector Leakage Assessment (TVLA)
# Fixed vs Random t-test (detect leakage)
from chipwhisperer.analyzer import tvla
# Collect fixed key traces
fixed_traces = []
random_traces = []
for i in range(5000):
if i % 2 == 0:
text = fixed_plaintext
else:
text = random_plaintext()
trace = cw.capture_trace(scope, target, text, key)
if i % 2 == 0:
fixed_traces.append(trace.wave)
else:
random_traces.append(trace.wave)
# Calculate t-statistic
t_val = tvla.t_test(np.array(fixed_traces), np.array(random_traces))
# Plot: |t| > 4.5 indicates leakage
plt.plot(np.abs(t_val))
plt.axhline(y=4.5, color='r', linestyle='--')
plt.title("TVLA t-test")
plt.show()
Troubleshooting
| Issue | Solution |
|---|
| Device not found | Check USB cable, install udev rules |
| Noisy traces | Shorten probe wires, add decoupling caps |
| No trigger | Verify trigger pin, check scope.trigger config |
| Glitch has no effect | Adjust width/offset, verify glitch output |
| Target resets during glitch | Reduce glitch width/repeat, check power supply |
| CPA attack fails | Need more traces, check alignment, verify model |
| Traces misaligned | Use resync preprocessing, check clock stability |
| Firmware programming fails | Verify programmer type, check connections |
Diagnostic Commands
# Check scope connection
scope = cw.scope()
print(scope)
print(f"FW Version: {scope.fw_version}")
print(f"SAM FW: {scope.sam_fw_version}")
# Check target connection
target = cw.target(scope)
target.flush()
target.simpleserial_write('v', b'') # Get version
print(target.simpleserial_read('v', 100))
# Verify clock
print(f"Clock freq: {scope.clock.clkgen_freq}")
print(f"ADC freq: {scope.clock.adc_freq}")
# Check glitch module
print(f"Glitch width: {scope.glitch.width}")
print(f"Glitch offset: {scope.glitch.offset}")
# Disconnect cleanly
scope.dis()
target.dis()