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:

  1. "HWSec" (a 5 bytes magic number)
  2. N, the number of traces in the file (a 4 bytes unsigned integer)
  3. L, the number of points per trace (a 4 bytes unsigned integer)
  4. K, the 64 bits secret key (a 8 bytes unsigned integer)
  5. 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:

  1. number of traces in context
  2. number of points per trace
  3. the secret key
  4. the plaintexts
  5. ciphertexts
  6. power traces

Attention

  1. 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.
  2. 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.
  3. 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:
HWSECMAGICNUMBER = 'HWSec'
class trContext:
 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

trContext(filename, max)
 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)

def trim(self, first_index, length):
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

def select(self, first_trace, n):
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.

def shrink(self, chunk_size):
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.

def dump(self, filename):
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.

def plot(prefix, i, t):
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.