= Further Analysis =
- Earlier, we said that our clock glitches would corrupt some bits of the internal state inside the FPGA's AES module. It's actually possible to use our glitched ciphertext to check if this is happening! Here's a rough outline of the steps required to do this:* Modify an AES implementation to save the values in the state array after every round (instead of only returning the output from the last round)* Encrypt the fixed plaintext using the fixed key to get the expected intermediate state values* Decrypt the glitched ciphertext using the fixed key to get the actual intermediate state values* Compare the two sets of states to check if they were ever close to each other (for example, by looking for small Hamming distances)A Python scriptto do this is shown in [[#Appendix A: Analysis Code]]. - Outline For example, three of the glitched ciphertexts were <code process>9b1202b925e0cb4967c486a69ede3133</code>, <code>cd9543adaa5bcbc558e7d4944b5230f7</code>, and <code>40581e3c073aaace9b9785fc258bac41</code>. The Hamming distance between the expected and actual states are shown in blue, green, and red, respectively: [[File:CW305HammingDistance.png|600px]] The first two curves show a significant dip at round 6, suggesting that the glitch caused 30 to 40 bits of the state to be corrupted after the 6th round. Then, these bits were immediately diffused into the rest of the state in the next round, causing the output to look totally random (because a Hamming distance of 64 indicates a 50% match rate). However, the final curve doesn't show this dip. It's possible that this glitch caused a different kind of error - Show perhaps the key schedule was corrupted instead. = Appendix A: Analysis Code =<div class="toccolours mw-collapsible mw-collapsed"><code >aes-int.py</code><div class="mw-collapsible-content"><syntaxhighlight lang="python">'''aes-int.pyAn AES-128 analysis script for using with NewAE Tutorial Cw305-3 Author: Greg d'EonDate: Jan 17, 2017''' from Crypto.Cipher import AESimport binasciiimport matplotlib.pyplot as plt # --------------------------------------------------------------- Lookup tabless_box = [ 0x63, 0x7C, 0x77, 0x7B, 0xF2, 0x6B, 0x6F, 0xC5, 0x30, 0x01, 0x67, 0x2B, 0xFE, 0xD7, 0xAB, 0x76, 0xCA, 0x82, 0xC9, 0x7D, 0xFA, 0x59, 0x47, 0xF0, 0xAD, 0xD4, 0xA2, 0xAF, 0x9C, 0xA4, 0x72, 0xC0, 0xB7, 0xFD, 0x93, 0x26, 0x36, 0x3F, 0xF7, 0xCC, 0x34, 0xA5, 0xE5, 0xF1, 0x71, 0xD8, 0x31, 0x15, 0x04, 0xC7, 0x23, 0xC3, 0x18, 0x96, 0x05, 0x9A, 0x07, 0x12, 0x80, 0xE2, 0xEB, 0x27, 0xB2, 0x75, 0x09, 0x83, 0x2C, 0x1A, 0x1B, 0x6E, 0x5A, 0xA0, 0x52, 0x3B, 0xD6, 0xB3, 0x29, 0xE3, 0x2F, 0x84, 0x53, 0xD1, 0x00, 0xED, 0x20, 0xFC, 0xB1, 0x5B, 0x6A, 0xCB, 0xBE, 0x39, 0x4A, 0x4C, 0x58, 0xCF, 0xD0, 0xEF, 0xAA, 0xFB, 0x43, 0x4D, 0x33, 0x85, 0x45, 0xF9, 0x02, 0x7F, 0x50, 0x3C, 0x9F, 0xA8, 0x51, 0xA3, 0x40, 0x8F, 0x92, 0x9D, 0x38, 0xF5, 0xBC, 0xB6, 0xDA, 0x21, 0x10, 0xFF, 0xF3, 0xD2, 0xCD, 0x0C, 0x13, 0xEC, 0x5F, 0x97, 0x44, 0x17, 0xC4, 0xA7, 0x7E, 0x3D, 0x64, 0x5D, 0x19, 0x73, 0x60, 0x81, 0x4F, 0xDC, 0x22, 0x2A, 0x90, 0x88, 0x46, 0xEE, 0xB8, 0x14, 0xDE, 0x5E, 0x0B, 0xDB, 0xE0, 0x32, 0x3A, 0x0A, 0x49, 0x06, 0x24, 0x5C, 0xC2, 0xD3, 0xAC, 0x62, 0x91, 0x95, 0xE4, 0x79, 0xE7, 0xC8, 0x37, 0x6D, 0x8D, 0xD5, 0x4E, 0xA9, 0x6C, 0x56, 0xF4, 0xEA, 0x65, 0x7A, 0xAE, 0x08, 0xBA, 0x78, 0x25, 0x2E, 0x1C, 0xA6, 0xB4, 0xC6, 0xE8, 0xDD, 0x74, 0x1F, 0x4B, 0xBD, 0x8B, 0x8A, 0x70, 0x3E, 0xB5, 0x66, 0x48, 0x03, 0xF6, 0x0E, 0x61, 0x35, 0x57, 0xB9, 0x86, 0xC1, 0x1D, 0x9E, 0xE1, 0xF8, 0x98, 0x11, 0x69, 0xD9, 0x8E, 0x94, 0x9B, 0x1E, 0x87, 0xE9, 0xCE, 0x55, 0x28, 0xDF, 0x8C, 0xA1, 0x89, 0x0D, 0xBF, 0xE6, 0x42, 0x68, 0x41, 0x99, 0x2D, 0x0F, 0xB0, 0x54, 0xBB, 0x16] s_box_inv = [ 0x52, 0x09, 0x6A, 0xD5, 0x30, 0x36, 0xA5, 0x38, 0xBF, 0x40, 0xA3, 0x9E, 0x81, 0xF3, 0xD7, 0xFB, 0x7C, 0xE3, 0x39, 0x82, 0x9B, 0x2F, 0xFF, 0x87, 0x34, 0x8E, 0x43, 0x44, 0xC4, 0xDE, 0xE9, 0xCB, 0x54, 0x7B, 0x94, 0x32, 0xA6, 0xC2, 0x23, 0x3D, 0xEE, 0x4C, 0x95, 0x0B, 0x42, 0xFA, 0xC3, 0x4E, 0x08, 0x2E, 0xA1, 0x66, 0x28, 0xD9, 0x24, 0xB2, 0x76, 0x5B, 0xA2, 0x49, 0x6D, 0x8B, 0xD1, 0x25, 0x72, 0xF8, 0xF6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xD4, 0xA4, 0x5C, 0xCC, 0x5D, 0x65, 0xB6, 0x92, 0x6C, 0x70, 0x48, 0x50, 0xFD, 0xED, 0xB9, 0xDA, 0x5E, 0x15, 0x46, 0x57, 0xA7, 0x8D, 0x9D, 0x84, 0x90, 0xD8, 0xAB, 0x00, 0x8C, 0xBC, 0xD3, 0x0A, 0xF7, 0xE4, 0x58, 0x05, 0xB8, 0xB3, 0x45, 0x06, 0xD0, 0x2C, 0x1E, 0x8F, 0xCA, 0x3F, 0x0F, 0x02, 0xC1, 0xAF, 0xBD, 0x03, 0x01, 0x13, 0x8A, 0x6B, 0x3A, 0x91, 0x11, 0x41, 0x4F, 0x67, 0xDC, 0xEA, 0x97, 0xF2, 0xCF, 0xCE, 0xF0, 0xB4, 0xE6, 0x73, 0x96, 0xAC, 0x74, 0x22, 0xE7, 0xAD, 0x35, 0x85, 0xE2, 0xF9, 0x37, 0xE8, 0x1C, 0x75, 0xDF, 0x6E, 0x47, 0xF1, 0x1A, 0x71, 0x1D, 0x29, 0xC5, 0x89, 0x6F, 0xB7, 0x62, 0x0E, 0xAA, 0x18, 0xBE, 0x1B, 0xFC, 0x56, 0x3E, 0x4B, 0xC6, 0xD2, 0x79, 0x20, 0x9A, 0xDB, 0xC0, 0xFE, 0x78, 0xCD, 0x5A, 0xF4, 0x1F, 0xDD, 0xA8, 0x33, 0x88, 0x07, 0xC7, 0x31, 0xB1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xEC, 0x5F, 0x60, 0x51, 0x7F, 0xA9, 0x19, 0xB5, 0x4A, 0x0D, 0x2D, 0xE5, 0x7A, 0x9F, 0x93, 0xC9, 0x9C, 0xEF, 0xA0, 0xE0, 0x3B, 0x4D, 0xAE, 0x2A, 0xF5, 0xB0, 0xC8, 0xEB, 0xBB, 0x3C, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2B, 0x04, 0x7E, 0xBA, 0x77, 0xD6, 0x26, 0xE1, 0x69, 0x14, 0x63, 0x55, 0x21, 0x0C, 0x7D] r_con = [ 0x00000000, # Unused 0x01000000, # i = 1 0x02000000, 0x04000000, 0x08000000, 0x10000000, 0x20000000, 0x40000000, 0x80000000, 0x1b000000, 0x36000000 # i = 10 ] # ------------------------------------------------------ Parameters for AES-128Nb = 4Nk = 4Nr = 10 # --------------------------------------------------------------- Key expansion#Apply the s-box to each of the 4 bytes in appendixa worddef sub_word(input): output = 0 for i in range(4): input_byte = input & 0xFF output_byte = s_box[input_byte] output = output + (output_byte << (8 * i)) input = input >> 8 return output #Rotate a word, making a cyclic permutationdef rot_word(input): return ((input << 8) + (input >> 24)) % (1 << 32) # Perform key expansion on the provided key using the value of Nk (above)def expand_key(key): # Split key into bytes k = [key[i] for i in range(16)] # Expand key, as per FIPS 197 section 5.2 w = [0 for i in range(Nb * (Nr+1))] for i in range(Nk): w[i] = ((k[4*i] << 24) + (k[4*i + 1] << 16) + (k[4*i + 2] << 8) + (k[4*i + 3] << 0)) for i in range(Nk, Nb*(Nr+1)): temp = w[i- Show 1] if (i % Nk == 0): temp = sub_word(rot_word(temp)) ^ r_con[i/Nk] w[i] = w[i-Nk] ^ temp return w # -------------------------------------------------------------- Cipher methods# Find the polynomial x * b mod m, as defined in FIPS 197 section 4.2.1def xtime(b): b = (b << 1) if (b & (1 << 8)): b = b ^ 0x1B return b & 0xFF # XOR each column of the state with the round keydef add_round_key(state, round_key): for x in range(4): w_in = ((state[0][x] << 24) + (state[1][x] << 16) + (state[2][x] << 8) + (state[3][x] << 0)) w_out = w_in ^ round_key[x] for y in range(4): state[y][x] = (w_out >> (24 - 8*y)) & 0xFF return state # Use the AES s-box on each byte of the current state.def sub_bytes(state): state = [[s_box[s_xy] for s_xy in s_y] for s_y in state] return state # Cyclically shift each row of the state matrix def shift_rows(state): output plots= [[0 for x in range(4)] for y in range(4)] for y in range(4): for x in range(4): output[y][x] = state[y][(x+y) % 4] return output # Mix each column of the state matrix def mix_columns(state): output = [[0 for x in range(4)] for y in range(4)] for x in range(4): for y in range(4): output[y][x] = (xtime(state[y][x]) ^ xtime(state[(y+1)%4][x]) ^ state[(y+1)%4][x] ^ state[(y+2)%4][x] ^ state[(y+3)%4][x]) return output # Convert the 4x4 state matrix into a single bytearraydef stateToBytearray(arr): ret = [arr[y][x] for x in range(4) for y in range(4)] return bytearray(ret) # Convert a bytearray into a 4x4 state matrixdef bytearrayToState(input): arr = [[0 for x in range(4)] for y in range(4)] for x in range(4): for y in range(4): i = y + 4*x print input[i] arr[y][x] = int(input[i]) return arr # Perform the AES-128 cipher routinedef cipher(input, w): # Build an array of the states ret = [] # Split input into bytes state = bytearrayToState(input) ret.append(stateToBytearray(state)) # Cipher state = add_round_key(state, w[0:Nb]) for round in range(1, Nr): state = sub_bytes(state) state = shift_rows(state) state = mix_columns(state) state = add_round_key(state, w[round*Nb:(round+1)*Nb]) ret.append(stateToBytearray(state)) state = sub_bytes(state) state = shift_rows(state) state = add_round_key(state, w[Nr*Nb:(Nr+1)*Nb]) ret.append(stateToBytearray(state)) return ret # Encrypt a 128 bit input with a 128 bit key.def encrypt(input, key): w = expand_key(key) return cipher(input, w) def main(): test_values = [ ('5C692F9103B2302914D7E555E4DCEE49', # Plaintext '2b7e151628aed2a6abf7158809cf4f3c', # Key '9b1202b925e0cb4967c486a69ede3133'), # Glitched ciphertext ('5C692F9103B2302914D7E555E4DCEE49', '2b7e151628aed2a6abf7158809cf4f3c', 'cd9543adaa5bcbc558e7d4944b5230f7'), ('5C692F9103B2302914D7E555E4DCEE49', '2b7e151628aed2a6abf7158809cf4f3c', '40581e3c073aaace9b9785fc258bac41'), ] for test in test_values: # Encrypt the plaintext to get the expected states pt = bytearray.fromhex(test[0]) k = bytearray.fromhex(test[1]) state = encrypt(pt, k) # Decrypt the glitched ciphertext ct = bytearray.fromhex(test[2]) obj = AES.new(str(k), AES.MODE_ECB) pt2 = obj.decrypt(str(ct)) # Re-encrypt the glitched plaintext to get the actual states pt2 = bytearray.fromhex(binascii.hexlify(pt2)) state2 = encrypt(pt2, k) print "States: " for i in range(Nr+1): print i print binascii.hexlify(state[i]) print binascii.hexlify(state2[i]) # Calculate the differences hd = [] for i in range(Nr+1): hd_i = sum([bin(s1 ^ s2).count("1") for (s1, s2) in zip(state[i], state2[i])]) hd.append(hd_i) print hd plt.plot(hd) plt.grid() plt.show() if __name__ == "__main__": main()</syntaxhighlight></div></div>
{{Template:Tutorials}}
[[Category:Tutorials]]