1# -*- coding: utf-8 -*-
2
3#
4# aes.py: implements AES - Advanced Encryption Standard
5# from the SlowAES project, http://code.google.com/p/slowaes/
6#
7# Copyright (c) 2008    Josh Davis ( http://www.josh-davis.org ),
8#           Alex Martelli ( http://www.aleax.it )
9#
10# Ported from C code written by Laurent Haan
11# ( http://www.progressive-coding.com )
12#
13# Licensed under the Apache License, Version 2.0
14# http://www.apache.org/licenses/
15#
16
17#
18# Ported to Python3
19#
20# Copyright (c) 2011 - 2021 Detlev Offenbach <detlev@die-offenbachs.de>
21#
22
23"""
24Module implementing classes for encryption according
25Advanced Encryption Standard.
26"""
27
28import os
29import math
30
31
32def append_PKCS7_padding(b):
33    """
34    Function to pad the given data to a multiple of 16-bytes by PKCS7 padding.
35
36    @param b data to be padded (bytes)
37    @return padded data (bytes)
38    """
39    numpads = 16 - (len(b) % 16)
40    return b + numpads * bytes(chr(numpads), encoding="ascii")
41
42
43def strip_PKCS7_padding(b):
44    """
45    Function to strip off PKCS7 padding.
46
47    @param b data to be stripped (bytes)
48    @return stripped data (bytes)
49    @exception ValueError data padding is invalid
50    """
51    if len(b) % 16 or not b:
52        raise ValueError(
53            "Data of len {0} can't be PCKS7-padded".format(len(b)))
54    numpads = b[-1]
55    if numpads > 16:
56        raise ValueError(
57            "Data ending with {0} can't be PCKS7-padded".format(b[-1]))
58    return b[:-numpads]
59
60
61class AES:
62    """
63    Class implementing the Advanced Encryption Standard algorithm.
64    """
65    # valid key sizes
66    KeySize = {
67        "SIZE_128": 16,
68        "SIZE_192": 24,
69        "SIZE_256": 32,
70    }
71
72    # Rijndael S-box
73    sbox = [0x63, 0x7c, 0x77, 0x7b, 0xf2, 0x6b, 0x6f, 0xc5, 0x30, 0x01, 0x67,
74            0x2b, 0xfe, 0xd7, 0xab, 0x76, 0xca, 0x82, 0xc9, 0x7d, 0xfa, 0x59,
75            0x47, 0xf0, 0xad, 0xd4, 0xa2, 0xaf, 0x9c, 0xa4, 0x72, 0xc0, 0xb7,
76            0xfd, 0x93, 0x26, 0x36, 0x3f, 0xf7, 0xcc, 0x34, 0xa5, 0xe5, 0xf1,
77            0x71, 0xd8, 0x31, 0x15, 0x04, 0xc7, 0x23, 0xc3, 0x18, 0x96, 0x05,
78            0x9a, 0x07, 0x12, 0x80, 0xe2, 0xeb, 0x27, 0xb2, 0x75, 0x09, 0x83,
79            0x2c, 0x1a, 0x1b, 0x6e, 0x5a, 0xa0, 0x52, 0x3b, 0xd6, 0xb3, 0x29,
80            0xe3, 0x2f, 0x84, 0x53, 0xd1, 0x00, 0xed, 0x20, 0xfc, 0xb1, 0x5b,
81            0x6a, 0xcb, 0xbe, 0x39, 0x4a, 0x4c, 0x58, 0xcf, 0xd0, 0xef, 0xaa,
82            0xfb, 0x43, 0x4d, 0x33, 0x85, 0x45, 0xf9, 0x02, 0x7f, 0x50, 0x3c,
83            0x9f, 0xa8, 0x51, 0xa3, 0x40, 0x8f, 0x92, 0x9d, 0x38, 0xf5, 0xbc,
84            0xb6, 0xda, 0x21, 0x10, 0xff, 0xf3, 0xd2, 0xcd, 0x0c, 0x13, 0xec,
85            0x5f, 0x97, 0x44, 0x17, 0xc4, 0xa7, 0x7e, 0x3d, 0x64, 0x5d, 0x19,
86            0x73, 0x60, 0x81, 0x4f, 0xdc, 0x22, 0x2a, 0x90, 0x88, 0x46, 0xee,
87            0xb8, 0x14, 0xde, 0x5e, 0x0b, 0xdb, 0xe0, 0x32, 0x3a, 0x0a, 0x49,
88            0x06, 0x24, 0x5c, 0xc2, 0xd3, 0xac, 0x62, 0x91, 0x95, 0xe4, 0x79,
89            0xe7, 0xc8, 0x37, 0x6d, 0x8d, 0xd5, 0x4e, 0xa9, 0x6c, 0x56, 0xf4,
90            0xea, 0x65, 0x7a, 0xae, 0x08, 0xba, 0x78, 0x25, 0x2e, 0x1c, 0xa6,
91            0xb4, 0xc6, 0xe8, 0xdd, 0x74, 0x1f, 0x4b, 0xbd, 0x8b, 0x8a, 0x70,
92            0x3e, 0xb5, 0x66, 0x48, 0x03, 0xf6, 0x0e, 0x61, 0x35, 0x57, 0xb9,
93            0x86, 0xc1, 0x1d, 0x9e, 0xe1, 0xf8, 0x98, 0x11, 0x69, 0xd9, 0x8e,
94            0x94, 0x9b, 0x1e, 0x87, 0xe9, 0xce, 0x55, 0x28, 0xdf, 0x8c, 0xa1,
95            0x89, 0x0d, 0xbf, 0xe6, 0x42, 0x68, 0x41, 0x99, 0x2d, 0x0f, 0xb0,
96            0x54, 0xbb, 0x16]
97
98    # Rijndael Inverted S-box
99    rsbox = [0x52, 0x09, 0x6a, 0xd5, 0x30, 0x36, 0xa5, 0x38, 0xbf, 0x40, 0xa3,
100             0x9e, 0x81, 0xf3, 0xd7, 0xfb, 0x7c, 0xe3, 0x39, 0x82, 0x9b, 0x2f,
101             0xff, 0x87, 0x34, 0x8e, 0x43, 0x44, 0xc4, 0xde, 0xe9, 0xcb, 0x54,
102             0x7b, 0x94, 0x32, 0xa6, 0xc2, 0x23, 0x3d, 0xee, 0x4c, 0x95, 0x0b,
103             0x42, 0xfa, 0xc3, 0x4e, 0x08, 0x2e, 0xa1, 0x66, 0x28, 0xd9, 0x24,
104             0xb2, 0x76, 0x5b, 0xa2, 0x49, 0x6d, 0x8b, 0xd1, 0x25, 0x72, 0xf8,
105             0xf6, 0x64, 0x86, 0x68, 0x98, 0x16, 0xd4, 0xa4, 0x5c, 0xcc, 0x5d,
106             0x65, 0xb6, 0x92, 0x6c, 0x70, 0x48, 0x50, 0xfd, 0xed, 0xb9, 0xda,
107             0x5e, 0x15, 0x46, 0x57, 0xa7, 0x8d, 0x9d, 0x84, 0x90, 0xd8, 0xab,
108             0x00, 0x8c, 0xbc, 0xd3, 0x0a, 0xf7, 0xe4, 0x58, 0x05, 0xb8, 0xb3,
109             0x45, 0x06, 0xd0, 0x2c, 0x1e, 0x8f, 0xca, 0x3f, 0x0f, 0x02, 0xc1,
110             0xaf, 0xbd, 0x03, 0x01, 0x13, 0x8a, 0x6b, 0x3a, 0x91, 0x11, 0x41,
111             0x4f, 0x67, 0xdc, 0xea, 0x97, 0xf2, 0xcf, 0xce, 0xf0, 0xb4, 0xe6,
112             0x73, 0x96, 0xac, 0x74, 0x22, 0xe7, 0xad, 0x35, 0x85, 0xe2, 0xf9,
113             0x37, 0xe8, 0x1c, 0x75, 0xdf, 0x6e, 0x47, 0xf1, 0x1a, 0x71, 0x1d,
114             0x29, 0xc5, 0x89, 0x6f, 0xb7, 0x62, 0x0e, 0xaa, 0x18, 0xbe, 0x1b,
115             0xfc, 0x56, 0x3e, 0x4b, 0xc6, 0xd2, 0x79, 0x20, 0x9a, 0xdb, 0xc0,
116             0xfe, 0x78, 0xcd, 0x5a, 0xf4, 0x1f, 0xdd, 0xa8, 0x33, 0x88, 0x07,
117             0xc7, 0x31, 0xb1, 0x12, 0x10, 0x59, 0x27, 0x80, 0xec, 0x5f, 0x60,
118             0x51, 0x7f, 0xa9, 0x19, 0xb5, 0x4a, 0x0d, 0x2d, 0xe5, 0x7a, 0x9f,
119             0x93, 0xc9, 0x9c, 0xef, 0xa0, 0xe0, 0x3b, 0x4d, 0xae, 0x2a, 0xf5,
120             0xb0, 0xc8, 0xeb, 0xbb, 0x3c, 0x83, 0x53, 0x99, 0x61, 0x17, 0x2b,
121             0x04, 0x7e, 0xba, 0x77, 0xd6, 0x26, 0xe1, 0x69, 0x14, 0x63, 0x55,
122             0x21, 0x0c, 0x7d]
123
124    # Rijndael Rcon
125    Rcon = [0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36,
126            0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97,
127            0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72,
128            0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66,
129            0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04,
130            0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d,
131            0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3,
132            0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61,
133            0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a,
134            0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40,
135            0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc,
136            0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5,
137            0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a,
138            0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d,
139            0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c,
140            0xd8, 0xab, 0x4d, 0x9a, 0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35,
141            0x6a, 0xd4, 0xb3, 0x7d, 0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4,
142            0xd3, 0xbd, 0x61, 0xc2, 0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc,
143            0x83, 0x1d, 0x3a, 0x74, 0xe8, 0xcb, 0x8d, 0x01, 0x02, 0x04, 0x08,
144            0x10, 0x20, 0x40, 0x80, 0x1b, 0x36, 0x6c, 0xd8, 0xab, 0x4d, 0x9a,
145            0x2f, 0x5e, 0xbc, 0x63, 0xc6, 0x97, 0x35, 0x6a, 0xd4, 0xb3, 0x7d,
146            0xfa, 0xef, 0xc5, 0x91, 0x39, 0x72, 0xe4, 0xd3, 0xbd, 0x61, 0xc2,
147            0x9f, 0x25, 0x4a, 0x94, 0x33, 0x66, 0xcc, 0x83, 0x1d, 0x3a, 0x74,
148            0xe8, 0xcb]
149
150    def __getSBoxValue(self, num):
151        """
152        Private method to retrieve a given S-Box value.
153
154        @param num position of the value (integer)
155        @return value of the S-Box (integer)
156        """
157        return self.sbox[num]
158
159    def __getSBoxInvert(self, num):
160        """
161        Private method to retrieve a given Inverted S-Box value.
162
163        @param num position of the value (integer)
164        @return value of the Inverted S-Box (integer)
165        """
166        return self.rsbox[num]
167
168    def __rotate(self, data):
169        """
170        Private method performing Rijndael's key schedule rotate operation.
171
172        Rotate the data word eight bits to the left: eg,
173        rotate(1d2c3a4f) == 2c3a4f1d.
174
175        @param data data of size 4 (bytearray)
176        @return rotated data (bytearray)
177        """
178        return data[1:] + data[:1]
179
180    def __getRconValue(self, num):
181        """
182        Private method to retrieve a given Rcon value.
183
184        @param num position of the value (integer)
185        @return Rcon value (integer)
186        """
187        return self.Rcon[num]
188
189    def __core(self, data, iteration):
190        """
191        Private method performing the key schedule core operation.
192
193        @param data data to operate on (bytearray)
194        @param iteration iteration counter (integer)
195        @return modified data (bytearray)
196        """
197        # rotate the 32-bit word 8 bits to the left
198        data = self.__rotate(data)
199        # apply S-Box substitution on all 4 parts of the 32-bit word
200        for i in range(4):
201            data[i] = self.__getSBoxValue(data[i])
202        # XOR the output of the rcon operation with i to the first part
203        # (leftmost) only
204        data[0] = data[0] ^ self.__getRconValue(iteration)
205        return data
206
207    def __expandKey(self, key, size, expandedKeySize):
208        """
209        Private method performing Rijndael's key expansion.
210
211        Expands a 128, 192 or 256 bit key into a 176, 208 or 240 bit key.
212
213        @param key key to be expanded (bytes or bytearray)
214        @param size size of the key in bytes (16, 24 or 32)
215        @param expandedKeySize size of the expanded key (integer)
216        @return expanded key (bytearray)
217        """
218        # current expanded keySize, in bytes
219        currentSize = 0
220        rconIteration = 1
221        expandedKey = bytearray(expandedKeySize)
222
223        # set the 16, 24, 32 bytes of the expanded key to the input key
224        for j in range(size):
225            expandedKey[j] = key[j]
226        currentSize += size
227
228        while currentSize < expandedKeySize:
229            # assign the previous 4 bytes to the temporary value t
230            t = expandedKey[currentSize - 4:currentSize]
231
232            # every 16, 24, 32 bytes we apply the core schedule to t
233            # and increment rconIteration afterwards
234            if currentSize % size == 0:
235                t = self.__core(t, rconIteration)
236                rconIteration += 1
237            # For 256-bit keys, we add an extra sbox to the calculation
238            if (
239                size == self.KeySize["SIZE_256"] and
240                ((currentSize % size) == 16)
241            ):
242                for ll in range(4):
243                    t[ll] = self.__getSBoxValue(t[ll])
244
245            # We XOR t with the four-byte block 16, 24, 32 bytes before the new
246            # expanded key. This becomes the next four bytes in the expanded
247            # key.
248            for m in range(4):
249                expandedKey[currentSize] = (
250                    expandedKey[currentSize - size] ^ t[m]
251                )
252                currentSize += 1        # __IGNORE_WARNING_Y113__
253
254        return expandedKey
255
256    def __addRoundKey(self, state, roundKey):
257        """
258        Private method to add (XORs) the round key to the state.
259
260        @param state state to be changed (bytearray)
261        @param roundKey key to be used for the modification (bytearray)
262        @return modified state (bytearray)
263        """
264        buf = state[:]
265        for i in range(16):
266            buf[i] ^= roundKey[i]
267        return buf
268
269    def __createRoundKey(self, expandedKey, roundKeyPointer):
270        """
271        Private method to create a round key.
272
273        @param expandedKey expanded key to be used (bytearray)
274        @param roundKeyPointer position within the expanded key (integer)
275        @return round key (bytearray)
276        """
277        roundKey = bytearray(16)
278        for i in range(4):
279            for j in range(4):
280                roundKey[j * 4 + i] = expandedKey[roundKeyPointer + i * 4 + j]
281        return roundKey
282
283    def __galois_multiplication(self, a, b):
284        """
285        Private method to perform a Galois multiplication of 8 bit characters
286        a and b.
287
288        @param a first factor (byte)
289        @param b second factor (byte)
290        @return result (byte)
291        """
292        p = 0
293        for _counter in range(8):
294            if b & 1:
295                p ^= a
296            hi_bit_set = a & 0x80
297            a <<= 1
298            # keep a 8 bit
299            a &= 0xFF
300            if hi_bit_set:
301                a ^= 0x1b
302            b >>= 1
303        return p
304
305    def __subBytes(self, state, isInv):
306        """
307        Private method to substitute all the values from the state with the
308        value in the SBox using the state value as index for the SBox.
309
310        @param state state to be worked on (bytearray)
311        @param isInv flag indicating an inverse operation (boolean)
312        @return modified state (bytearray)
313        """
314        state = state[:]
315        getter = self.__getSBoxInvert if isInv else self.__getSBoxValue
316        for i in range(16):
317            state[i] = getter(state[i])
318        return state
319
320    def __shiftRows(self, state, isInv):
321        """
322        Private method to iterate over the 4 rows and call __shiftRow() with
323        that row.
324
325        @param state state to be worked on (bytearray)
326        @param isInv flag indicating an inverse operation (boolean)
327        @return modified state (bytearray)
328        """
329        state = state[:]
330        for i in range(4):
331            state = self.__shiftRow(state, i * 4, i, isInv)
332        return state
333
334    def __shiftRow(self, state, statePointer, nbr, isInv):
335        """
336        Private method to shift the bytes of a row to the left.
337
338        @param state state to be worked on (bytearray)
339        @param statePointer index into the state (integer)
340        @param nbr number of positions to shift (integer)
341        @param isInv flag indicating an inverse operation (boolean)
342        @return modified state (bytearray)
343        """
344        state = state[:]
345        for _ in range(nbr):
346            if isInv:
347                state[statePointer:statePointer + 4] = (
348                    state[statePointer + 3:statePointer + 4] +
349                    state[statePointer:statePointer + 3]
350                )
351            else:
352                state[statePointer:statePointer + 4] = (
353                    state[statePointer + 1:statePointer + 4] +
354                    state[statePointer:statePointer + 1]
355                )
356        return state
357
358    def __mixColumns(self, state, isInv):
359        """
360        Private method to perform a galois multiplication of the 4x4 matrix.
361
362        @param state state to be worked on (bytearray)
363        @param isInv flag indicating an inverse operation (boolean)
364        @return modified state (bytearray)
365        """
366        state = state[:]
367        # iterate over the 4 columns
368        for i in range(4):
369            # construct one column by slicing over the 4 rows
370            column = state[i:i + 16:4]
371            # apply the __mixColumn on one column
372            column = self.__mixColumn(column, isInv)
373            # put the values back into the state
374            state[i:i + 16:4] = column
375
376        return state
377
378    # galois multiplication of 1 column of the 4x4 matrix
379    def __mixColumn(self, column, isInv):
380        """
381        Private method to perform a galois multiplication of 1 column the
382        4x4 matrix.
383
384        @param column column to be worked on (bytearray)
385        @param isInv flag indicating an inverse operation (boolean)
386        @return modified column (bytearray)
387        """
388        column = column[:]
389        mult = [14, 9, 13, 11] if isInv else [2, 1, 1, 3]
390        cpy = column[:]
391        g = self.__galois_multiplication
392
393        column[0] = (
394            g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^
395            g(cpy[2], mult[2]) ^ g(cpy[1], mult[3])
396        )
397        column[1] = (
398            g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^
399            g(cpy[3], mult[2]) ^ g(cpy[2], mult[3])
400        )
401        column[2] = (
402            g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^
403            g(cpy[0], mult[2]) ^ g(cpy[3], mult[3])
404        )
405        column[3] = (
406            g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^
407            g(cpy[1], mult[2]) ^ g(cpy[0], mult[3])
408        )
409        return column
410
411    def __aes_round(self, state, roundKey):
412        """
413        Private method to apply the 4 operations of the forward round in
414        sequence.
415
416        @param state state to be worked on (bytearray)
417        @param roundKey round key to be used (bytearray)
418        @return modified state (bytearray)
419        """
420        state = self.__subBytes(state, False)
421        state = self.__shiftRows(state, False)
422        state = self.__mixColumns(state, False)
423        state = self.__addRoundKey(state, roundKey)
424        return state
425
426    def __aes_invRound(self, state, roundKey):
427        """
428        Private method to apply the 4 operations of the inverse round in
429        sequence.
430
431        @param state state to be worked on (bytearray)
432        @param roundKey round key to be used (bytearray)
433        @return modified state (bytearray)
434        """
435        state = self.__shiftRows(state, True)
436        state = self.__subBytes(state, True)
437        state = self.__addRoundKey(state, roundKey)
438        state = self.__mixColumns(state, True)
439        return state
440
441    def __aes_main(self, state, expandedKey, nbrRounds):
442        """
443        Private method to do the AES encryption for one round.
444
445        Perform the initial operations, the standard round, and the
446        final operations of the forward AES, creating a round key for
447        each round.
448
449        @param state state to be worked on (bytearray)
450        @param expandedKey expanded key to be used (bytearray)
451        @param nbrRounds number of rounds to be done (integer)
452        @return modified state (bytearray)
453        """
454        state = self.__addRoundKey(
455            state, self.__createRoundKey(expandedKey, 0))
456        i = 1
457        while i < nbrRounds:
458            state = self.__aes_round(
459                state, self.__createRoundKey(expandedKey, 16 * i))
460            i += 1
461        state = self.__subBytes(state, False)
462        state = self.__shiftRows(state, False)
463        state = self.__addRoundKey(
464            state, self.__createRoundKey(expandedKey, 16 * nbrRounds))
465        return state
466
467    def __aes_invMain(self, state, expandedKey, nbrRounds):
468        """
469        Private method to do the inverse AES encryption for one round.
470
471        Perform the initial operations, the standard round, and the
472        final operations of the inverse AES, creating a round key for
473        each round.
474
475        @param state state to be worked on (bytearray)
476        @param expandedKey expanded key to be used (bytearray)
477        @param nbrRounds number of rounds to be done (integer)
478        @return modified state (bytearray)
479        """
480        state = self.__addRoundKey(
481            state, self.__createRoundKey(expandedKey, 16 * nbrRounds))
482        i = nbrRounds - 1
483        while i > 0:
484            state = self.__aes_invRound(
485                state, self.__createRoundKey(expandedKey, 16 * i))
486            i -= 1
487        state = self.__shiftRows(state, True)
488        state = self.__subBytes(state, True)
489        state = self.__addRoundKey(
490            state, self.__createRoundKey(expandedKey, 0))
491        return state
492
493    def encrypt(self, iput, key, size):
494        """
495        Public method to encrypt a 128 bit input block against the given key
496        of size specified.
497
498        @param iput input data (bytearray)
499        @param key key to be used (bytes or bytearray)
500        @param size key size (16, 24 or 32)
501        @return encrypted data (bytes)
502        @exception ValueError key size is invalid
503        """
504        if size not in self.KeySize.values():
505            raise ValueError("Wrong key size given ({0}).".format(size))
506
507        output = bytearray(16)
508        # the number of rounds
509        nbrRounds = 0
510        # the 128 bit block to encode
511        block = bytearray(16)
512        # set the number of rounds
513        if size == self.KeySize["SIZE_128"]:
514            nbrRounds = 10
515        elif size == self.KeySize["SIZE_192"]:
516            nbrRounds = 12
517        else:
518            nbrRounds = 14
519
520        # the expanded keySize
521        expandedKeySize = 16 * (nbrRounds + 1)
522
523        # Set the block values, for the block:
524        # a0,0 a0,1 a0,2 a0,3
525        # a1,0 a1,1 a1,2 a1,3
526        # a2,0 a2,1 a2,2 a2,3
527        # a3,0 a3,1 a3,2 a3,3
528        # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
529        #
530        # iterate over the columns
531        for i in range(4):
532            # iterate over the rows
533            for j in range(4):
534                block[i + j * 4] = iput[i * 4 + j]
535
536        # expand the key into an 176, 208, 240 bytes key
537        # the expanded key
538        expandedKey = self.__expandKey(key, size, expandedKeySize)
539
540        # encrypt the block using the expandedKey
541        block = self.__aes_main(block, expandedKey, nbrRounds)
542
543        # unmap the block again into the output
544        for kk in range(4):
545            # iterate over the rows
546            for ll in range(4):
547                output[kk * 4 + ll] = block[kk + ll * 4]
548        return bytes(output)
549
550    # decrypts a 128 bit input block against the given key of size specified
551    def decrypt(self, iput, key, size):
552        """
553        Public method to decrypt a 128 bit input block against the given key
554        of size specified.
555
556        @param iput input data (bytearray)
557        @param key key to be used (bytes or bytearray)
558        @param size key size (16, 24 or 32)
559        @return decrypted data (bytes)
560        @exception ValueError key size is invalid
561        """
562        if size not in self.KeySize.values():
563            raise ValueError("Wrong key size given ({0}).".format(size))
564
565        output = bytearray(16)
566        # the number of rounds
567        nbrRounds = 0
568        # the 128 bit block to decode
569        block = bytearray(16)
570        # set the number of rounds
571
572        if size == self.KeySize["SIZE_128"]:
573            nbrRounds = 10
574        elif size == self.KeySize["SIZE_192"]:
575            nbrRounds = 12
576        else:
577            nbrRounds = 14
578
579        # the expanded keySize
580        expandedKeySize = 16 * (nbrRounds + 1)
581
582        # Set the block values, for the block:
583        # a0,0 a0,1 a0,2 a0,3
584        # a1,0 a1,1 a1,2 a1,3
585        # a2,0 a2,1 a2,2 a2,3
586        # a3,0 a3,1 a3,2 a3,3
587        # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3
588
589        # iterate over the columns
590        for i in range(4):
591            # iterate over the rows
592            for j in range(4):
593                block[i + j * 4] = iput[i * 4 + j]
594        # expand the key into an 176, 208, 240 bytes key
595        expandedKey = self.__expandKey(key, size, expandedKeySize)
596        # decrypt the block using the expandedKey
597        block = self.__aes_invMain(block, expandedKey, nbrRounds)
598        # unmap the block again into the output
599        for kk in range(4):
600            # iterate over the rows
601            for ll in range(4):
602                output[kk * 4 + ll] = block[kk + ll * 4]
603        return output
604
605
606class AESModeOfOperation:
607    """
608    Class implementing the different AES mode of operations.
609    """
610    aes = AES()
611
612    # structure of supported modes of operation
613    ModeOfOperation = {
614        "OFB": 0,
615        "CFB": 1,
616        "CBC": 2,
617    }
618
619    def __extractBytes(self, inputData, start, end, mode):
620        """
621        Private method to extract a range of bytes from the input.
622
623        @param inputData input data (bytes)
624        @param start start index (integer)
625        @param end end index (integer)
626        @param mode mode of operation (0, 1, 2)
627        @return extracted bytes (bytearray)
628        """
629        if end - start > 16:
630            end = start + 16
631        ar = (bytearray(16) if mode == self.ModeOfOperation["CBC"]
632              else bytearray())
633
634        i = start
635        j = 0
636        while len(ar) < end - start:
637            ar.append(0)
638        while i < end:
639            ar[j] = inputData[i]
640            j += 1
641            i += 1
642        return ar
643
644    def encrypt(self, inputData, mode, key, size, IV):
645        """
646        Public method to perform the encryption operation.
647
648        @param inputData data to be encrypted (bytes)
649        @param mode mode of operation (0, 1 or 2)
650        @param key key to be used (bytes)
651        @param size length of the key (16, 24 or 32)
652        @param IV initialisation vector (bytearray)
653        @return tuple with mode of operation, length of the input data and
654            the encrypted data (integer, integer, bytes)
655        @exception ValueError key size is invalid or decrypted data is invalid
656        """
657        if len(key) % size:
658            raise ValueError("Illegal size ({0}) for key '{1}'.".format(
659                size, key))
660        if len(IV) % 16:
661            raise ValueError("IV is not a multiple of 16.")
662        # the AES input/output
663        iput = bytearray(16)
664        output = bytearray()
665        ciphertext = bytearray(16)
666        # the output cipher string
667        cipherOut = bytearray()
668        # char firstRound
669        firstRound = True
670        if inputData:
671            for j in range(int(math.ceil(float(len(inputData)) / 16))):
672                start = j * 16
673                end = j * 16 + 16
674                if end > len(inputData):
675                    end = len(inputData)
676                plaintext = self.__extractBytes(inputData, start, end, mode)
677                if mode == self.ModeOfOperation["CFB"]:
678                    if firstRound:
679                        output = self.aes.encrypt(IV, key, size)
680                        firstRound = False
681                    else:
682                        output = self.aes.encrypt(iput, key, size)
683                    for i in range(16):
684                        if len(plaintext) - 1 < i:
685                            ciphertext[i] = 0 ^ output[i]
686                        elif len(output) - 1 < i:
687                            ciphertext[i] = plaintext[i] ^ 0
688                        elif len(plaintext) - 1 < i and len(output) < i:
689                            ciphertext[i] = 0 ^ 0
690                        else:
691                            ciphertext[i] = plaintext[i] ^ output[i]
692                    for k in range(end - start):
693                        cipherOut.append(ciphertext[k])
694                    iput = ciphertext
695                elif mode == self.ModeOfOperation["OFB"]:
696                    if firstRound:
697                        output = self.aes.encrypt(IV, key, size)
698                        firstRound = False
699                    else:
700                        output = self.aes.encrypt(iput, key, size)
701                    for i in range(16):
702                        if len(plaintext) - 1 < i:
703                            ciphertext[i] = 0 ^ output[i]
704                        elif len(output) - 1 < i:
705                            ciphertext[i] = plaintext[i] ^ 0
706                        elif len(plaintext) - 1 < i and len(output) < i:
707                            ciphertext[i] = 0 ^ 0
708                        else:
709                            ciphertext[i] = plaintext[i] ^ output[i]
710                    for k in range(end - start):
711                        cipherOut.append(ciphertext[k])
712                    iput = output
713                elif mode == self.ModeOfOperation["CBC"]:
714                    for i in range(16):
715                        if firstRound:
716                            iput[i] = plaintext[i] ^ IV[i]
717                        else:
718                            iput[i] = plaintext[i] ^ ciphertext[i]
719                    firstRound = False
720                    ciphertext = self.aes.encrypt(iput, key, size)
721                    # always 16 bytes because of the padding for CBC
722                    for k in range(16):
723                        cipherOut.append(ciphertext[k])
724        return mode, len(inputData), bytes(cipherOut)
725
726    # Mode of Operation Decryption
727    # cipherIn - Encrypted String
728    # originalsize - The unencrypted string length - required for CBC
729    # mode - mode of type modeOfOperation
730    # key - a number array of the bit length size
731    # size - the bit length of the key
732    # IV - the 128 bit number array Initilization Vector
733    def decrypt(self, cipherIn, originalsize, mode, key, size, IV):
734        """
735        Public method to perform the decryption operation.
736
737        @param cipherIn data to be decrypted (bytes)
738        @param originalsize unencrypted string length (required for CBC)
739            (integer)
740        @param mode mode of operation (0, 1 or 2)
741        @param key key to be used (bytes)
742        @param size length of the key (16, 24 or 32)
743        @param IV initialisation vector (bytearray)
744        @return decrypted data (bytes)
745        @exception ValueError key size is invalid or decrypted data is invalid
746        """
747        if len(key) % size:
748            raise ValueError("Illegal size ({0}) for key '{1}'.".format(
749                size, key))
750        if len(IV) % 16:
751            raise ValueError("IV is not a multiple of 16.")
752        # the AES input/output
753        ciphertext = bytearray()
754        iput = bytearray()
755        output = bytearray()
756        plaintext = bytearray(16)
757        # the output bytes
758        bytesOut = bytearray()
759        # char firstRound
760        firstRound = True
761        if cipherIn is not None:
762            for j in range(int(math.ceil(float(len(cipherIn)) / 16))):
763                start = j * 16
764                end = j * 16 + 16
765                if j * 16 + 16 > len(cipherIn):
766                    end = len(cipherIn)
767                ciphertext = cipherIn[start:end]
768                if mode == self.ModeOfOperation["CFB"]:
769                    if firstRound:
770                        output = self.aes.encrypt(IV, key, size)
771                        firstRound = False
772                    else:
773                        output = self.aes.encrypt(iput, key, size)
774                    for i in range(16):
775                        if len(output) - 1 < i:
776                            plaintext[i] = 0 ^ ciphertext[i]
777                        elif len(ciphertext) - 1 < i:
778                            plaintext[i] = output[i] ^ 0
779                        elif len(output) - 1 < i and len(ciphertext) < i:
780                            plaintext[i] = 0 ^ 0
781                        else:
782                            plaintext[i] = output[i] ^ ciphertext[i]
783                    for k in range(end - start):
784                        bytesOut.append(plaintext[k])
785                    iput = ciphertext
786                elif mode == self.ModeOfOperation["OFB"]:
787                    if firstRound:
788                        output = self.aes.encrypt(IV, key, size)
789                        firstRound = False
790                    else:
791                        output = self.aes.encrypt(iput, key, size)
792                    for i in range(16):
793                        if len(output) - 1 < i:
794                            plaintext[i] = 0 ^ ciphertext[i]
795                        elif len(ciphertext) - 1 < i:
796                            plaintext[i] = output[i] ^ 0
797                        elif len(output) - 1 < i and len(ciphertext) < i:
798                            plaintext[i] = 0 ^ 0
799                        else:
800                            plaintext[i] = output[i] ^ ciphertext[i]
801                    for k in range(end - start):
802                        bytesOut.append(plaintext[k])
803                    iput = output
804                elif mode == self.ModeOfOperation["CBC"]:
805                    output = self.aes.decrypt(ciphertext, key, size)
806                    for i in range(16):
807                        if firstRound:
808                            plaintext[i] = IV[i] ^ output[i]
809                        else:
810                            plaintext[i] = iput[i] ^ output[i]
811                    firstRound = False
812                    if originalsize is not None and originalsize < end:
813                        for k in range(originalsize - start):
814                            bytesOut.append(plaintext[k])
815                    else:
816                        for k in range(end - start):
817                            bytesOut.append(plaintext[k])
818                    iput = ciphertext
819        return bytes(bytesOut)
820
821
822def encryptData(key, data, mode=AESModeOfOperation.ModeOfOperation["CBC"]):
823    """
824    Module function to encrypt the given data with the given key.
825
826    @param key key to be used for encryption (bytes)
827    @param data data to be encrypted (bytes)
828    @param mode mode of operations (0, 1 or 2)
829    @return encrypted data prepended with the initialization vector (bytes)
830    @exception ValueError raised to indicate an invalid key size
831    """
832    key = bytearray(key)
833    if mode == AESModeOfOperation.ModeOfOperation["CBC"]:
834        data = append_PKCS7_padding(data)
835    keysize = len(key)
836    if keysize not in AES.KeySize.values():
837        raise ValueError('invalid key size: {0}'.format(keysize))
838    # create a new iv using random data
839    iv = bytearray([i for i in os.urandom(16)])
840    moo = AESModeOfOperation()
841    mode, length, ciph = moo.encrypt(data, mode, key, keysize, iv)
842    # With padding, the original length does not need to be known. It's a bad
843    # idea to store the original message length.
844    # prepend the iv.
845    return bytes(iv) + bytes(ciph)
846
847
848def decryptData(key, data, mode=AESModeOfOperation.ModeOfOperation["CBC"]):
849    """
850    Module function to decrypt the given data with the given key.
851
852    @param key key to be used for decryption (bytes)
853    @param data data to be decrypted (with initialization vector prepended)
854        (bytes)
855    @param mode mode of operations (0, 1 or 2)
856    @return decrypted data (bytes)
857    @exception ValueError raised to indicate an invalid key size
858    """
859    key = bytearray(key)
860    keysize = len(key)
861    if keysize not in AES.KeySize.values():
862        raise ValueError('invalid key size: {0}'.format(keysize))
863    # iv is first 16 bytes
864    iv = bytearray(data[:16])
865    data = bytearray(data[16:])
866    moo = AESModeOfOperation()
867    decr = moo.decrypt(data, None, mode, key, keysize, iv)
868    if mode == AESModeOfOperation.ModeOfOperation["CBC"]:
869        decr = strip_PKCS7_padding(decr)
870    return bytes(decr)
871