From 9b2768d943a452ce6b30c41906ec1aeed495f4b1 Mon Sep 17 00:00:00 2001 From: Andreas Bogk Date: Thu, 15 Jan 2009 17:21:19 +0100 Subject: A5/1 implementation in Python by Piotr. --- A5.1/python/A51_Tables/a51.py | 167 ++++++++++++++++++++++++++++++++++++ A5.1/python/A51_Tables/table_gen.py | 62 +++++++++++++ 2 files changed, 229 insertions(+) create mode 100644 A5.1/python/A51_Tables/a51.py create mode 100644 A5.1/python/A51_Tables/table_gen.py diff --git a/A5.1/python/A51_Tables/a51.py b/A5.1/python/A51_Tables/a51.py new file mode 100644 index 0000000..b118287 --- /dev/null +++ b/A5.1/python/A51_Tables/a51.py @@ -0,0 +1,167 @@ +from BitVector import BitVector +""" +Module with implementations of: +lsfr - Linear Feedback Shift Register +a51 - algorithm A5/1 +""" + +class lsfr: + def __init__(self, length, taps, middle_bit, name=""): + """ + length - length of the register in bits + taps - feedback taps, for clocking the shift register. + These correspond to the primitive polynomial + Example polynomials from A5/1: + x**19 + x**5 + x**2 + x + 1, x**22 + x + 1, + or x**23 + x**15 + x**2 + x + 1 + middle_bit - middle bit of each of the three shift registers, for clock control + name - name of LSFR - for print() + """ + self.taps = taps + self.length = length + self.name = name + self.value = BitVector(size = length); + self.clk_bit_nr = length-middle_bit-1 + + def mix(self, liczba): + """Read value from LSB to MSB and add each bit to LSFR's feedback""" + for key_bit in reversed(liczba): + bit = key_bit + self.clock(bit) + + def clock(self, bit=False): + """Clock LSFR. Can add value of bit to feedback.""" + for tap in self.taps: + bit_nr = self.length - tap - 1 + bit = bit ^ self.value[bit_nr] + self.value << 1 + self.value[self.length-1] = bit + + def out(self): + """Clock LSFR. Can add value of bit to loopback.""" + return self.value[0] + + def clk_bit(self): + """Return clocking bit.""" + return self.value[self.clk_bit_nr] + + def set_value(self, value): + """Set internal state of LSFR.""" + self.value = BitVector(size=self.length, intVal=value) + + def __str__(self): + return "%s:%X" % (self.name, self.value.intValue()) + + +class a51: + def __init__(self): + self.key_size = 64 + self.fn_size = 22 + self.r1 = lsfr(19, [13, 16, 17, 18], 8, "R1") + self.r2 = lsfr(22, [20, 21], 10, "R2") + self.r3 = lsfr(23, [7, 20, 21, 22], 10, "R3") + self.r1_state_mask = ((1 << self.r1.length) -1 ) + self.r2_state_mask = ((1 << self.r2.length) -1 ) + self.r3_state_mask = ((1 << self.r3.length) -1 ) + + def initialize(self, key, fn): + """ + Initialize algorithm with: + + key - value of Kc - integer value of 64 bits + fn - fame number - integer value of 22 bits + """ + key_vect = BitVector(size=self.key_size, intVal=key) + fn_vect = BitVector(size=self.fn_size, intVal=fn) + + self.r1.mix(key_vect) + self.r2.mix(key_vect) + self.r3.mix(key_vect) + + self.r1.mix(fn_vect) + self.r2.mix(fn_vect) + self.r3.mix(fn_vect) + + for i in xrange(0, 100): + self._clock_regs() + + def _clock_regs(self): + """Clock all LSFR's according to majority rule.""" + maj = self._majority() + if self.r1.clk_bit() == maj: + self.r1.clock() + if self.r2.clk_bit() == maj: + self.r2.clock() + if self.r3.clk_bit() == maj: + self.r3.clock() + + def get_output(self): + """ + Generate 228 keystream bits: + + AtoB - 114 bits of keystream for the A->B direction. Store it, MSB first + BtoA - 114 bits of keystream for the B->A direction. Store it, MSB first + """ + AtoB = self.gen_block(114) + BtoA = self.gen_block(114) + return (AtoB, BtoA) + + def _out_bit(self): + out_bit = self.r1.out() ^ self.r2.out() ^ self.r3.out() + return out_bit + + def gen_block(self,size): + out = BitVector(size=0) + for i in xrange(0, size): + self._clock_regs() + out = out + BitVector(intVal = int(self._out_bit())) + return out.intValue() + + def _majority(self): + sum = self.r1.clk_bit()+self.r2.clk_bit()+self.r3.clk_bit() + if sum >= 2: + return True + else: + return False + + def get_state(self): + state = self.r3.value + self.r2.value + self.r1.value + return state.intValue() + + def set_state(self,state): + r1_value = state & self.r1_state_mask + state = state >> self.r1.length + r2_value = state & self.r2_state_mask + state = state >> self.r2.length + r3_value = state & self.r3_state_mask + state = state >> self.r3.length + self.r1.set_value(r1_value) + self.r2.set_value(r2_value) + self.r3.set_value(r3_value) + +def test(): + key = 0xEFCDAB8967452312 #== 0x48C4A2E691D5B3F7 from A5/1 pedagogical implementation in C + fn = 0x000134 + known_good_AtoB = 0x14D3AA960BFA0546ADB861569CA30# == 0x534EAA582FE8151AB6E1855A728C00 from A5/1 pedagogical implementation in C + known_good_BtoA = 0x093F4D68D757ED949B4CBE41B7C6B#== 0x24FD35A35D5FB6526D32F906DF1AC0 from A5/1 pedagogical implementation in C + alg = a51() + alg.initialize(key, fn) + alg.set_state(alg.get_state()) + (AtoB, BtoA) = alg.get_output() + failed = False + if (AtoB != known_good_AtoB) or (BtoA != known_good_BtoA): + failed = True + + print "Key: %x" % key + print "Fame number: %x" % fn + print "Known good keystream from A to B: 0x%029x" % known_good_AtoB + print "Observed keystream from A to B : 0x%029x" % AtoB + print "Known good keystream from B to A: 0x%029x" % known_good_BtoA + print "Observed keystream from B to A : 0x%029x" % BtoA + if failed != True: + print "\nResult: everything looks ok." + else: + print "\nResult: test failed!" + +if __name__ == '__main__': + test() \ No newline at end of file diff --git a/A5.1/python/A51_Tables/table_gen.py b/A5.1/python/A51_Tables/table_gen.py new file mode 100644 index 0000000..0e22db3 --- /dev/null +++ b/A5.1/python/A51_Tables/table_gen.py @@ -0,0 +1,62 @@ +from a51 import * + +class table_gen: + def __init__(self, end_point_bit, max_chain_len, max_number_chains): + self.end_point_mask = (1 << end_point_bit) - 1 + self.max_number_chains = max_number_chains + self.max_chain_len = max_chain_len + self.reduction_function = 0 + + def gen(self, reduction_function): + self.reduction_function = reduction_function + for chain_number in xrange(0,self.max_number_chains): + start_point = self._gen_start_point(chain_number) + (max_chain_len_exceed, end_point, chain_length) = self._gen_chain(start_point) + + if max_chain_len_exceed == False: #max_chain_len_exceed is looping + print (start_point, end_point, chain_length) + + def _gen_start_point(self, chain_number): + start_point = ((chain_number+1) << (18+22))+((chain_number+1) << (18))+(chain_number+1) + return start_point #TODO: find better method to generate start_points of chains + + def _gen_chain(self, start_point): #a5_until_endpoint + alg = a51() + state = start_point + alg.set_state(state) + + print "Generating a chain" + + for chain_links in xrange(0, max_chain_len): + output = alg.gen_block(64) + print "State:%x -> Output:%x" % (state, output) + if self._is_endpoint(output): + return (False, output, chain_links) + + state = self._reduction(output) + alg.set_state(state) + + return (True, None, None) + + def _is_endpoint(self, output): + if (output & self.end_point_mask) == 0: + return True + else: + return False + + def _reduction(self, output): + return output ^ self.reduction_function + +def gen_tables(max_tables, end_point_bit, max_chain_len, max_number_chains): + generator = table_gen(end_point_bit, max_chain_len, max_number_chains) + for table_nr in xrange(1,max_tables): + reduction_function = table_nr + generator.gen(reduction_function) + +if __name__ == '__main__': + max_tables = 156 + max_number_chains = 2000 + end_point_bit = 27 + max_chain_len = 2000 + + gen_tables(max_tables, end_point_bit, max_chain_len, max_number_chains) -- cgit v1.2.3