Noise in AES fpga implementation in CW HUSKY

Hello,

I am trying to implement the PA_HW_CW305_1-Attacking_AES_on_an_FPGA notebook using the default setup with CW Husky, CW313, and CW312T-A35. However, the traces I am capturing appear to be entirely noise. I have experimented with various sample sizes, but the issue persists. Here is an image of the traces I am getting:

AES_200_samples

AES_20000_samples

I was expecting to see all 10 rounds of AES in the simple power analysis trace, but that’s not happening. Can anyone identify the problem and suggest how to make all rounds clearly visible in the trace?

Here’s the code I’m using:

import chipwhisperer as cw
scope = cw.scope()
if scope._is_husky:
    scope.adc.samples = 200
else:
    scope.adc.samples = 129
scope.adc.offset = 0
scope.adc.basic_mode = "rising_edge"
scope.trigger.triggers = "tio4"
scope.io.tio1 = "serial_rx"
scope.io.tio2 = "serial_tx"
scope.io.hs2 = "disabled"

#TARGET_PLATFORM = 'CW305_100t'
#TARGET_PLATFORM = 'CW305_35t'
TARGET_PLATFORM = 'CW312T_A35'
#TARGET_PLATFORM = 'CW312T_ICE40'


if TARGET_PLATFORM in ['CW312T_A35', 'CW312T_ICE40']:
    scope.io.hs2 = 'clkgen'
    fpga_id = None # not needed
    if TARGET_PLATFORM == 'CW312T_A35':
        platform = 'ss2_a35'
        scope.gain.db = 45 # this is a good setting for the inductive shunt; if using another, adjust as needed
    else:
        platform = 'ss2_ice40'
        scope.gain.db = 15
else:
    scope.gain.db = 25
    scope.io.hs2 = "disabled"
    platform = 'cw305'
    if TARGET_PLATFORM == 'CW305_100t':
        fpga_id = '100t'
    elif TARGET_PLATFORM == 'CW305_35t':
        fpga_id = '35t'

# On the CW305, setting force=False only programs the FPGA if it is currently unprogrammed, whereas force=True programs the FPGA regardless.
# This option isn't available on the CW312T_A35 or CW312T_ICE40.
target = cw.target(scope, cw.targets.CW305, force=True, fpga_id=fpga_id, platform=platform)

if TARGET_PLATFORM in ['CW305_100t', 'CW305_35t']:
    target.vccint_set(1.0)
    # we only need PLL1:
    target.pll.pll_enable_set(True)
    target.pll.pll_outenable_set(False, 0)
    target.pll.pll_outenable_set(True, 1)
    target.pll.pll_outenable_set(False, 2)

    # run at 10 MHz:
    target.pll.pll_outfreq_set(10E6, 1)

    # 1ms is plenty of idling time
    target.clkusbautooff = True
    target.clksleeptime = 1


if TARGET_PLATFORM in ['CW305_100t', 'CW305_35t']:
    if scope._is_husky:
        scope.clock.clkgen_freq = 40e6
        scope.clock.clkgen_src = 'extclk'
        scope.clock.adc_mul = 4
        # if the target PLL frequency is changed, the above must also be changed accordingly
    else:
        scope.clock.adc_src = "extclk_x4"


if TARGET_PLATFORM in ['CW312T_A35', 'CW312T_ICE40']:
    scope.clock.clkgen_freq = 7.37e6
    scope.io.hs2 = 'clkgen'
    if scope._is_husky:
        scope.clock.clkgen_src = 'system'
        scope.clock.adc_mul = 4
        scope.clock.reset_dcms()
    else:
        scope.clock.adc_src = "clkgen_x4"
    import time
    time.sleep(0.1)
    target._ss2_test_echo()

import time
for i in range(5):
    scope.clock.reset_adc()
    time.sleep(1)
    if scope.clock.adc_locked:
        break 
assert (scope.clock.adc_locked), "ADC failed to lock"


project_file = "projects/Tutorial_HW_CW305.cwp"
project = cw.create_project(project_file, overwrite=True)


from tqdm.notebook import tnrange
import numpy as np
import time
from Crypto.Cipher import AES

ktp = cw.ktp.Basic()

traces = []
textin = []
keys = []
N = 50  # Number of traces

# initialize cipher to verify DUT result:
key, text = ktp.next()
cipher = AES.new(bytes(key), AES.MODE_ECB)

for i in tnrange(N, desc='Capturing traces'):
    # run aux stuff that should come before trace here

    key, text = ktp.next()  # manual creation of a key, text pair can be substituted here
    textin.append(text)
    keys.append(key)
    
    ret = cw.capture_trace(scope, target, text, key)
    if not ret:
        print("Failed capture")
        continue

    assert (list(ret.textout) == list(cipher.encrypt(bytes(text)))), "Incorrect encryption result!\nGot {}\nExp {}\n".format(ret.textout, list(text))
    #trace += scope.getLastTrace()
        
    traces.append(ret.wave)
    project.traces.append(ret)

Probably, you expect to see SW like patterns of the AES rounds. But HW implementation doesn’t have such patterns.
Capture 5-10 traces and draw them to make sure whether it is noise. If it is a real noise, the traces will not be aligned. Also trace syncronization will be lost.

First- does the CPA attack in the notebook succeed? If it does, then you know that the traces aren’t just noise :wink:

Second- the notebook explains, “the encryption is completed in less than 60 samples with an x4 ADC clock.”, so plotting 20k samples is going to really hide the AES operation.

In your first plot, I can clearly see 10 peaks corresponding to the 10 AES rounds between samples 0-50 (approximately).

1 Like

Is it possible to set adc_mul to 6 or 8 in Husky? When I tried with scope.clock.adc_mul = 6, the traces showed cleaner signals, and all rounds were clearly visible. However, at the same time, the ADC and Glitch LEDs were blinking, which means there were some errors, right? Why is this happening? I did not receive any errors in the notebook, and the ciphertexts were correct as well.

Sure, as long as the sampling rate doesn’t go over 200 MS/s. Don’t forget to increase the number of samples accordingly.

Red blinking LEDs means that Husky hardware detected an error condition. You need to query scope.errors to learn what it is, as explained here , section 1.