traces
The traces library, a software library dedicated to processing of power traces in relation with the Data Encryption Standard (DES).
:Date: 2009-07-29 :Authors: - Renaud Pacalet, renaud.pacalet@telecom-paris.fr
Traces are one dimension arrays of floating point numbers. They are stored in a binary file along with some parameters and their corresponding 64 bits (8 bytes) plaintexts and ciphertexts. The format of the trace files is the following:
- "HWSec" (a 5 bytes magic number)
- N, the number of traces in the file (a 4 bytes unsigned integer)
- L, the number of points per trace (a 4 bytes unsigned integer)
- K, the 64 bits secret key (a 8 bytes unsigned integer)
- N * (8 + 8 + L * 4) bytes corresponding to the N traces. The 8 bytes of the plaintext come first, then the 8 bytes of the cycphertext and the L floating point numbers (L * 4 bytes) of the trace.
A trace file containing 50 traces, 100 points each will thus be 20813 bytes long: 5 + 4 + 4 + 50 * (8 + 8 + 100 * 4).
Reading a trace file is done by instantiating a trContext object::
import traces ... ctx = traces.trContext ("MyTraceFile.hws", 1000)
where "MyTraceFile.hws" is a regular trace file in HWSec format and 1000 the maximum number of traces to read from it. Once initialized, the context contains every useful information:
- number of traces in context
- number of points per trace
- the secret key
- the plaintexts
- ciphertexts
- power traces
Attention
- Most functions of the traces library check their input parameters and issue warnings or errors when they carry invalid values. Warnings are printed on the standard error output. Errors are also printed on the standard error output and the program exits with a -1 exit status.
- The traces library uses a single data type to represent all the data of the DES standard: uint64_t. It is a 64 bits unsigned integer.
- DES data are always right aligned: when the data width is less than 64 bits, the meaningful bits are always the rightmost bits of the uint64_t.
1#!/usr/bin/env python3 2 3# MAIN-ONLY: DO NOT MODIFY THIS FILE 4 5"""The traces library, a software library dedicated to processing of power 6traces in relation with the Data Encryption Standard (DES). 7 8:Date: 2009-07-29 9:Authors: 10 - Renaud Pacalet, renaud.pacalet@telecom-paris.fr 11 12Traces are one dimension arrays of floating point numbers. They are stored in 13a binary file along with some parameters and their corresponding 64 bits (8 14bytes) plaintexts and ciphertexts. The format of the trace files is the 15following: 16 171. "HWSec" (a 5 bytes magic number) 182. N, the number of traces in the file (a 4 bytes unsigned integer) 193. L, the number of points per trace (a 4 bytes unsigned integer) 204. K, the 64 bits secret key (a 8 bytes unsigned integer) 215. N * (8 + 8 + L * 4) bytes corresponding to the N traces. The 8 bytes of 22 the plaintext come first, then the 8 bytes of the cycphertext and the L 23 floating point numbers (L * 4 bytes) of the trace. 24 25A trace file containing 50 traces, 100 points each will thus be 20813 bytes 26long: 5 + 4 + 4 + 50 * (8 + 8 + 100 * 4). 27 28Reading a trace file is done by instantiating a `trContext` object:: 29 30import traces 31... 32ctx = traces.trContext ("MyTraceFile.hws", 1000) 33 34where "MyTraceFile.hws" is a regular trace file in HWSec format and 1000 the 35maximum number of traces to read from it. Once initialized, the context 36contains every useful information: 37 381. number of traces in context 392. number of points per trace 403. the secret key 414. the plaintexts 425. ciphertexts 436. power traces 44 45Attention 46========= 47 481. Most functions of the traces library check their input parameters and 49 issue warnings or errors when they carry invalid values. Warnings are 50 printed on the standard error output. Errors are also printed on the 51 standard error output and the program exits with a -1 exit status. 522. The traces library uses a single data type to represent all the data of 53 the DES standard: **uint64_t**. It is a 64 bits unsigned integer. 543. DES data are always right aligned: when the data width is less than 64 55 bits, the meaningful bits are always the rightmost bits of the 56 **uint64_t**. 57 58""" 59 60import struct 61 62HWSECMAGICNUMBER = "HWSec" 63 64class trContext: 65 """The data structure used to manage a set of traces 66 67 Attributes: 68 n (int): Number of traces in the context 69 l (int): Number of points per trace 70 k (long): Secret key 71 p (List[long]): Plaintexts 72 c (List[long]): Ciphertexts 73 t (List[List[float]]): Power traces 74 75 """ 76 77 def __init__ (self, filename, max): 78 """Reads the trace file `filename` and initializes the context. 79 80 Args: 81 filename (str): Name of the trace file in HWSec format 82 max (int): Maximum number of traces to read from the file (read all traces if 0) 83 84 """ 85 86 if not isinstance (max, int) or max < 0: 87 raise ValueError('Invalid maximum number of traces: ' + str(max)) 88 89 with open(str(filename), 'rb') as f: 90 91 header = f.read (len(HWSECMAGICNUMBER) + 4 + 4 + 8) 92 if len(header) != len(HWSECMAGICNUMBER) + 4 + 4 + 8: 93 raise ValueError ('wrong header; is this a real HWSec trace file?') 94 95 magic, self.n, self.l, self.k = struct.unpack ("<" + str(len(HWSECMAGICNUMBER)) + "siiQ", header) 96 magic = bytes.decode(magic) 97 if magic != HWSECMAGICNUMBER: 98 raise ValueError ('wrong magic number; is this a real HWSec trace file?' % magic) 99 100 if max == 0: 101 max = self.n 102 elif self.n >= max: 103 self.n = max 104 else: 105 raise ValueError ('not enough traces in trace file (%d < %d)' % (self.n, max)) 106 107 self.t = [] 108 self.c = [] 109 self.p = [] 110 for i in range (self.n): 111 tr = f.read (8 + 8 + 4 * self.l) 112 if len(tr) != 8 + 8 + 4 * self.l: 113 raise ValueError ('cannot read trace %d; is this a real HWSec trace file?' % i) 114 115 l = struct.unpack ("<QQ" + str(self.l) + "f", tr) 116 self.p.append (l[0]) 117 self.c.append (l[1]) 118 self.t.append (list(l[2:])) 119 120 def trim (self, first_index, length): 121 """Trim all the traces of the context, keeping only `length` 122 points, starting from point number `first_index` 123 124 Args: 125 first_index (int): The index of first point to keep 126 length (int): The number of points to keep 127 128 """ 129 130 if not isinstance (first_index, int) or not isinstance (length, int): 131 raise TypeError('parameters first_index and length should be numbers') 132 133 first = first_index % self.l 134 if length < 0 or first + length > self.l: 135 raise ValueError('Invalid parameters value: first_index=%d, length=%d (traces length=%d)' % (first_index, length, self.l)) 136 137 self.t = [tt[first:first+length] for tt in self.t] 138 self.l = length 139 140 def select (self, first_trace, n): 141 """Selects `n` traces of the context, starting from trace number `first_trace`, 142 and discards the others 143 144 Args: 145 first_trace (int): Index of first trace to keep. 146 n (int): Number of traces to keep. 147 148 """ 149 150 if not isinstance (first_trace, int) or not isinstance (n, int): 151 raise TypeError('parameters first_trace and n should be numbers') 152 153 first = first_trace % self.n 154 if n < 0 or first + n > self.n: 155 raise ValueError('Invalid parameters value: first_trace=%d, n=%d (number of traces=%d)' % (first_trace, n, self.n)) 156 157 self.t = self.t[first:first+n] 158 self.p = self.p[first:first+n] 159 self.c = self.c[first:first+n] 160 self.n = n 161 162 def shrink (self, chunk_size): 163 """Shrink all the traces of the context, by replacing each chunk 164 of `chunk_size` points by their sum. If incomplete, the last chunk is discarded 165 166 Args: 167 chunk_size (int): Number of points per chunk. 168 169 """ 170 171 if not isinstance (chunk_size, int): 172 raise TypeError('parameter chunk_size should be a number') 173 if chunk_size < 1 or chunk_size > self.l: 174 raise ValueError('Invalid parameters value: chunk_size=%d (traces length=%d)' % (chunk_size, self.l)) 175 176 self.l = self.l / chunk_size 177 self.t = [[sum(tt[i*chunk_size:(i+1)*chunk_size]) for i in range(self.l)] for tt in self.t] 178 179 def dump (self, filename): 180 """Writes the context in a HWSec trace file `filename` 181 182 Args: 183 filename (str): Name of output HWSec trace file. 184 185 """ 186 187 with open(str(filename), 'wb') as f: 188 f.write (struct.pack ("<" + str(len(HWSECMAGICNUMBER)) + "siiQ", HWSECMAGICNUMBER, self.n, self.l, self.k)) 189 190 for p, c, t in zip (self.p, self.c, self.t): 191 f.write (struct.pack ("<QQ" + str(self.l) + "f", tuple([p, c] + t))) 192 193 194def plot (prefix, i, t): 195 """Create two gnuplot files for a set of traces. `prefix`.dat is the data file 196 containing the `n` traces `t[0]` ... `t[n-1]` in gnuplot format. `prefix`.cmd 197 is a gnuplot command file that can be used to plot them with the command:: 198 199 $ gnuplot prefix.cmd 200 201 If the parameter `i` is the index of one of the traces (0 <= `i` <= `n`-1), the 202 corresponding trace will be plotted in red and with the title "Trace X (0xY)" where 203 X and Y are the decimal and hexadecimal forms of `i`. All the other traces are 204 plotted in blue without title. 205 206 Args: 207 prefix (str): The prefix of the two file names. The data file name is prefix.dat 208 and the gnuplot command file name is prefix.cmd 209 i (int): The index of a trace to plot in red. None if not in the 0..n-1 range. 210 t (List[List[float]]): The traces. 211 212 """ 213 214 if not isinstance (i, int): 215 raise TypeError('parameter i should be a number') 216 217 n = len(t) 218 219 title = None 220 if i >= 0 and i < n: 221 title = "Trace #%d (0x%x)" % (i, i) 222 else: 223 i = -1 224 225 with open ("%s.cmd" % str(prefix), 'w') as fpc: 226 fname = "%s.dat" % str(prefix) 227 with open (fname, 'w') as fpd: 228 fpc.write ("set terminal pngcairo size 1280,720 enhanced font 'Verdana,10'\n") 229 fpc.write ("set output '%s.png'\n" % str(prefix)) 230 fpc.write ("plot \\\n") 231 for ii, tt in enumerate (t): 232 for p in tt: 233 fpd.write ("%e\n" % p) 234 fpd.write ("\n\n") 235 if ii != i: 236 fpc.write ("'%s' index %d notitle with lines linecolor 3" % (fname, ii)) 237 if i != -1 or ii != n-1: 238 fpc.write (",\\\n") 239 if i != -1: 240 fpc.write ("'%s' index %d title '%s' with lines linecolor 1" % (fname, i, title)) 241 fpc.write ("\n") 242 243# vim: set tabstop=8 softtabstop=4 shiftwidth=4 expandtab textwidth=0:
65class trContext: 66 """The data structure used to manage a set of traces 67 68 Attributes: 69 n (int): Number of traces in the context 70 l (int): Number of points per trace 71 k (long): Secret key 72 p (List[long]): Plaintexts 73 c (List[long]): Ciphertexts 74 t (List[List[float]]): Power traces 75 76 """ 77 78 def __init__ (self, filename, max): 79 """Reads the trace file `filename` and initializes the context. 80 81 Args: 82 filename (str): Name of the trace file in HWSec format 83 max (int): Maximum number of traces to read from the file (read all traces if 0) 84 85 """ 86 87 if not isinstance (max, int) or max < 0: 88 raise ValueError('Invalid maximum number of traces: ' + str(max)) 89 90 with open(str(filename), 'rb') as f: 91 92 header = f.read (len(HWSECMAGICNUMBER) + 4 + 4 + 8) 93 if len(header) != len(HWSECMAGICNUMBER) + 4 + 4 + 8: 94 raise ValueError ('wrong header; is this a real HWSec trace file?') 95 96 magic, self.n, self.l, self.k = struct.unpack ("<" + str(len(HWSECMAGICNUMBER)) + "siiQ", header) 97 magic = bytes.decode(magic) 98 if magic != HWSECMAGICNUMBER: 99 raise ValueError ('wrong magic number; is this a real HWSec trace file?' % magic) 100 101 if max == 0: 102 max = self.n 103 elif self.n >= max: 104 self.n = max 105 else: 106 raise ValueError ('not enough traces in trace file (%d < %d)' % (self.n, max)) 107 108 self.t = [] 109 self.c = [] 110 self.p = [] 111 for i in range (self.n): 112 tr = f.read (8 + 8 + 4 * self.l) 113 if len(tr) != 8 + 8 + 4 * self.l: 114 raise ValueError ('cannot read trace %d; is this a real HWSec trace file?' % i) 115 116 l = struct.unpack ("<QQ" + str(self.l) + "f", tr) 117 self.p.append (l[0]) 118 self.c.append (l[1]) 119 self.t.append (list(l[2:])) 120 121 def trim (self, first_index, length): 122 """Trim all the traces of the context, keeping only `length` 123 points, starting from point number `first_index` 124 125 Args: 126 first_index (int): The index of first point to keep 127 length (int): The number of points to keep 128 129 """ 130 131 if not isinstance (first_index, int) or not isinstance (length, int): 132 raise TypeError('parameters first_index and length should be numbers') 133 134 first = first_index % self.l 135 if length < 0 or first + length > self.l: 136 raise ValueError('Invalid parameters value: first_index=%d, length=%d (traces length=%d)' % (first_index, length, self.l)) 137 138 self.t = [tt[first:first+length] for tt in self.t] 139 self.l = length 140 141 def select (self, first_trace, n): 142 """Selects `n` traces of the context, starting from trace number `first_trace`, 143 and discards the others 144 145 Args: 146 first_trace (int): Index of first trace to keep. 147 n (int): Number of traces to keep. 148 149 """ 150 151 if not isinstance (first_trace, int) or not isinstance (n, int): 152 raise TypeError('parameters first_trace and n should be numbers') 153 154 first = first_trace % self.n 155 if n < 0 or first + n > self.n: 156 raise ValueError('Invalid parameters value: first_trace=%d, n=%d (number of traces=%d)' % (first_trace, n, self.n)) 157 158 self.t = self.t[first:first+n] 159 self.p = self.p[first:first+n] 160 self.c = self.c[first:first+n] 161 self.n = n 162 163 def shrink (self, chunk_size): 164 """Shrink all the traces of the context, by replacing each chunk 165 of `chunk_size` points by their sum. If incomplete, the last chunk is discarded 166 167 Args: 168 chunk_size (int): Number of points per chunk. 169 170 """ 171 172 if not isinstance (chunk_size, int): 173 raise TypeError('parameter chunk_size should be a number') 174 if chunk_size < 1 or chunk_size > self.l: 175 raise ValueError('Invalid parameters value: chunk_size=%d (traces length=%d)' % (chunk_size, self.l)) 176 177 self.l = self.l / chunk_size 178 self.t = [[sum(tt[i*chunk_size:(i+1)*chunk_size]) for i in range(self.l)] for tt in self.t] 179 180 def dump (self, filename): 181 """Writes the context in a HWSec trace file `filename` 182 183 Args: 184 filename (str): Name of output HWSec trace file. 185 186 """ 187 188 with open(str(filename), 'wb') as f: 189 f.write (struct.pack ("<" + str(len(HWSECMAGICNUMBER)) + "siiQ", HWSECMAGICNUMBER, self.n, self.l, self.k)) 190 191 for p, c, t in zip (self.p, self.c, self.t): 192 f.write (struct.pack ("<QQ" + str(self.l) + "f", tuple([p, c] + t)))
The data structure used to manage a set of traces
Attributes: n (int): Number of traces in the context l (int): Number of points per trace k (long): Secret key p (List[long]): Plaintexts c (List[long]): Ciphertexts t (List[List[float]]): Power traces
78 def __init__ (self, filename, max): 79 """Reads the trace file `filename` and initializes the context. 80 81 Args: 82 filename (str): Name of the trace file in HWSec format 83 max (int): Maximum number of traces to read from the file (read all traces if 0) 84 85 """ 86 87 if not isinstance (max, int) or max < 0: 88 raise ValueError('Invalid maximum number of traces: ' + str(max)) 89 90 with open(str(filename), 'rb') as f: 91 92 header = f.read (len(HWSECMAGICNUMBER) + 4 + 4 + 8) 93 if len(header) != len(HWSECMAGICNUMBER) + 4 + 4 + 8: 94 raise ValueError ('wrong header; is this a real HWSec trace file?') 95 96 magic, self.n, self.l, self.k = struct.unpack ("<" + str(len(HWSECMAGICNUMBER)) + "siiQ", header) 97 magic = bytes.decode(magic) 98 if magic != HWSECMAGICNUMBER: 99 raise ValueError ('wrong magic number; is this a real HWSec trace file?' % magic) 100 101 if max == 0: 102 max = self.n 103 elif self.n >= max: 104 self.n = max 105 else: 106 raise ValueError ('not enough traces in trace file (%d < %d)' % (self.n, max)) 107 108 self.t = [] 109 self.c = [] 110 self.p = [] 111 for i in range (self.n): 112 tr = f.read (8 + 8 + 4 * self.l) 113 if len(tr) != 8 + 8 + 4 * self.l: 114 raise ValueError ('cannot read trace %d; is this a real HWSec trace file?' % i) 115 116 l = struct.unpack ("<QQ" + str(self.l) + "f", tr) 117 self.p.append (l[0]) 118 self.c.append (l[1]) 119 self.t.append (list(l[2:]))
Reads the trace file filename and initializes the context.
Args: filename (str): Name of the trace file in HWSec format max (int): Maximum number of traces to read from the file (read all traces if 0)
121 def trim (self, first_index, length): 122 """Trim all the traces of the context, keeping only `length` 123 points, starting from point number `first_index` 124 125 Args: 126 first_index (int): The index of first point to keep 127 length (int): The number of points to keep 128 129 """ 130 131 if not isinstance (first_index, int) or not isinstance (length, int): 132 raise TypeError('parameters first_index and length should be numbers') 133 134 first = first_index % self.l 135 if length < 0 or first + length > self.l: 136 raise ValueError('Invalid parameters value: first_index=%d, length=%d (traces length=%d)' % (first_index, length, self.l)) 137 138 self.t = [tt[first:first+length] for tt in self.t] 139 self.l = length
Trim all the traces of the context, keeping only length
points, starting from point number first_index
Args: first_index (int): The index of first point to keep length (int): The number of points to keep
141 def select (self, first_trace, n): 142 """Selects `n` traces of the context, starting from trace number `first_trace`, 143 and discards the others 144 145 Args: 146 first_trace (int): Index of first trace to keep. 147 n (int): Number of traces to keep. 148 149 """ 150 151 if not isinstance (first_trace, int) or not isinstance (n, int): 152 raise TypeError('parameters first_trace and n should be numbers') 153 154 first = first_trace % self.n 155 if n < 0 or first + n > self.n: 156 raise ValueError('Invalid parameters value: first_trace=%d, n=%d (number of traces=%d)' % (first_trace, n, self.n)) 157 158 self.t = self.t[first:first+n] 159 self.p = self.p[first:first+n] 160 self.c = self.c[first:first+n] 161 self.n = n
Selects n traces of the context, starting from trace number first_trace,
and discards the others
Args: first_trace (int): Index of first trace to keep. n (int): Number of traces to keep.
163 def shrink (self, chunk_size): 164 """Shrink all the traces of the context, by replacing each chunk 165 of `chunk_size` points by their sum. If incomplete, the last chunk is discarded 166 167 Args: 168 chunk_size (int): Number of points per chunk. 169 170 """ 171 172 if not isinstance (chunk_size, int): 173 raise TypeError('parameter chunk_size should be a number') 174 if chunk_size < 1 or chunk_size > self.l: 175 raise ValueError('Invalid parameters value: chunk_size=%d (traces length=%d)' % (chunk_size, self.l)) 176 177 self.l = self.l / chunk_size 178 self.t = [[sum(tt[i*chunk_size:(i+1)*chunk_size]) for i in range(self.l)] for tt in self.t]
Shrink all the traces of the context, by replacing each chunk
of chunk_size points by their sum. If incomplete, the last chunk is discarded
Args: chunk_size (int): Number of points per chunk.
180 def dump (self, filename): 181 """Writes the context in a HWSec trace file `filename` 182 183 Args: 184 filename (str): Name of output HWSec trace file. 185 186 """ 187 188 with open(str(filename), 'wb') as f: 189 f.write (struct.pack ("<" + str(len(HWSECMAGICNUMBER)) + "siiQ", HWSECMAGICNUMBER, self.n, self.l, self.k)) 190 191 for p, c, t in zip (self.p, self.c, self.t): 192 f.write (struct.pack ("<QQ" + str(self.l) + "f", tuple([p, c] + t)))
Writes the context in a HWSec trace file filename
Args: filename (str): Name of output HWSec trace file.
195def plot (prefix, i, t): 196 """Create two gnuplot files for a set of traces. `prefix`.dat is the data file 197 containing the `n` traces `t[0]` ... `t[n-1]` in gnuplot format. `prefix`.cmd 198 is a gnuplot command file that can be used to plot them with the command:: 199 200 $ gnuplot prefix.cmd 201 202 If the parameter `i` is the index of one of the traces (0 <= `i` <= `n`-1), the 203 corresponding trace will be plotted in red and with the title "Trace X (0xY)" where 204 X and Y are the decimal and hexadecimal forms of `i`. All the other traces are 205 plotted in blue without title. 206 207 Args: 208 prefix (str): The prefix of the two file names. The data file name is prefix.dat 209 and the gnuplot command file name is prefix.cmd 210 i (int): The index of a trace to plot in red. None if not in the 0..n-1 range. 211 t (List[List[float]]): The traces. 212 213 """ 214 215 if not isinstance (i, int): 216 raise TypeError('parameter i should be a number') 217 218 n = len(t) 219 220 title = None 221 if i >= 0 and i < n: 222 title = "Trace #%d (0x%x)" % (i, i) 223 else: 224 i = -1 225 226 with open ("%s.cmd" % str(prefix), 'w') as fpc: 227 fname = "%s.dat" % str(prefix) 228 with open (fname, 'w') as fpd: 229 fpc.write ("set terminal pngcairo size 1280,720 enhanced font 'Verdana,10'\n") 230 fpc.write ("set output '%s.png'\n" % str(prefix)) 231 fpc.write ("plot \\\n") 232 for ii, tt in enumerate (t): 233 for p in tt: 234 fpd.write ("%e\n" % p) 235 fpd.write ("\n\n") 236 if ii != i: 237 fpc.write ("'%s' index %d notitle with lines linecolor 3" % (fname, ii)) 238 if i != -1 or ii != n-1: 239 fpc.write (",\\\n") 240 if i != -1: 241 fpc.write ("'%s' index %d title '%s' with lines linecolor 1" % (fname, i, title)) 242 fpc.write ("\n")
Create two gnuplot files for a set of traces. prefix.dat is the data file
containing the n traces t[0] ... t[n-1] in gnuplot format. prefix.cmd
is a gnuplot command file that can be used to plot them with the command::
$ gnuplot prefix.cmd
If the parameter i is the index of one of the traces (0 <= i <= n-1), the
corresponding trace will be plotted in red and with the title "Trace X (0xY)" where
X and Y are the decimal and hexadecimal forms of i. All the other traces are
plotted in blue without title.
Args: prefix (str): The prefix of the two file names. The data file name is prefix.dat and the gnuplot command file name is prefix.cmd i (int): The index of a trace to plot in red. None if not in the 0..n-1 range. t (List[List[float]]): The traces.