#! /usr/bin/env python2 import tr_pcc import sys import argparse import des import traces # The P permutation table, as in the standard. The first entry (16) is the # position of the first (leftmost) bit of the result in the input 32 bits word. # Used to convert target bit index into SBox index (just for printed summary # after attack completion). p_table = [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25] def hamming_weight (v): v = v - ((v>>1) & 0x5555555555555555) v = (v & 0x3333333333333333) + ((v>>2) & 0x3333333333333333) return (((v + (v>>4) & 0xF0F0F0F0F0F0F0F) * 0x101010101010101) >> 56) & 0xFF def hamming_distance(n1,n2): return hamming_weight(n1^n2) def main (): # ************************************************************************ # * Before doing anything else, check the correctness of the DES library * # ************************************************************************ if not des.check (): sys.exit ("DES functional test failed") # ************************************* # * Check arguments and read datafile * # ************************************* argparser = argparse.ArgumentParser(description="Apply P. Kocher's DPA algorithm based on decision function") argparser.add_argument("datafile", metavar='FILE', help='name of the traces file in HWSec format. (e.g. pa.dat)') argparser.add_argument("n", metavar='N', type=int, help='number of acquisitions to use') argparser.add_argument("target_bit", metavar='[B]', type=int, nargs='?', default=1, help='index of target bit in L15 (1 to 32, as in DES standard, default: 1)') args = argparser.parse_args() if args.n < 1: # If invalid number of acquisitions. sys.exit ("Invalid number of acquisitions: %d (shall be greater than 1)" % args.n) if args.target_bit < 1 or args.target_bit > 32: # If invalid target bit index. sys.exit ("Invalid target bit index: %d (shall be between 1 and 32 included)" % args.target_bit) # Compute index of corresponding SBox target_sbox = (p_table[args.target_bit - 1] - 1) / 4 + 1 # Read power traces and ciphertexts. n is the number of acquisitions to use. ctx = read_datafile (args.datafile, args.n) print >> sys.stderr, 'Hey' print >> sys.stderr, 'Hope you have had a nice day ! :)' # *************************************************************** dpa_attack (ctx) #print >> sys.stderr, "target bit %d" % args.target_bit #dpa_attack(ctx, 1) # ******************************************************************************* # * Print the 64 DPA traces in a data file named dpa.dat. Print corresponding * # * gnuplot commands in a command file named dpa.cmd. All DPA traces are * # * plotted in blue but the one corresponding to the best guess which is * # * plotted in red with the title "Trace X (0xY)" where X and Y are the decimal * # * and heaxdecimal forms of the 6 bits best guess. * # ******************************************************************************* # Plot DPA traces in dpa.dat, gnuplot commands in dpa.cmd # ***************** # * Print summary * # ***************** #print >> sys.stderr, "Target bit: %d" % args.target_bit #print >> sys.stderr, "Target SBox: %d" % target_sbox #print >> sys.stderr, "Best guess: %d (0x%02x)" % (best_guess, best_guess) #print >> sys.stderr, "Maximum of DPA trace: %e" % best_max #print >> sys.stderr, "Index of maximum in DPA trace: %d" % best_idx #print >> sys.stderr, "DPA traces stored in file 'dpa.dat'. In order to plot them, type:" #print >> sys.stderr, "$ gnuplot -persist dpa.cmd" # ************************ # * Print last round key * # ************************ # A function to allocate cipher texts and power traces, read the # datafile and store its content in allocated context. def read_datafile (name, n): ctx = traces.trContext (name, n) if ctx.n != n: sys.exit ("Could not read %d acquisitions from traces file. Traces file contains %d acquisitions." % (n, ctx.n)); return ctx # Apply P. Kocher's DPA algorithm based on decision function. Computes 64 DPA # traces dpa[0..63], best_guess (6-bits subkey corresponding to highest DPA # peak), best_idx (index of sample with maximum value in best DPA trace) and # best_max (value of sample with maximum value in best DPA trace). def dpa_attack (ctx): pcc = [0]*64 sbomask = 0xf0000000 key=[] for sbox in range(8): print >> sys.stderr, 'Calculating subkey for Sbox number: ' + str(sbox) ctx_pcc = tr_pcc.pccContext(ctx.l,64) # pccContext object for i in range(ctx.n): ct = ctx.c[i] r16l16 = des.ip(ct) l16 = des.right_half(r16l16) r15 = l16 r16 = des.left_half(r16l16) er15 = des.e(l16) ctx_pcc.insert_x(ctx.t[i][:]) rk= 0x0 for j in range(64): l15 = r16^des.p(des.sboxes(er15^rk)) r14 = l15 ctx_pcc.insert_y(j, int(hamming_distance(r14 & des.p(sbomask), r15 & des.p(sbomask)))) rk+= 0x041041041041 ctx_pcc.consolidate() dpa = list([ctx_pcc.get_pcc(g) for g in range(64)]) maxs = [max(l) for l in dpa] idx = [l.index(m) for l,m in zip (dpa, maxs)] # Get best maximum best_max = max (maxs) best_guess = maxs.index (best_max) best_idx = idx[best_guess] sbomask = sbomask >> 4 key.append(best_guess) print >> sys.stderr, 'Subkey for Sbox nr. ' + str(sbox) +'are ' + str(hex(best_guess)) #traces.plot('sboxnr' + str(sbox), -1, dpa) # Plot the traces for each sbox # Format output res = '' for i in range(len(key)): res += bin(key[i])[2:].zfill(6) print hex(int(res,2)) def shit_i_dont_like (ctx, target_bit): t0 = [[0.0] * ctx.l] * 64 n0 = [0] * 64 t1 = [[0.0] * ctx.l] * 64 n1 = [0] * 64 for ct, t in zip (ctx.c, ctx.t): # For all acquisitions d = decision (ct, target_bit) # Compute the 64 decisions # For all guesses (64) for g in xrange (64): if d[g] == 0: # If decision on target bit is zero t0[g] = [a+b for a,b in zip (t0[g], t)] # Accumulate power trace in zero-set n0[g] += 1 # Increment traces count for zero-set else: # If decision on target bit is one t1[g] = [a+b for a,b in zip (t1[g], t)] # Accumulate power trace in one-set n1[g] += 1 # Increment traces count for one-set #DPA=[] #for g in range(64): #DPA[g] = sum(t1)/float(len(t1)) # Compute normalized one-set minus zero-set dpa = [[t1[g][i]/n1[g] - t0[g][i]/n0[g] for i in xrange (ctx.l)] for g in xrange (64)] # Get max and argmax for each guess maxs = [max(l) for l in dpa] idx = [l.index(m) for l,m in zip (dpa, maxs)] # Get best maximum best_max = max (maxs) best_guess = maxs.index (best_max) best_idx = idx[best_guess] return dpa, best_guess, best_max, best_idx if __name__ == "__main__": main ()