Tue, 23 Apr 2024 11:26:04 +0200
Changed the source code and the source code documentation to improve the indication of unused method/function arguments.
# -*- coding: utf-8 -*- # # aes.py: implements AES - Advanced Encryption Standard # from the SlowAES project, http://code.google.com/p/slowaes/ # # Copyright (c) 2008 Josh Davis ( http://www.josh-davis.org ), # Alex Martelli ( http://www.aleax.it ) # # Ported from C code written by Laurent Haan # ( http://www.progressive-coding.com ) # # Licensed under the Apache License, Version 2.0 # http://www.apache.org/licenses/ # # # Ported to Python3 # # Copyright (c) 2011 - 2024 Detlev Offenbach <detlev@die-offenbachs.de> # """ Module implementing classes for encryption according Advanced Encryption Standard. """ import math import os def append_PKCS7_padding(b): """ Function to pad the given data to a multiple of 16-bytes by PKCS7 padding. @param b data to be padded @type bytes @return padded data @rtype bytes """ numpads = 16 - (len(b) % 16) return b + numpads * bytes(chr(numpads), encoding="ascii") def strip_PKCS7_padding(b): """ Function to strip off PKCS7 padding. @param b data to be stripped @type bytes @return stripped data @rtype bytes @exception ValueError data padding is invalid """ if len(b) % 16 or not b: raise ValueError("Data of len {0} can't be PCKS7-padded".format(len(b))) numpads = b[-1] if numpads > 16: raise ValueError("Data ending with {0} can't be PCKS7-padded".format(b[-1])) return b[:-numpads] class AES: """ Class implementing the Advanced Encryption Standard algorithm. """ # valid key sizes KeySize = { "SIZE_128": 16, "SIZE_192": 24, "SIZE_256": 32, } # Rijndael S-box sbox = [ 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, ] # Rijndael Inverted S-box rsbox = [ 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, ] # Rijndael Rcon Rcon = [ 0x8D, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, 0x72, 0xE4, 0xD3, 0xBD, 0x61, 0xC2, 0x9F, 0x25, 0x4A, 0x94, 0x33, 0x66, 0xCC, 0x83, 0x1D, 0x3A, 0x74, 0xE8, 0xCB, 0x8D, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, 0x72, 0xE4, 0xD3, 0xBD, 0x61, 0xC2, 0x9F, 0x25, 0x4A, 0x94, 0x33, 0x66, 0xCC, 0x83, 0x1D, 0x3A, 0x74, 0xE8, 0xCB, 0x8D, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, 0x72, 0xE4, 0xD3, 0xBD, 0x61, 0xC2, 0x9F, 0x25, 0x4A, 0x94, 0x33, 0x66, 0xCC, 0x83, 0x1D, 0x3A, 0x74, 0xE8, 0xCB, 0x8D, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, 0x72, 0xE4, 0xD3, 0xBD, 0x61, 0xC2, 0x9F, 0x25, 0x4A, 0x94, 0x33, 0x66, 0xCC, 0x83, 0x1D, 0x3A, 0x74, 0xE8, 0xCB, 0x8D, 0x01, 0x02, 0x04, 0x08, 0x10, 0x20, 0x40, 0x80, 0x1B, 0x36, 0x6C, 0xD8, 0xAB, 0x4D, 0x9A, 0x2F, 0x5E, 0xBC, 0x63, 0xC6, 0x97, 0x35, 0x6A, 0xD4, 0xB3, 0x7D, 0xFA, 0xEF, 0xC5, 0x91, 0x39, 0x72, 0xE4, 0xD3, 0xBD, 0x61, 0xC2, 0x9F, 0x25, 0x4A, 0x94, 0x33, 0x66, 0xCC, 0x83, 0x1D, 0x3A, 0x74, 0xE8, 0xCB, ] def __getSBoxValue(self, num): """ Private method to retrieve a given S-Box value. @param num position of the value @type int @return value of the S-Box @rtype int """ return self.sbox[num] def __getSBoxInvert(self, num): """ Private method to retrieve a given Inverted S-Box value. @param num position of the value @type int @return value of the Inverted S-Box @rtype int """ return self.rsbox[num] def __rotate(self, data): """ Private method performing Rijndael's key schedule rotate operation. Rotate the data word eight bits to the left: eg, rotate(1d2c3a4f) == 2c3a4f1d. @param data data of size 4 @type bytearray @return rotated data @rtype bytearray """ return data[1:] + data[:1] def __getRconValue(self, num): """ Private method to retrieve a given Rcon value. @param num position of the value @type int @return Rcon value @rtype int """ return self.Rcon[num] def __core(self, data, iteration): """ Private method performing the key schedule core operation. @param data data to operate on @type bytearray @param iteration iteration counter @type int @return modified data @rtype bytearray """ # rotate the 32-bit word 8 bits to the left data = self.__rotate(data) # apply S-Box substitution on all 4 parts of the 32-bit word for i in range(4): data[i] = self.__getSBoxValue(data[i]) # XOR the output of the rcon operation with i to the first part # (leftmost) only data[0] = data[0] ^ self.__getRconValue(iteration) return data def __expandKey(self, key, size, expandedKeySize): """ Private method performing Rijndael's key expansion. Expands a 128, 192 or 256 bit key into a 176, 208 or 240 bit key. @param key key to be expanded @type bytes or bytearray @param size size of the key in bytes (16, 24 or 32) @type int @param expandedKeySize size of the expanded key @type int @return expanded key @rtype bytearray """ # current expanded keySize, in bytes currentSize = 0 rconIteration = 1 expandedKey = bytearray(expandedKeySize) # set the 16, 24, 32 bytes of the expanded key to the input key for j in range(size): expandedKey[j] = key[j] currentSize += size while currentSize < expandedKeySize: # assign the previous 4 bytes to the temporary value t t = expandedKey[currentSize - 4 : currentSize] # every 16, 24, 32 bytes we apply the core schedule to t # and increment rconIteration afterwards if currentSize % size == 0: t = self.__core(t, rconIteration) rconIteration += 1 # For 256-bit keys, we add an extra sbox to the calculation if size == self.KeySize["SIZE_256"] and ((currentSize % size) == 16): for ll in range(4): t[ll] = self.__getSBoxValue(t[ll]) # We XOR t with the four-byte block 16, 24, 32 bytes before the new # expanded key. This becomes the next four bytes in the expanded # key. for m in range(4): expandedKey[currentSize] = expandedKey[currentSize - size] ^ t[m] currentSize += 1 # noqa: Y113 return expandedKey def __addRoundKey(self, state, roundKey): """ Private method to add (XORs) the round key to the state. @param state state to be changed @type bytearray @param roundKey key to be used for the modification @type bytearray @return modified state @rtype bytearray """ buf = state[:] for i in range(16): buf[i] ^= roundKey[i] return buf def __createRoundKey(self, expandedKey, roundKeyPointer): """ Private method to create a round key. @param expandedKey expanded key to be used @type bytearray @param roundKeyPointer position within the expanded key @type int @return round key @rtype bytearray """ roundKey = bytearray(16) for i in range(4): for j in range(4): roundKey[j * 4 + i] = expandedKey[roundKeyPointer + i * 4 + j] return roundKey def __galois_multiplication(self, a, b): """ Private method to perform a Galois multiplication of 8 bit characters a and b. @param a first factor @type bytes @param b second factor @type bytes @return result @rtype bytes """ p = 0 for _counter in range(8): if b & 1: p ^= a hi_bit_set = a & 0x80 a <<= 1 # keep a 8 bit a &= 0xFF if hi_bit_set: a ^= 0x1B b >>= 1 return p def __subBytes(self, state, isInv): """ Private method to substitute all the values from the state with the value in the SBox using the state value as index for the SBox. @param state state to be worked on @type bytearray @param isInv flag indicating an inverse operation @type bool @return modified state @rtype bytearray """ state = state[:] getter = self.__getSBoxInvert if isInv else self.__getSBoxValue for i in range(16): state[i] = getter(state[i]) return state def __shiftRows(self, state, isInv): """ Private method to iterate over the 4 rows and call __shiftRow() with that row. @param state state to be worked on @type bytearray @param isInv flag indicating an inverse operation @type bool @return modified state @rtype bytearray """ state = state[:] for i in range(4): state = self.__shiftRow(state, i * 4, i, isInv) return state def __shiftRow(self, state, statePointer, nbr, isInv): """ Private method to shift the bytes of a row to the left. @param state state to be worked on @type bytearray @param statePointer index into the state @type int @param nbr number of positions to shift @type int @param isInv flag indicating an inverse operation @type bool @return modified state @rtype bytearray """ state = state[:] for _ in range(nbr): if isInv: state[statePointer : statePointer + 4] = ( state[statePointer + 3 : statePointer + 4] + state[statePointer : statePointer + 3] ) else: state[statePointer : statePointer + 4] = ( state[statePointer + 1 : statePointer + 4] + state[statePointer : statePointer + 1] ) return state def __mixColumns(self, state, isInv): """ Private method to perform a galois multiplication of the 4x4 matrix. @param state state to be worked on @type bytearray @param isInv flag indicating an inverse operation @type bool @return modified state @rtype bytearray """ state = state[:] # iterate over the 4 columns for i in range(4): # construct one column by slicing over the 4 rows column = state[i : i + 16 : 4] # apply the __mixColumn on one column column = self.__mixColumn(column, isInv) # put the values back into the state state[i : i + 16 : 4] = column return state # galois multiplication of 1 column of the 4x4 matrix def __mixColumn(self, column, isInv): """ Private method to perform a galois multiplication of 1 column the 4x4 matrix. @param column column to be worked on @type bytearray @param isInv flag indicating an inverse operation @type bool @return modified column @rtype bytearray """ column = column[:] mult = [14, 9, 13, 11] if isInv else [2, 1, 1, 3] cpy = column[:] g = self.__galois_multiplication column[0] = ( g(cpy[0], mult[0]) ^ g(cpy[3], mult[1]) ^ g(cpy[2], mult[2]) ^ g(cpy[1], mult[3]) ) column[1] = ( g(cpy[1], mult[0]) ^ g(cpy[0], mult[1]) ^ g(cpy[3], mult[2]) ^ g(cpy[2], mult[3]) ) column[2] = ( g(cpy[2], mult[0]) ^ g(cpy[1], mult[1]) ^ g(cpy[0], mult[2]) ^ g(cpy[3], mult[3]) ) column[3] = ( g(cpy[3], mult[0]) ^ g(cpy[2], mult[1]) ^ g(cpy[1], mult[2]) ^ g(cpy[0], mult[3]) ) return column def __aes_round(self, state, roundKey): """ Private method to apply the 4 operations of the forward round in sequence. @param state state to be worked on @type bytearray @param roundKey round key to be used @type bytearray @return modified state @rtype bytearray """ state = self.__subBytes(state, False) state = self.__shiftRows(state, False) state = self.__mixColumns(state, False) state = self.__addRoundKey(state, roundKey) return state def __aes_invRound(self, state, roundKey): """ Private method to apply the 4 operations of the inverse round in sequence. @param state state to be worked on @type bytearray @param roundKey round key to be used @type bytearray @return modified state @rtype bytearray """ state = self.__shiftRows(state, True) state = self.__subBytes(state, True) state = self.__addRoundKey(state, roundKey) state = self.__mixColumns(state, True) return state def __aes_main(self, state, expandedKey, nbrRounds): """ Private method to do the AES encryption for one round. Perform the initial operations, the standard round, and the final operations of the forward AES, creating a round key for each round. @param state state to be worked on @type bytearray @param expandedKey expanded key to be used @type bytearray @param nbrRounds number of rounds to be done @type int @return modified state @rtype bytearray """ state = self.__addRoundKey(state, self.__createRoundKey(expandedKey, 0)) i = 1 while i < nbrRounds: state = self.__aes_round(state, self.__createRoundKey(expandedKey, 16 * i)) i += 1 state = self.__subBytes(state, False) state = self.__shiftRows(state, False) state = self.__addRoundKey( state, self.__createRoundKey(expandedKey, 16 * nbrRounds) ) return state def __aes_invMain(self, state, expandedKey, nbrRounds): """ Private method to do the inverse AES encryption for one round. Perform the initial operations, the standard round, and the final operations of the inverse AES, creating a round key for each round. @param state state to be worked on @type bytearray @param expandedKey expanded key to be used @type bytearray @param nbrRounds number of rounds to be done @type int @return modified state @rtype bytearray """ state = self.__addRoundKey( state, self.__createRoundKey(expandedKey, 16 * nbrRounds) ) i = nbrRounds - 1 while i > 0: state = self.__aes_invRound( state, self.__createRoundKey(expandedKey, 16 * i) ) i -= 1 state = self.__shiftRows(state, True) state = self.__subBytes(state, True) state = self.__addRoundKey(state, self.__createRoundKey(expandedKey, 0)) return state def encrypt(self, iput, key, size): """ Public method to encrypt a 128 bit input block against the given key of size specified. @param iput input data @type bytearray @param key key to be used @type bytes or bytearray @param size key size (16, 24 or 32) @type int @return encrypted data @rtype bytes @exception ValueError key size is invalid """ if size not in self.KeySize.values(): raise ValueError("Wrong key size given ({0}).".format(size)) output = bytearray(16) # the number of rounds nbrRounds = 0 # the 128 bit block to encode block = bytearray(16) # set the number of rounds if size == self.KeySize["SIZE_128"]: nbrRounds = 10 elif size == self.KeySize["SIZE_192"]: nbrRounds = 12 else: nbrRounds = 14 # the expanded keySize expandedKeySize = 16 * (nbrRounds + 1) # Set the block values, for the block: # a0,0 a0,1 a0,2 a0,3 # a1,0 a1,1 a1,2 a1,3 # a2,0 a2,1 a2,2 a2,3 # a3,0 a3,1 a3,2 a3,3 # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 # # iterate over the columns for i in range(4): # iterate over the rows for j in range(4): block[i + j * 4] = iput[i * 4 + j] # expand the key into an 176, 208, 240 bytes key # the expanded key expandedKey = self.__expandKey(key, size, expandedKeySize) # encrypt the block using the expandedKey block = self.__aes_main(block, expandedKey, nbrRounds) # unmap the block again into the output for kk in range(4): # iterate over the rows for ll in range(4): output[kk * 4 + ll] = block[kk + ll * 4] return bytes(output) # decrypts a 128 bit input block against the given key of size specified def decrypt(self, iput, key, size): """ Public method to decrypt a 128 bit input block against the given key of size specified. @param iput input data @type bytearray @param key key to be used @type bytes or bytearray @param size key size (16, 24 or 32) @type int @return decrypted data @rtype bytes @exception ValueError key size is invalid """ if size not in self.KeySize.values(): raise ValueError("Wrong key size given ({0}).".format(size)) output = bytearray(16) # the number of rounds nbrRounds = 0 # the 128 bit block to decode block = bytearray(16) # set the number of rounds if size == self.KeySize["SIZE_128"]: nbrRounds = 10 elif size == self.KeySize["SIZE_192"]: nbrRounds = 12 else: nbrRounds = 14 # the expanded keySize expandedKeySize = 16 * (nbrRounds + 1) # Set the block values, for the block: # a0,0 a0,1 a0,2 a0,3 # a1,0 a1,1 a1,2 a1,3 # a2,0 a2,1 a2,2 a2,3 # a3,0 a3,1 a3,2 a3,3 # the mapping order is a0,0 a1,0 a2,0 a3,0 a0,1 a1,1 ... a2,3 a3,3 # iterate over the columns for i in range(4): # iterate over the rows for j in range(4): block[i + j * 4] = iput[i * 4 + j] # expand the key into an 176, 208, 240 bytes key expandedKey = self.__expandKey(key, size, expandedKeySize) # decrypt the block using the expandedKey block = self.__aes_invMain(block, expandedKey, nbrRounds) # unmap the block again into the output for kk in range(4): # iterate over the rows for ll in range(4): output[kk * 4 + ll] = block[kk + ll * 4] return output class AESModeOfOperation: """ Class implementing the different AES mode of operations. """ aes = AES() # structure of supported modes of operation ModeOfOperation = { "OFB": 0, "CFB": 1, "CBC": 2, } def __extractBytes(self, inputData, start, end, mode): """ Private method to extract a range of bytes from the input. @param inputData input data @type bytes @param start start index @type int @param end end index @type int @param mode mode of operation (0, 1, 2) @type int @return extracted bytes @rtype bytearray """ if end - start > 16: end = start + 16 ar = bytearray(16) if mode == self.ModeOfOperation["CBC"] else bytearray() i = start j = 0 while len(ar) < end - start: ar.append(0) while i < end: ar[j] = inputData[i] j += 1 i += 1 return ar def encrypt(self, inputData, mode, key, size, IV): """ Public method to perform the encryption operation. @param inputData data to be encrypted @type bytes @param mode mode of operation (0, 1 or 2) @type int @param key key to be used @type bytes @param size length of the key (16, 24 or 32) @type int @param IV initialisation vector @type bytearray @return tuple with mode of operation, length of the input data and the encrypted data @rtype tuple of (int, int, bytes) @exception ValueError key size is invalid or decrypted data is invalid """ if len(key) % size: raise ValueError("Illegal size ({0}) for key '{1}'.".format(size, key)) if len(IV) % 16: raise ValueError("IV is not a multiple of 16.") # the AES input/output iput = bytearray(16) output = bytearray() ciphertext = bytearray(16) # the output cipher string cipherOut = bytearray() # char firstRound firstRound = True if inputData: for j in range(int(math.ceil(float(len(inputData)) / 16))): start = j * 16 end = j * 16 + 16 if end > len(inputData): end = len(inputData) plaintext = self.__extractBytes(inputData, start, end, mode) if mode == self.ModeOfOperation["CFB"]: if firstRound: output = self.aes.encrypt(IV, key, size) firstRound = False else: output = self.aes.encrypt(iput, key, size) for i in range(16): if len(plaintext) - 1 < i: ciphertext[i] = 0 ^ output[i] elif len(output) - 1 < i: ciphertext[i] = plaintext[i] ^ 0 elif len(plaintext) - 1 < i and len(output) < i: ciphertext[i] = 0 ^ 0 else: ciphertext[i] = plaintext[i] ^ output[i] for k in range(end - start): cipherOut.append(ciphertext[k]) iput = ciphertext elif mode == self.ModeOfOperation["OFB"]: if firstRound: output = self.aes.encrypt(IV, key, size) firstRound = False else: output = self.aes.encrypt(iput, key, size) for i in range(16): if len(plaintext) - 1 < i: ciphertext[i] = 0 ^ output[i] elif len(output) - 1 < i: ciphertext[i] = plaintext[i] ^ 0 elif len(plaintext) - 1 < i and len(output) < i: ciphertext[i] = 0 ^ 0 else: ciphertext[i] = plaintext[i] ^ output[i] for k in range(end - start): cipherOut.append(ciphertext[k]) iput = output elif mode == self.ModeOfOperation["CBC"]: for i in range(16): if firstRound: iput[i] = plaintext[i] ^ IV[i] else: iput[i] = plaintext[i] ^ ciphertext[i] firstRound = False ciphertext = self.aes.encrypt(iput, key, size) # always 16 bytes because of the padding for CBC for k in range(16): cipherOut.append(ciphertext[k]) return mode, len(inputData), bytes(cipherOut) # Mode of Operation Decryption # cipherIn - Encrypted String # originalsize - The unencrypted string length - required for CBC # mode - mode of type modeOfOperation # key - a number array of the bit length size # size - the bit length of the key # IV - the 128 bit number array Initilization Vector def decrypt(self, cipherIn, originalsize, mode, key, size, IV): """ Public method to perform the decryption operation. @param cipherIn data to be decrypted @type bytes @param originalsize unencrypted string length (required for CBC) @type int @param mode mode of operation (0, 1 or 2) @type int @param key key to be used @type bytes @param size length of the key (16, 24 or 32) @type int @param IV initialisation vector @type bytearray @return decrypted data @rtype bytes @exception ValueError key size is invalid or decrypted data is invalid """ if len(key) % size: raise ValueError("Illegal size ({0}) for key '{1}'.".format(size, key)) if len(IV) % 16: raise ValueError("IV is not a multiple of 16.") # the AES input/output ciphertext = bytearray() iput = bytearray() output = bytearray() plaintext = bytearray(16) # the output bytes bytesOut = bytearray() # char firstRound firstRound = True if cipherIn is not None: for j in range(int(math.ceil(float(len(cipherIn)) / 16))): start = j * 16 end = j * 16 + 16 if j * 16 + 16 > len(cipherIn): end = len(cipherIn) ciphertext = cipherIn[start:end] if mode == self.ModeOfOperation["CFB"]: if firstRound: output = self.aes.encrypt(IV, key, size) firstRound = False else: output = self.aes.encrypt(iput, key, size) for i in range(16): if len(output) - 1 < i: plaintext[i] = 0 ^ ciphertext[i] elif len(ciphertext) - 1 < i: plaintext[i] = output[i] ^ 0 elif len(output) - 1 < i and len(ciphertext) < i: plaintext[i] = 0 ^ 0 else: plaintext[i] = output[i] ^ ciphertext[i] for k in range(end - start): bytesOut.append(plaintext[k]) iput = ciphertext elif mode == self.ModeOfOperation["OFB"]: if firstRound: output = self.aes.encrypt(IV, key, size) firstRound = False else: output = self.aes.encrypt(iput, key, size) for i in range(16): if len(output) - 1 < i: plaintext[i] = 0 ^ ciphertext[i] elif len(ciphertext) - 1 < i: plaintext[i] = output[i] ^ 0 elif len(output) - 1 < i and len(ciphertext) < i: plaintext[i] = 0 ^ 0 else: plaintext[i] = output[i] ^ ciphertext[i] for k in range(end - start): bytesOut.append(plaintext[k]) iput = output elif mode == self.ModeOfOperation["CBC"]: output = self.aes.decrypt(ciphertext, key, size) for i in range(16): if firstRound: plaintext[i] = IV[i] ^ output[i] else: plaintext[i] = iput[i] ^ output[i] firstRound = False if originalsize is not None and originalsize < end: for k in range(originalsize - start): bytesOut.append(plaintext[k]) else: for k in range(end - start): bytesOut.append(plaintext[k]) iput = ciphertext return bytes(bytesOut) def encryptData(key, data, mode=AESModeOfOperation.ModeOfOperation["CBC"]): """ Module function to encrypt the given data with the given key. @param key key to be used for encryption @type bytes @param data data to be encrypted @type bytes @param mode mode of operations (0, 1 or 2) @type int @return encrypted data prepended with the initialization vector @rtype bytes @exception ValueError raised to indicate an invalid key size """ key = bytearray(key) if mode == AESModeOfOperation.ModeOfOperation["CBC"]: data = append_PKCS7_padding(data) keysize = len(key) if keysize not in AES.KeySize.values(): raise ValueError("invalid key size: {0}".format(keysize)) # create a new iv using random data iv = bytearray([i for i in os.urandom(16)]) moo = AESModeOfOperation() _mode, _length, ciph = moo.encrypt(data, mode, key, keysize, iv) # With padding, the original length does not need to be known. It's a bad # idea to store the original message length. # prepend the iv. return bytes(iv) + bytes(ciph) def decryptData(key, data, mode=AESModeOfOperation.ModeOfOperation["CBC"]): """ Module function to decrypt the given data with the given key. @param key key to be used for decryption @type bytes @param data data to be decrypted (with initialization vector prepended) @type bytes @param mode mode of operations (0, 1 or 2) @type int @return decrypted data @rtype bytes @exception ValueError raised to indicate an invalid key size """ key = bytearray(key) keysize = len(key) if keysize not in AES.KeySize.values(): raise ValueError("invalid key size: {0}".format(keysize)) # iv is first 16 bytes iv = bytearray(data[:16]) data = bytearray(data[16:]) moo = AESModeOfOperation() decr = moo.decrypt(data, None, mode, key, keysize, iv) if mode == AESModeOfOperation.ModeOfOperation["CBC"]: decr = strip_PKCS7_padding(decr) return bytes(decr)