def gen_subbytes_table(): subBytesTable = [] c = BitVector(bitstring='01100011') for i in range(0, 256): a = BitVector(intVal=i, size=8).gf_MI( AES_modulus, 8) if i != 0 else BitVector(intVal=0) a1, a2, a3, a4 = [a.deep_copy() for x in range(4)] a ^= (a1 >> 4) ^ (a2 >> 5) ^ (a3 >> 6) ^ (a4 >> 7) ^ c subBytesTable.append(int(a)) return subBytesTable
def genTables(): c = BitVector(bitstring='01100011') d = BitVector(bitstring='00000101') for i in range(0, 256): # For the encryption SBox a = BitVector(intVal=i, size=8).gf_MI( AES_modulus, 8) if i != 0 else BitVector(intVal=0) # For bit scrambling for the encryption SBox entries: a1, a2, a3, a4 = [a.deep_copy() for x in range(4)] a ^= (a1 >> 4) ^ (a2 >> 5) ^ (a3 >> 6) ^ (a4 >> 7) ^ c subBytesTable.append(int(a)) # For the decryption Sbox: b = BitVector(intVal=i, size=8) # For bit scrambling for the decryption SBox entries: b1, b2, b3 = [b.deep_copy() for x in range(3)] b = (b1 >> 2) ^ (b2 >> 5) ^ (b3 >> 7) ^ d check = b.gf_MI(AES_modulus, 8) b = check if isinstance(check, BitVector) else 0 invSubBytesTable.append(int(b)) return subBytesTable, invSubBytesTable
def getDecryptionSubBox(): dbox = [[0 for i in range(16)] for j in range(16)] modulus = BitVector(bitstring='100011011') n = 8 d = BitVector(bitstring='00000101') for i in range(16): for j in range(16): i_s = bin(i)[2:].zfill(4) j_s = bin(j)[2:].zfill(4) bv = BitVector(bitstring=i_s + j_s) dboxt = bv.deep_copy() for k in range(8): bv[k] = dboxt[(k - 2) % 8] ^ dboxt[(k - 5) % 8] ^ dboxt[ (k - 7) % 8] ^ d[k] if bv.get_hex_string_from_bitvector() != '00': dbox[i][j] = bv.gf_MI(modulus, n) else: dbox[i][j] = BitVector(bitstring='00000000') dbox[i][j] = dbox[i][j].get_hex_string_from_bitvector() return dbox
class LowMC: def __init__(self, param): print("LowMC init for " + param) # Set security parameters according to param if (param == 'picnic-L1'): self.blocksize = 128 self.keysize = 128 self.number_sboxes = 10 self.number_rounds = 20 self.filename = 'picnic-L1.dat' elif (param == 'picnic-L3'): self.blocksize = 192 self.keysize = 192 self.number_sboxes = 10 self.number_rounds = 30 self.filename = 'picnic-L3.dat' elif (param == 'picnic-L5'): self.blocksize = 256 self.keysize = 256 self.number_sboxes = 10 self.number_rounds = 38 self.filename = 'picnic-L5.dat' # Public variables self.plaintext = None # Private variables self.__priv_key = None self.__state = None self.__lin_layer = [] self.__lin_layer_inv = [] self.__round_consts = [] self.__round_key_mats = [] self.__sbox = [0x00, 0x01, 0x03, 0x06, 0x07, 0x04, 0x05, 0x02] self.__sbox_inv = [0x00, 0x01, 0x07, 0x02, 0x05, 0x06, 0x03, 0x04] # Call init functions self.__read_constants() self.__invert_lin_matrix() print("LowMC init done") ''' //////////////////////////// /// Public functions /// //////////////////////////// ''' # Generate private key with lenght self.keysize from urandom def generate_priv_key(self): temp_key = os.urandom(int(self.keysize / 8)) self.__priv_key = BitVector(rawbytes=temp_key) # Set private key # @param priv_key as raw bytes with length self.keysize def set_priv_key(self, priv_key): assert (len(priv_key) * 8 == self.keysize), "Private key has length != keysize" self.__priv_key = BitVector(rawbytes=priv_key) # Encrypts a plaintext # @param plaintext must be raw bytes with length blocksize # @return ciphertext as raw bytes with length blocksize def encrypt(self, plaintext): assert (len(plaintext) * 8) == self.blocksize, "Plaintext has length != blocksize" assert (self.__priv_key is not None), "Private key not set" self.__state = BitVector(rawbytes=plaintext) self.__key_addition(0) for i in range(self.number_rounds): self.__apply_sbox() self.__multiply_with_lin_mat(i) self.__state = self.__state ^ self.__round_consts[i] self.__key_addition(i + 1) result = bytes.fromhex(self.__state.get_bitvector_in_hex()) self.__state = None return result # Decrypts a ciphertext # @param ciphertext must be raw bytes with length blocksize # @return plaintext as raw bytes with length blocksize def decrypt(self, ciphertext): assert (len(ciphertext) * 8) == self.blocksize, "Ciphertext has length != blocksize" assert (self.__priv_key is not None), "Private key not set" self.__state = BitVector(rawbytes=ciphertext) for i in range(self.number_rounds, 0, -1): self.__key_addition(i) self.__state = self.__state ^ self.__round_consts[i - 1] self.__multiply_with_lin_mat_inv(i - 1) self.__apply_sbox_inv() self.__key_addition(0) result = bytes.fromhex(self.__state.get_bitvector_in_hex()) self.__state = None return result ''' ///////////////////////////// /// Picnic functions /// ///////////////////////////// ''' def mpc_matrix_mul_keys(self, outputs, inputs, r, players): tmp_inputs = copy.deepcopy(inputs) for player in range(players): for i in range(self.blocksize): outputs[player][i] = (self.__round_key_mats[r][i] & tmp_inputs[player]).count_bits() % 2 return outputs def mpc_matrix_mul_lin(self, outputs, inputs, r, players): tmp_inputs = copy.deepcopy(inputs) for player in range(players): for i in range(self.blocksize): outputs[player][i] = (self.__lin_layer[r][i] & tmp_inputs[player]).count_bits() % 2 return outputs def mpc_xor_rconsts(self, states, r): states[0] = states[0] ^ self.__round_consts[r] return states def mpc_xor_rconsts_verify(self, states, r, chal_trit): if (chal_trit == 0): states[0] = states[0] ^ self.__round_consts[r] if (chal_trit == 2): states[1] = states[1] ^ self.__round_consts[r] return states ''' ///////////////////////////// /// Private functions /// ///////////////////////////// ''' def __apply_sbox(self): result = BitVector(size=self.blocksize) state_copy = self.__state.deep_copy() # Copy the identity part of the message result_ident = state_copy[(3 * self.number_sboxes):self.blocksize] # Substitute the rest of the message with the sboxes # ---------------------------------------------------- # ATTENTION: The 3-bit chunks seem to be reversed # in the Picnic-Ref-Implementation, compared to the # LowMC-Ref-Implementation and the original LowMC-paper. # Example: state[0:3]='001' becomes '100' then gets sboxed # to '111' and reversed again for the state-update. # ---------------------------------------------------- state_copy = self.__state[0:(3 * self.number_sboxes)] result_sbox = BitVector(size=0) for i in range(self.number_sboxes): state_index = (3 * i) state_3_bits = state_copy[state_index:state_index + 3].reverse() sbox_3_bits = BitVector(intVal=self.__sbox[int(state_3_bits)], size=3).reverse() result_sbox = result_sbox + sbox_3_bits result = result_sbox + result_ident self.__state = result def __apply_sbox_inv(self): result = BitVector(size=self.blocksize) state_copy = self.__state.deep_copy() # Copy the identity part of the message result_ident = state_copy[(3 * self.number_sboxes):self.blocksize] # Substitute the rest of the message with the inverse sboxes # ---------------------------------------------------- # ATTENTION: The 3-bit chunks seem to be reversed # in the Picnic-Ref-Implementation, compared to the # LowMC-Ref-Implementation and the original LowMC-paper. # ---------------------------------------------------- state_copy = self.__state[0:(3 * self.number_sboxes)] result_sbox = BitVector(size=0) for i in range(self.number_sboxes): state_index = (3 * i) state_3_bits = state_copy[state_index:state_index + 3].reverse() sbox_3_bits = BitVector(intVal=self.__sbox_inv[int(state_3_bits)], size=3).reverse() result_sbox = result_sbox + sbox_3_bits result = result_sbox + result_ident self.__state = result def __multiply_with_lin_mat(self, r): result = BitVector(size=self.blocksize) for i in range(self.blocksize): result[i] = (self.__lin_layer[r][i] & self.__state).count_bits() % 2 self.__state = result def __multiply_with_lin_mat_inv(self, r): result = BitVector(size=self.blocksize) for i in range(self.blocksize): result[i] = (self.__lin_layer_inv[r][i] & self.__state).count_bits() % 2 self.__state = result def __key_addition(self, r): round_key = BitVector(size=self.keysize) for i in range(self.blocksize): round_key[i] = (self.__round_key_mats[r][i] & self.__priv_key).count_bits() % 2 self.__state = self.__state ^ round_key def __read_constants(self): with open(self.filename, 'r') as matfile: const_data = matfile.read() const_data_split = const_data.split('\n') # Check for correct parameters and file length params = const_data_split[0:3] assert params[0] == str( self.blocksize), "Wrong blocksize in data file!" assert params[1] == str(self.keysize), "Wrong keysize in data file!" assert params[2] == str( self.number_rounds), "Wrong number of rounds in data file!" assert (len(const_data_split) - 1) == \ 3 + (((self.number_rounds * 2) + 1) * self.blocksize) + self.number_rounds,\ "Wrong file size (number of lines)" # Linear layer matrices lines_offset = 3 lines_count = self.number_rounds * self.blocksize lin_layer = const_data_split[lines_offset:(lines_offset + lines_count)] for r in range(self.number_rounds): mat = [] for s in range(self.blocksize): bv = BitVector(bitstring=lin_layer[(r * self.blocksize) + s]) mat.append(bv) self.__lin_layer.append(mat) # Round constants lines_offset += lines_count lines_count = self.number_rounds round_consts = const_data_split[lines_offset:(lines_offset + lines_count)] for line in round_consts: self.__round_consts.append(BitVector(bitstring=line)) # Round key matrices lines_offset += lines_count lines_count = (self.number_rounds + 1) * self.blocksize round_key_mats = const_data_split[lines_offset:(lines_offset + lines_count)] for r in range(self.number_rounds + 1): mat = [] for s in range(self.blocksize): mat.append( BitVector(bitstring=round_key_mats[(r * self.blocksize) + s])) self.__round_key_mats.append(mat) def __invert_lin_matrix(self): self.__lin_layer_inv = [] for r in range(self.number_rounds): # Copy lin_layer mat = [] for i in range(self.blocksize): mat.append(self.__lin_layer[r][i].deep_copy()) # Create (initial identity) matrix, where the # inverted matrix will be stored in. inv_mat = [] for i in range(self.blocksize): temp_bv = BitVector(intVal=0, size=self.blocksize) temp_bv[i] = 1 inv_mat.append(temp_bv) # Transform to upper triangular matrix row = 0 for col in range(self.keysize): if (not mat[row][col]): r = row + 1 while ((r < self.blocksize) and (not mat[r][col])): r += 1 if (r >= self.blocksize): continue else: temp = mat[row] mat[row] = mat[r] mat[r] = temp temp = inv_mat[row] inv_mat[row] = inv_mat[r] inv_mat[r] = temp for i in range(row + 1, self.blocksize): if (mat[i][col]): mat[i] = mat[i] ^ mat[row] inv_mat[i] = inv_mat[i] ^ inv_mat[row] row += 1 # Transform to inverse matrix for col in range(self.keysize, 0, -1): for r in range(col - 1): if (mat[r][col - 1]): mat[r] = mat[r] ^ mat[col - 1] inv_mat[r] = inv_mat[r] ^ inv_mat[col - 1] self.__lin_layer_inv.append(inv_mat)
class A5_2(object): """ Represents the A5/2 stream cipher. A key stream for a given session key and the corresponding frame counter can be generated """ def __init__(self, key, frame_counter): """ Creates an A5/2 Object :param key: 64 bit Session Key :param frame_counter: 22 bit frame counter """ if not (key >= 0 and key < math.pow(2, KEY_SIZE)): raise ValueError('Key value must be between 0 and 2^64!') if not (frame_counter >= 0 and frame_counter < math.pow(2, FRAME_COUNTER_SIZE)): raise ValueError('Frame counter value must be between 0 and 2^22!') self.r1 = LFSR(R1_SIZE, [], R1_TAPS, R1_MAJORITY_BITS, R1_NEGATED_BIT) self.r2 = LFSR(R2_SIZE, [], R2_TAPS, R2_MAJORITY_BITS, R2_NEGATED_BIT) self.r3 = LFSR(R3_SIZE, [], R3_TAPS, R3_MAJORITY_BITS, R3_NEGATED_BIT) self.r4 = LFSR(R4_SIZE, R4_CLOCK_BITS, R4_TAPS, [], None) self.key = BitVector(size=KEY_SIZE, intVal=key) self.frame_counter = BitVector(size=FRAME_COUNTER_SIZE, intVal=frame_counter) self.key_stream = BitVector(size=KEY_STREAM_SIZE) self.register_states = [] def get_key_stream_with_predefined_registers(self, r1, r2, r3, r4, generate_only_send_key=False): """ Sets the registers to the specified values (r1, r2, r3 and r4) and calculates a key stream :param r1: register 1 LFSR :param r2: register 2 LFSR :param r3: register 3 LFSR :param r4: register 4 LFSR :param generate_only_send_key: generates only the first 114 bits. Useful for checking the correct register values in the attack :return the generated key stream """ self.r1 = LFSR(R1_SIZE, [], R1_TAPS, R1_MAJORITY_BITS, R1_NEGATED_BIT, bitstring=r1) self.r2 = LFSR(R2_SIZE, [], R2_TAPS, R2_MAJORITY_BITS, R2_NEGATED_BIT, bitstring=r2) self.r3 = LFSR(R3_SIZE, [], R3_TAPS, R3_MAJORITY_BITS, R3_NEGATED_BIT, bitstring=r3) self.r4 = LFSR(R4_SIZE, R4_CLOCK_BITS, R4_TAPS, [], None, bitstring=r4) self._clocking_with_majority(MAJORITY_CYCLES_A52) self._generate_key_stream(generate_only_send_key=generate_only_send_key) return (self.send_key, self.receive_key) def _create_register_backup(self): """ Saves the register states r1, r2, r3 and r4 in a dictionary """ self.initial_sates = {'r1': copy.deepcopy(self.r1), 'r2': copy.deepcopy(self.r2), 'r3': copy.deepcopy(self.r3), 'r4': copy.deepcopy(self.r4)} def _set_bits(self): """ Sets the bits R1[15] = 1, R2[16] = 1, R3[18] = 1, R4[10] = 1. """ self.r1.set_bit(FORCE_R1_BIT_TO_1, 1) self.r2.set_bit(FORCE_R2_BIT_TO_1, 1) self.r3.set_bit(FORCE_R3_BIT_TO_1, 1) self.r4.set_bit(FORCE_R4_BIT_TO_1, 1) def _clocking(self, limit, vector): """ Performs clocking for all registers (r1, r2, r3 and r4) :param limit: number of clocking cycles :param vector: either the session key or the frame counter. In each cycle a bit is XORed to the first position. """ for i in reversed(range(limit)): self.r1.clock(vector[i]) self.r2.clock(vector[i]) self.r3.clock(vector[i]) self.r4.clock(vector[i]) def _clocking_with_majority(self, limit, generate_key_stream=False, save_register_states=False): """ Performs clocking for the registers r1, r2 and r3 with the majority function of r4 :param limit: number of clocking cycles :param generate_key_stream: Boolean, which determines whether the output bits should be discarded :param save_register_states: Flag, that indicates whether the register states in each clock cycle should be saved """ for i in range(limit): majority = self._majority() if self.r4.get_bit(R4_CLOCKING_BIT_FOR_R1) == majority: self.r1.clock() if self.r4.get_bit(R4_CLOCKING_BIT_FOR_R2) == majority: self.r2.clock() if self.r4.get_bit(R4_CLOCKING_BIT_FOR_R3) == majority: self.r3.clock() self.r4.clock() if generate_key_stream: if save_register_states: self.register_states.append({'r1': copy.deepcopy(self.r1), 'r2': copy.deepcopy(self.r2), 'r3': copy.deepcopy(self.r3)}) self._add_key_stream_bit(i) def _generate_key_stream(self, save_register_states=False, generate_only_send_key=False): """ Generates 114 bits for the send key and 114 bits for the receive key. """ self._clocking_with_majority(KEY_STREAM_SIZE, save_register_states=save_register_states, generate_key_stream=True) if not generate_only_send_key: self.send_key = self.key_stream.deep_copy() self._clocking_with_majority(KEY_STREAM_SIZE, save_register_states=save_register_states, generate_key_stream=True) self.receive_key = self.key_stream.deep_copy() else: self.send_key = self.key_stream self.receive_key = None def get_key_stream(self, save_register_states=False, generate_only_send_key=False): """ Performs the following steps: 1. Run A5/2 for 64 cycles and XOR the session key into the registers 2. Run A5/2 for 22 cycles and XOR the frame counter into the registers 3. Sets the bits R1[15] = 1, R2[16] = 1, R3[18] = 1, R4[10] = 1. 4. Run A5/2 for 99 cycles and discard the output 5. Run A5/2 for 228 cycles and use the output as key stream :param save_register_states: Flag, that indicates whether the register states in each clock cycle should be saved :return key stream as pair (send_key, receive_key) """ self._clocking(KEY_SIZE, self.key) self._clocking(FRAME_COUNTER_SIZE, self.frame_counter) self._set_bits() self._create_register_backup() self._clocking_with_majority(MAJORITY_CYCLES_A52) self._generate_key_stream(save_register_states, generate_only_send_key=generate_only_send_key) return (self.send_key, self.receive_key) def _add_key_stream_bit(self, index): """ Calculates the output bit (key bit) :param index: The key stream bit index """ self.key_stream[index] = self.r1.register[0] ^ self.r2.register[0] ^ self.r3.register[0] ^ self.r1.get_majority() ^ self.r2.get_majority() ^ self.r3.get_majority() def _majority(self): """ :return most common bit in R4 """ clocked_bits = [] clocked_bits = clocked_bits + self.r4.get_clock_bits() a = clocked_bits[0] b = clocked_bits[1] c = clocked_bits[2] return (a*b) ^ (a*c) ^ (b*c)
class ImmediateConstant: """ An immediate constant. This class represents some sort of constant used as an immediate value by an instruction. Such constant can be a literal value or a symbolic one. Immediate formats can differ in size, so a size must be specified at creation time for faithful representation and correct manipulation of the binary value. :var symbol: the symbolic identifier of the constant, if any :var value: the binary representation of the value, if assigned :var int_val: the integer representation of the value, if assigned :var size: the size in bits of the containing immediate field """ _symbol: Optional[str] _value: Optional[BitVector] _size: int def __init__(self, size, symbol: str = None, value: int = None): """ Instantiate an immediate constant of the specified size and value, identified by a symbol. :param size: the size in bits of the constant :param symbol: the symbol identifying the constant, if any :param value: the integer value of the constant, if any :raise ValueError: when both symbol and value are left unspecified """ if symbol is None and value is None: raise ValueError("Constant must be symbolic or have a value") self._size = size self._symbol = symbol if value is not None: # Prepare the mask for cutting the supplied value's bit representation to the specified size mask = 0 for f in range(0, size): mask += 2**f value = value & mask self._value = BitVector(intVal=value, size=size) # Sizes must be coherent assert self._size == len(self._value) else: self._value = None @property def symbol(self) -> str: return self._symbol @property def value(self) -> BitVector: return self._value.deep_copy() @property def int_val(self) -> int: # Return the constant's integer representation, preserving its sign through an overly complicated procedure return -((~self._value).int_val() + 1) if self._value[0] == 1 else self._value.int_val() @property def size(self): return self._size def __repr__(self): return "Instruction.ImmediateConstant(size=" + repr(self._size) + ", symbol=" + repr(self._symbol) + \ ", value=" + repr(None if self._value is None else self.int_val) + ")" def __str__(self): return str(self.int_val) if self._symbol is None else self.symbol
class LowMC(object): """LowMC blockcipher mainclass. For de- and encryption of message blocks with the LowMC blockipher. This class can handle the various Picnic security levels and is able to generate or set and store a single privat key. """ __slots__ = [ '__blocksize', '__keysize', '__number_sboxes', '__number_rounds', '__filename', '__blocksize_bytes', '__keysize_bytes', '__plaintext', '__priv_key', '__state', '__lin_layer', '__lin_layer_inv', '__round_consts', '__round_key_mats', '__sbox', '__sbox_inv' ] def __init__(self, param: str) -> None: """Instanciates a LowMC object. Args: param: A string containing the Picnic security level """ if (param == 'picnic-L1'): self.__blocksize = 128 self.__keysize = 128 self.__number_sboxes = 10 self.__number_rounds = 20 self.__filename = 'picnic-L1.dat' elif (param == 'picnic-L3'): self.__blocksize = 192 self.__keysize = 192 self.__number_sboxes = 10 self.__number_rounds = 30 self.__filename = 'picnic-L3.dat' elif (param == 'picnic-L5'): self.__blocksize = 256 self.__keysize = 256 self.__number_sboxes = 10 self.__number_rounds = 38 self.__filename = 'picnic-L5.dat' else: raise Exception( 'Argument is not a valid Picnic security Level: {}'.format( param)) self.__blocksize_bytes = int(self.__blocksize / 8) self.__keysize_bytes = int(self.__keysize / 8) self.__plaintext = None self.__priv_key = None self.__state = None self.__lin_layer = [] self.__lin_layer_inv = [] self.__round_consts = [] self.__round_key_mats = [] self.__sbox = [0x00, 0x01, 0x03, 0x06, 0x07, 0x04, 0x05, 0x02] self.__sbox_inv = [0x00, 0x01, 0x07, 0x02, 0x05, 0x06, 0x03, 0x04] self.__read_constants() self.__invert_lin_matrix() @property def private_key(self) -> bytes: """Private key getter. Getter method for returning the private key Returns: bytearray of the private key """ return self.__priv_key @private_key.setter def private_key(self, priv_key: Optional[bytes] = None) -> None: """Set or generate a private key. If no private key is provided as argument, one is generated from the CSPRNG of the underlying OS. This should be a plattform independend source for randomness. It will have the length self.__keysize_bytes. Args: priv_key: If provided, must be a bytearray of length self.__keysize_bytes """ if (priv_key is None): temp_key = os.urandom(int(self.__keysize_bytes)) self.__priv_key = BitVector(rawbytes=temp_key) else: assert (len(priv_key) == self.__keysize_bytes), \ "Private key has length != keysize" self.__priv_key = BitVector(rawbytes=priv_key) def encrypt(self, plaintext: bytes) -> bytes: """Encryption of a plaintext. Args: plaintext: Must be a bytearray of length self.__blocksize_bytes Returns: A bytearray containing the ciphertext of length self.__blocksize_bytes """ assert (len(plaintext) == self.__blocksize_bytes), \ "Plaintext has length != blocksize" assert (self.__priv_key is not None), "Private key not set" self.__state = BitVector(rawbytes=plaintext) self.__key_addition(0) for i in range(self.__number_rounds): self.__apply_sbox() self.__multiply_with_lin_mat(i) self.__state = self.__state ^ self.__round_consts[i] self.__key_addition(i + 1) result = bytes.fromhex(self.__state.get_bitvector_in_hex()) self.__state = None return result def decrypt(self, ciphertext: bytes) -> bytes: """Decryption of a ciphertext. Args: ciphertext: Must be a bytearray of length self.__blocksize_bytes Returns: bytearray containing the plaintext of length self.__blocksize_bytes """ assert (len(ciphertext) == self.__blocksize_bytes), \ "Ciphertext has length != blocksize" assert (self.__priv_key is not None), "Private key not set" self.__state = BitVector(rawbytes=ciphertext) for i in range(self.__number_rounds, 0, -1): self.__key_addition(i) self.__state = self.__state ^ self.__round_consts[i - 1] self.__multiply_with_lin_mat_inv(i - 1) self.__apply_sbox_inv() self.__key_addition(0) result = bytes.fromhex(self.__state.get_bitvector_in_hex()) self.__state = None return result def __apply_sbox(self) -> None: result = BitVector(size=self.__blocksize) state_copy = self.__state.deep_copy() # Copy the identity part of the message result_ident = state_copy[(3 * self.__number_sboxes):self.__blocksize] # Substitute the rest of the message with the sboxes # ---------------------------------------------------- # ATTENTION: The 3-bit chunks seem to be reversed # in the Picnic-Ref-Implementation, compared to the # LowMC-Ref-Implementation and the original LowMC-paper. # Example: state[0:3]='001' becomes '100' then gets sboxed # to '111' and reversed again for the state-update. # ---------------------------------------------------- state_copy = self.__state[0:(3 * self.__number_sboxes)] result_sbox = BitVector(size=0) for i in range(self.__number_sboxes): state_index = (3 * i) state_3_bits = state_copy[state_index:state_index + 3].reverse() sbox_3_bits = BitVector(intVal=self.__sbox[int(state_3_bits)], size=3).reverse() result_sbox = result_sbox + sbox_3_bits result = result_sbox + result_ident self.__state = result def __apply_sbox_inv(self) -> None: result = BitVector(size=self.__blocksize) state_copy = self.__state.deep_copy() # Copy the identity part of the message result_ident = state_copy[(3 * self.__number_sboxes):self.__blocksize] # Substitute the rest of the message with the inverse sboxes # ---------------------------------------------------- # ATTENTION: The 3-bit chunks seem to be reversed # in the Picnic-Ref-Implementation, compared to the # LowMC-Ref-Implementation and the original LowMC-paper. # ---------------------------------------------------- state_copy = self.__state[0:(3 * self.__number_sboxes)] result_sbox = BitVector(size=0) for i in range(self.__number_sboxes): state_index = (3 * i) state_3_bits = state_copy[state_index:state_index + 3].reverse() sbox_3_bits = BitVector(intVal=self.__sbox_inv[int(state_3_bits)], size=3).reverse() result_sbox = result_sbox + sbox_3_bits result = result_sbox + result_ident self.__state = result def __multiply_with_lin_mat(self, r: int) -> None: result = BitVector(size=self.__blocksize) for i in range(self.__blocksize): result[i] = (self.__lin_layer[r][i] & self.__state) \ .count_bits() % 2 self.__state = result def __multiply_with_lin_mat_inv(self, r: int) -> None: result = BitVector(size=self.__blocksize) for i in range(self.__blocksize): result[i] = (self.__lin_layer_inv[r][i] & self.__state) \ .count_bits() % 2 self.__state = result def __key_addition(self, r: int) -> None: round_key = BitVector(size=self.__keysize) for i in range(self.__blocksize): round_key[i] = (self.__round_key_mats[r][i] & self.__priv_key) \ .count_bits() % 2 self.__state = self.__state ^ round_key def __read_constants(self) -> None: with open(self.__filename, 'r') as matfile: const_data = matfile.read() const_data_split = const_data.split('\n') # Check for correct parameters and file length params = const_data_split[0:3] assert params[0] == str(self.__blocksize), \ "Wrong blocksize in data file!" assert params[1] == str(self.__keysize), \ "Wrong keysize in data file!" assert params[2] == str(self.__number_rounds), \ "Wrong number of rounds in data file!" assert (len(const_data_split) - 1) == 3 \ + (((self.__number_rounds * 2) + 1) * self.__blocksize) \ + self.__number_rounds, \ "Wrong file size (number of lines)" # Linear layer matrices lines_offset = 3 lines_count = self.__number_rounds * self.__blocksize lin_layer = const_data_split[lines_offset:(lines_offset + lines_count)] for r in range(self.__number_rounds): mat = [] for s in range(self.__blocksize): bv = BitVector(bitstring=lin_layer[(r * self.__blocksize) + s]) mat.append(bv) self.__lin_layer.append(mat) # Round constants lines_offset += lines_count lines_count = self.__number_rounds round_consts = const_data_split[lines_offset:(lines_offset + lines_count)] for line in round_consts: self.__round_consts.append(BitVector(bitstring=line)) # Round key matrices lines_offset += lines_count lines_count = (self.__number_rounds + 1) * self.__blocksize round_key_mats = const_data_split[lines_offset:(lines_offset + lines_count)] for r in range(self.__number_rounds + 1): mat = [] for s in range(self.__blocksize): mat.append( BitVector(bitstring=round_key_mats[(r * self.__blocksize) + s])) self.__round_key_mats.append(mat) def __invert_lin_matrix(self) -> None: self.__lin_layer_inv = [] for r in range(self.__number_rounds): # Copy lin_layer mat = [] for i in range(self.__blocksize): mat.append(self.__lin_layer[r][i].deep_copy()) # Create (initial identity) matrix, where the # inverted matrix will be stored in. inv_mat = [] for i in range(self.__blocksize): temp_bv = BitVector(intVal=0, size=self.__blocksize) temp_bv[i] = 1 inv_mat.append(temp_bv) # Transform to upper triangular matrix row = 0 for col in range(self.__keysize): if (not mat[row][col]): r = row + 1 while ((r < self.__blocksize) and (not mat[r][col])): r += 1 if (r >= self.__blocksize): continue else: temp = mat[row] mat[row] = mat[r] mat[r] = temp temp = inv_mat[row] inv_mat[row] = inv_mat[r] inv_mat[r] = temp for i in range(row + 1, self.__blocksize): if (mat[i][col]): mat[i] = mat[i] ^ mat[row] inv_mat[i] = inv_mat[i] ^ inv_mat[row] row += 1 # Transform to inverse matrix for col in range(self.__keysize, 0, -1): for r in range(col - 1): if (mat[r][col - 1]): mat[r] = mat[r] ^ mat[col - 1] inv_mat[r] = inv_mat[r] ^ inv_mat[col - 1] self.__lin_layer_inv.append(inv_mat)
class A5_1(object): """ Represents the A5/1 stream cipher. A key stream for a given session key and the corresponding frame counter can be generated """ def __init__(self, key, frame_counter): """ Creates an A5/1 Object :param key: 64 bit Session Key :param frame_counter: 22 bit frame counter """ if not (key >= 0 and key < math.pow(2, KEY_SIZE)): raise ValueError('Key value must be between 0 and 2^64!') if not (frame_counter >= 0 and frame_counter < math.pow(2, FRAME_COUNTER_SIZE)): raise ValueError('Frame counter value must be between 0 and 2^22!') self.r1 = LFSR(R1_SIZE, [R1_CLOCKING_BIT], R1_TAPS) self.r2 = LFSR(R2_SIZE, [R2_CLOCKING_BIT], R2_TAPS) self.r3 = LFSR(R3_SIZE, [R3_CLOCKING_BIT], R3_TAPS) self.key = BitVector(size=KEY_SIZE, intVal=key) self.frame_counter = BitVector(size=FRAME_COUNTER_SIZE, intVal=frame_counter) self.key_stream = BitVector(size=KEY_STREAM_SIZE) self._clocking(KEY_SIZE, self.key) self._clocking(FRAME_COUNTER_SIZE, self.frame_counter) self._clocking_with_majority(MAJORITY_CYCLES_A51) self._generate_key_stream() def _clocking(self, limit, vector): """ Performs clocking for all registers (r1, r2 and r3) :param limit: number of clocking cycles :param vector: either the session key or the frame counter. In each cycle a bit is XORed to the first position. """ for i in reversed(range(limit)): self.r1.clock(vector[i]) self.r2.clock(vector[i]) self.r3.clock(vector[i]) def _clocking_with_majority(self, limit, generate_key_stream=False): """ Performs clocking for the registers r1, r2 and r3 :param limit: number of clocking cycles :param generate_key_stream: Boolean, which determines whether the output bits should be discarded """ for i in range(limit): majority = self._majority() if self.r1.get_clock_bits()[0] == majority: self.r1.clock() if self.r2.get_clock_bits()[0] == majority: self.r2.clock() if self.r3.get_clock_bits()[0] == majority: self.r3.clock() if generate_key_stream: self._add_key_stream_bit(i) def _generate_key_stream(self): """ Generates 114 bits for the send key and 114 bits for the receive key. """ self._clocking_with_majority(KEY_STREAM_SIZE, True) self.send_key = self.key_stream.deep_copy() self._clocking_with_majority(KEY_STREAM_SIZE, True) self.receive_key = self.key_stream.deep_copy() def get_key_stream(self): return (self.send_key, self.receive_key) def _add_key_stream_bit(self, index): """ Calculates the output bit (key bit) :param index: The key stream bit index """ self.key_stream[index] = self.r1.register[0] ^ self.r2.register[ 0] ^ self.r3.register[0] def _majority(self): """ :return most common bit of the clocking bits from r1, r2 and r3 """ clocked_bits = [] clocked_bits.append(self.r1.get_clock_bits()[0]) clocked_bits.append(self.r2.get_clock_bits()[0]) clocked_bits.append(self.r3.get_clock_bits()[0]) counter = Counter(clocked_bits) return counter.most_common(1)[0][0]