Skip to content

Commit

Permalink
add waverunner batch to simple capture and improve script; also new b…
Browse files Browse the repository at this point in the history
…inary

Signed-off-by: {Johann Heyszl} <[email protected]>
  • Loading branch information
johannheyszl committed Oct 18, 2023
1 parent 5abf4c6 commit 64152b5
Show file tree
Hide file tree
Showing 3 changed files with 160 additions and 114 deletions.
4 changes: 2 additions & 2 deletions cw/objs/aes_serial_fpga_cw310.bin
Git LFS file not shown
260 changes: 151 additions & 109 deletions cw/simple_capture_aes_sca.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,12 +21,12 @@
from util import device, plot, trace_util


def abort_handler_during_loop(project, sig, frame):
def abort_handler_during_loop(this_project, sig, frame):
# Handler for ctrl-c keyboard interrupts
# TODO: Has to be modified according to database (i.e. CW project atm) used
if project is not None:
if this_project is not None:
print("\nHandling keyboard interrupt")
project.close(save=True)
this_project.close(save=True)
sys.exit(0)


Expand All @@ -35,19 +35,34 @@ def abort_handler_during_loop(project, sig, frame):
with open('simple_capture_aes_sca.yaml') as f:
cfg = yaml.load(f, Loader=yaml.FullLoader)

# Choose scope from configuration
if cfg["capture"]["scope_select"] == "waverunner":
# Determine scope and single/batch from configuration ----------------------
BATCH_MODE = False
NUM_SEGMENTS = 1
if "waverunner" in cfg and cfg["capture"]["scope_select"] == "waverunner":
print("Using Waverunner scope")
USE_WAVERUNNER = True
USE_HUSKY = False
WAVERUNNER = True
HUSKY = False

# Determine single or batch mode
if cfg["waverunner"]["num_segments"] > 1:
BATCH_MODE = True
NUM_SEGMENTS = cfg["waverunner"]["num_segments"]
elif cfg["capture"]["scope_select"] == "husky":
print("Using Husky scope")
USE_WAVERUNNER = False
USE_HUSKY = True
WAVERUNNER = False
HUSKY = True

# Batch not supported for Husky at the moment
if cfg["cwfpgahusky"]["num_segments"] > 1:
print("Warning: Sequence (batch) mode not supported for Husky yet")
sys.exit(0)
else:
print("Warning: No valid scope selected in coniguration")

# Create ChipWhisperer project for storage of traces and metadata
# Create ChipWhisperer project for storage of traces and metadata ----------
project = cw.create_project(cfg["capture"]["project_name"], overwrite=True)

# Create device and scope --------------------------------------------------
# Create OpenTitan encapsulating ChipWhisperer Husky and FPGA
# NOTE: A clean separation of the two seems infeasible since
# scope needs FPGA (PLL?) to be configured and target constructor needs scope as input.
Expand All @@ -61,7 +76,7 @@ def abort_handler_during_loop(project, sig, frame):
cfg["cwfpgahusky"]["offset"],
cfg["cwfpgahusky"]["output_len_bytes"])

if USE_WAVERUNNER:
if WAVERUNNER:
# Create WaveRunner
waverunner = WaveRunner(cfg["waverunner"]["waverunner_ip"])
# Capture configuration: num_segments, sparsing, num_samples, first_point, acqu_channel
Expand All @@ -78,121 +93,148 @@ def abort_handler_during_loop(project, sig, frame):
file_name_local = f"{project_name}_data/scope_config_{now_str}.lss"
waverunner.save_setup_to_local_file(file_name_local)

# Check if batch mode is requested TODO: not supported yet
if cfg["waverunner"]["num_segments"] > 1:
print("Warning: Sequence (batch) mode not supported yet")
if cfg["cwfpgahusky"]["num_segments"] > 1:
print("Warning: Sequence (batch) mode not supported yet")

# Register ctrl-c handler to store traces on abort
signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project))
# Preparation of Key and plaintext generation ------------------------------

# Preparation of Key and plaintext generation
# Generate key at random based on test_random_seed (not used atm)
random.seed(cfg["test"]["test_random_seed"])
key = bytearray(cfg["test"]["key_len_bytes"])
for i in range(0, cfg["test"]["key_len_bytes"]):
key[i] = random.randint(0, 255)
# random.seed(cfg["test"]["test_random_seed"])
# key = bytearray(cfg["test"]["key_len_bytes"])
# for i in range(0, cfg["test"]["key_len_bytes"]):
# key[i] = random.randint(0, 255)

# Load initial key and text values from cfg
key = bytearray(cfg["test"]["key"])
print(f'Using key: {binascii.b2a_hex(bytes(key))}')
text = bytearray(cfg["test"]["text"])

# Cipher to compute expected responses
cipher = AES.new(bytes(key), AES.MODE_ECB)

# Prepare generation of new texts/keys by encryption using key_for_generation
key_for_gen = bytearray(cfg["test"]["key_for_gen"])
cipher_gen = AES.new(bytes(key_for_gen), AES.MODE_ECB)

# Seed the target's PRNGs for masking, turns off masking when '0'
cwfpgahusky.target.simpleserial_write("l", cfg["test"]["lfsr_seed"].to_bytes(4, "little"))

# Set key
cwfpgahusky.target.simpleserial_write("k", key)

# Cipher to compute expected responses
cipher = AES.new(bytes(key), AES.MODE_ECB)
if WAVERUNNER and BATCH_MODE:
# Set initial plaintext for batch mode
cwfpgahusky.target.simpleserial_write("i", text)

# Main loop for measurements with progress bar
for _ in tqdm(range(cfg["capture"]["num_traces"]), desc='Capturing', ncols=80):

# TODO: Useful code line for batch capture
# cwfpgahusky..simpleserial_write("s", capture_cfg["batch_prng_seed"].to_bytes(4, "little"))

# Note: Capture performance tested Oct. 2023:
# Using husky with 1200 samples per trace leads to 48 it/s
# Using Waverunner with 1200 - 50000 samples per trace leads to 27 it/s
# Increases to 31 it/s when 'Performance' set to 'Analysis' in Utilies->Preferences
# Transfer over UART only slows down if .e.g transfering key 5 additional times

if USE_HUSKY:
# Arm Husky scope
cwfpgahusky.scope.arm()

if USE_WAVERUNNER:
# Arm Waverunner scope
waverunner.arm()

# Generate new text for this iteration
text = bytearray(cipher_gen.encrypt(text))
# Load text and trigger execution
cwfpgahusky.target.simpleserial_write('p', text)

if USE_HUSKY:
# Capture Husky trace
ret = cwfpgahusky.scope.capture(poll_done=False)
i = 0
while not cwfpgahusky.target.is_done():
i += 1
time.sleep(0.05)
if i > 100:
print("Warning: Target did not finish operation")
if ret:
print("Warning: Timeout happened during capture")

# Get Husky trace
wave = cwfpgahusky.scope.get_last_trace(as_int=True)

if USE_WAVERUNNER:
# Capture and get Waverunner trace
waves = waverunner.capture_and_transfer_waves()
assert waves.shape[0] == cfg["waverunner"]["num_segments"]
# For single capture, 1st dim contains wave data
wave = waves[0, :]
# Put into uint8 range
wave = wave + 128

# Get response from device and verify
response = cwfpgahusky.target.simpleserial_read('r',
cwfpgahusky.target.output_len, ack=False)
if binascii.b2a_hex(response) != binascii.b2a_hex(cipher.encrypt(bytes(text))):
raise RuntimeError(f'Bad ciphertext: {response} != {cipher.encrypt(bytes(text))}.')

# TODO: Useful code line for batch capture
# waves = scope.capture_and_transfer_waves()

# Sanity check retrieved data (wave) and create CW Trace
if len(wave) >= 1:
trace = cw.Trace(wave, text, response, key)
else:
raise RuntimeError('Capture failed.')

if USE_HUSKY:
# Check if ADC range has been exceeded for Husky.
# Not done for WaveRunner because clipping can be inspected on screen.
trace_util.check_range(trace.wave, cwfpgahusky.scope.adc.bits_per_sample)

# Append CW trace to CW project storage
if USE_HUSKY:
project.traces.append(trace, dtype=np.uint16)
if USE_WAVERUNNER:
# Also use uint16 as dtype so that tvla processing works
project.traces.append(trace, dtype=np.uint16)

# Save metadata and entire configuration cfg to project file
project.settingsDict['datetime'] = datetime.now().strftime("%m/%d/%Y, %H:%M:%S")
project.settingsDict['cfg'] = cfg
sample_rate = int(round(cwfpgahusky.scope.clock.adc_freq, -6))
project.settingsDict['sample_rate'] = sample_rate
project.save()
# Main loop for measurements with progress bar -----------------------------

# Create and show test plot
# Register ctrl-c handler to store traces on abort
signal.signal(signal.SIGINT, partial(abort_handler_during_loop, project))

remaining_num_traces = cfg["capture"]["num_traces"]
with tqdm(total=remaining_num_traces, desc="Capturing", ncols=80, unit=" traces") as pbar:
while remaining_num_traces > 0:

# Note: Capture performance tested Oct. 2023:
# Husy with 1200 samples per trace: 50 it/s
# WaveRunner with 1200 - 50000 samples per trace: ~30 it/s
# +10% by setting 'Performance' to 'Analysis' in 'Utilies->Preferences' in GUI
# WaveRunner batchmode (6k samples, 100 segmets, 1 GHz): ~150 it/s

# Arm scope --------------------------------------------------------
if HUSKY:
cwfpgahusky.scope.arm()

if WAVERUNNER:
waverunner.arm()

# Trigger execution(s) ---------------------------------------------
if WAVERUNNER and BATCH_MODE:
# Perform batch encryptions
cwfpgahusky.target.simpleserial_write("a", NUM_SEGMENTS.to_bytes(4, "little"))

else:
# Generate new text for this iteration
text = bytearray(cipher_gen.encrypt(text))
# Load text and trigger execution
cwfpgahusky.target.simpleserial_write('p', text)

# Capture trace(s) (HUSKY single mode only)-------------------------
if HUSKY:
ret = cwfpgahusky.scope.capture(poll_done=False)
i = 0
while not cwfpgahusky.target.is_done():
i += 1
time.sleep(0.05)
if i > 100:
print("Warning: Target did not finish operation")
if ret:
print("Warning: Timeout happened during capture")
# Get Husky trace (single mode only)
wave = cwfpgahusky.scope.get_last_trace(as_int=True)

if WAVERUNNER:
waves = waverunner.capture_and_transfer_waves()
assert waves.shape[0] == NUM_SEGMENTS
# Put into uint8 range
waves = waves + 128

# Storing traces ---------------------------------------------------
if WAVERUNNER and BATCH_MODE:
# Loop through num_segments to store traces and compute ciphertexts
# Note this batch capture command uses the ciphertext as next text
for i in range(NUM_SEGMENTS):
ciphertext = bytearray(cipher.encrypt(bytes(text)))

# Sanity check retrieved data (wave) and create CW Trace
assert len(waves[i, :]) >= 1
wave = waves[i, :]
trace = cw.Trace(wave, text, ciphertext, key)

# Append CW trace to CW project storage
# Also use uint16 as dtype so that tvla processing works
project.traces.append(trace, dtype=np.uint16)

# Use ciphertext as next text
text = ciphertext

else: # not BATCH_MODE either scope
ciphertext = bytearray(cipher.encrypt(bytes(text)))

if WAVERUNNER:
# For single capture on WaveRunner, waves[0] contains data
wave = waves[0, :]

# Sanity check retrieved data (wave) and create CW Trace
assert len(wave) >= 1
trace = cw.Trace(wave, text, ciphertext, key)
if HUSKY:
# Check if ADC range has been exceeded for Husky.
# Not done for WaveRunner because clipping can be inspected on screen.
trace_util.check_range(trace.wave, cwfpgahusky.scope.adc.bits_per_sample)

# Append CW trace to CW project storage
# Also use uint16 for WaveRunner even though 8 bit so that tvla processing works
project.traces.append(trace, dtype=np.uint16)

# Get (last) ciphertext after all callsfrom device and verify ------
response = cwfpgahusky.target.simpleserial_read('r',
cwfpgahusky.target.output_len, ack=False)
if binascii.b2a_hex(response) != binascii.b2a_hex(ciphertext):
raise RuntimeError(f'Bad ciphertext: {response} != {ciphertext}.')

# Update the loop variable and the progress bar.
remaining_num_traces -= NUM_SEGMENTS
pbar.update(NUM_SEGMENTS)

# Create and show test plot ------------------------------------------------
if cfg["capture"]["show_plot"]:
plot.save_plot_to_file(project.waves, None, cfg["capture"]["plot_traces"],
cfg["capture"]["trace_image_filename"], add_mean_stddev=True)
print(f'Created plot with {cfg["capture"]["plot_traces"]} traces: '
f'{Path(cfg["capture"]["trace_image_filename"]).resolve()}')

# Save metadata and entire configuration cfg to project file ---------------
project.settingsDict['datetime'] = datetime.now().strftime("%m/%d/%Y, %H:%M:%S")
project.settingsDict['cfg'] = cfg
if HUSKY:
sample_rate = int(round(cwfpgahusky.scope.clock.adc_freq, -6))
project.settingsDict['sample_rate'] = sample_rate
project.save()
10 changes: 7 additions & 3 deletions cw/simple_capture_aes_sca.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -11,20 +11,24 @@ cwfpgahusky:
scope_gain: 31.5
waverunner:
waverunner_ip: 100.107.71.10
num_segments: 1
num_segments: 50
# currently need to manually disable seq mode on scope when setting to 1
num_samples: 6000
sample_offset: 0
capture:
scope_select: husky
# scope_select: waverunner
num_traces: 1000
num_traces: 1000000
project_name: "projects/simple_capture_aes_sca"
show_plot: True
plot_traces: 50
plot_traces: 100
trace_image_filename: "projects/simple_capture_aes_sca_sample_traces.html"
test:
key_len_bytes: 16
test_random_seed: 0
key: [0x81, 0x1E, 0x37, 0x31, 0xB0, 0x12, 0x0A, 0x78, 0x42, 0x78, 0x1E, 0x22, 0xB2, 0x5C, 0xDD, 0xF9]
text: [0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA, 0xAA]
key_for_gen: [0x12, 0x34, 0x56, 0x78, 0x9A, 0xBC, 0xDE, 0xF1, 0x23, 0x45, 0x67, 0x89, 0xAB, 0xCD, 0xE0, 0xF0]
# 32-bit seed for masking on device. To switch off the masking, use 0 as LFSR seed.
# lfsr_seed: 0xdeadbeef
lfsr_seed: 0x00000000

0 comments on commit 64152b5

Please sign in to comment.