/ analysis / checkbitperm.py
checkbitperm.py
  1  #!/usr/bin/env python
  2  
  3  # this file verifies if the indexes of the 64 bit fixed permutation of
  4  # step 2 of sbt is according to my reverse engineering as seen in
  5  # bitpermbit. as this list of bits is very different from the list of
  6  # bits shown on the sbt page of the cryptomuseum.
  7  
  8  # this is using the byte/bit indexes as reverse engineered and
  9  # verified by emulator and reimplementation against test vectors
 10  def bitperm(cb):
 11     return bytes(sum(((cb[by] >> bi) & 1) << (7-i) for i, (by, bi) in enumerate(b))
 12         for b in (((4,4),(1,5),(3,1),(7,6),(0,5),(6,0),(5,7),(2,3)),
 13                   ((3,2),(0,7),(7,1),(1,0),(6,3),(4,5),(5,4),(2,0)),
 14                   ((7,5),(3,3),(1,3),(5,1),(0,3),(2,4),(6,2),(4,1)),
 15                   ((5,5),(3,0),(0,1),(4,3),(1,6),(6,7),(2,2),(7,3)),
 16                   ((2,5),(6,4),(4,7),(0,6),(5,6),(7,7),(3,5),(1,2)),
 17                   ((6,5),(4,2),(3,6),(5,2),(1,7),(2,6),(7,4),(0,2)),
 18                   ((5,0),(7,2),(1,4),(3,4),(4,6),(6,1),(0,0),(2,1)),
 19                   ((6,6),(0,4),(1,1),(7,0),(3,7),(2,7),(4,0),(5,3)))
 20     )
 21  
 22  # this is the above indexes converted into one continous 0..63
 23  # left-to-right indexes of the 64 bit vector.
 24  # this is the indexes that we try to verify and contrast with the
 25  # indexes published by the cryptomuseum.
 26  def bitpermbit(cb):
 27      bits = f"{cb:064b}"
 28      return sum(int(bits[x]) << (63-i) for i,x in enumerate((35, 10, 30, 57, 2, 55, 40, 20,
 29                                                              29, 0, 62, 15, 52, 34, 43, 23,
 30                                                              58, 28, 12, 46, 4, 19, 53, 38,
 31                                                              42, 31, 6, 36, 9, 48, 21, 60,
 32                                                              18, 51, 32, 1, 41, 56, 26, 13,
 33                                                              50, 37, 25, 45, 8, 17, 59, 5,
 34                                                              47, 61, 11, 27, 33, 54, 7, 22,
 35                                                              49, 3, 14, 63, 24, 16, 39, 44))
 36                 )
 37  
 38  # test vector from emulator/reimplementation
 39  cb0=0x86820280c3c181c0.to_bytes(8,byteorder="big")
 40  cb1 = bitperm(cb0)
 41  assert cb1 == 0x164001242c09892a.to_bytes(8,byteorder="big")
 42  
 43  cb1b = bitpermbit(0x86820280c3c181c0)
 44  assert cb1b == 0x164001242c09892a
 45  
 46  # symbolic testing, instead of concrete values, we have a distinct
 47  def split_by_n(obj, n):
 48    # src https://stackoverflow.com/questions/9475241/split-string-every-nth-character
 49    return [obj[i:i+n] for i in range(0, len(obj), n)]
 50  
 51  # same as above but instead of algebraic symbolic
 52  def bitperm_sym(cb):
 53     return [cb[by][7-bi] for by, bi in ((4,4),(1,5),(3,1),(7,6),(0,5),(6,0),(5,7),(2,3),
 54                                         (3,2),(0,7),(7,1),(1,0),(6,3),(4,5),(5,4),(2,0),
 55                                         (7,5),(3,3),(1,3),(5,1),(0,3),(2,4),(6,2),(4,1),
 56                                         (5,5),(3,0),(0,1),(4,3),(1,6),(6,7),(2,2),(7,3),
 57                                         (2,5),(6,4),(4,7),(0,6),(5,6),(7,7),(3,5),(1,2),
 58                                         (6,5),(4,2),(3,6),(5,2),(1,7),(2,6),(7,4),(0,2),
 59                                         (5,0),(7,2),(1,4),(3,4),(4,6),(6,1),(0,0),(2,1),
 60                                         (6,6),(0,4),(1,1),(7,0),(3,7),(2,7),(4,0),(5,3))]
 61  
 62  # same as above but instead of algebraic symbolic
 63  def bitpermbit_sym(bits):
 64      bits=''.join(''.join(x) for x in bits)
 65      return [bits[x] for x in (35, 10, 30, 57, 2, 55, 40, 20,
 66                                29, 0, 62, 15, 52, 34, 43, 23,
 67                                58, 28, 12, 46, 4, 19, 53, 38,
 68                                42, 31, 6, 36, 9, 48, 21, 60,
 69                                18, 51, 32, 1, 41, 56, 26, 13,
 70                                50, 37, 25, 45, 8, 17, 59, 5,
 71                                47, 61, 11, 27, 33, 54, 7, 22,
 72                                49, 3, 14, 63, 24, 16, 39, 44)]
 73  
 74  allchars=list(chr(x) for x in range(ord('a'),ord('z')+1))
 75  allchars.extend(list(chr(x) for x in range(ord('A'),ord('Z')+1)))
 76  allchars.extend(list(chr(x) for x in range(ord('0'),ord('9')+1)))
 77  allchars.extend(list(chr(x) for x in range(0x1d400,0x1d467+1)))
 78  
 79  # symbol for each bit position (a-zA-Z0-9𝐀𝐁)
 80  cb0sym = split_by_n(allchars[:8*8], 8)
 81  # cb0sym == abcdefgh ijklmnop qrstuvwx yzABCDEF GHIJKLMN OPQRSTUV WXYZ0123 456789𝐀𝐁
 82  
 83  print(' '.join((''.join(x) for x in cb0sym)))
 84  
 85  cb1sym = bitperm_sym(cb0sym)
 86  print(' '.join(split_by_n(''.join(cb1sym),8)))
 87  cb1bsym = bitpermbit_sym(cb0sym)
 88  print(' '.join(split_by_n(''.join(cb1bsym),8)))
 89  
 90  assert cb1sym == cb1bsym
 91  
 92  # this confirms my fixed bit permutation indexes are correct.
 93  
 94  # cryptomuseum published order
 95  def bitpermbit_cm_sym(bits):
 96      bits=''.join(''.join(x) for x in bits)
 97      return [bits[x-1] for x in (10, 60, 55,  8, 33, 41, 32, 21,
 98                                  24,  6, 52, 36, 26, 15, 64, 47,
 99                                  11, 30, 34, 22, 49, 42,  4, 62,
100                                  43, 12, 25, 58, 18,  1, 35, 54,
101                                  19, 40, 63, 29, 50,  9, 46,  5,
102                                  3, 37, 53, 23, 61, 44, 14, 31,
103                                  38, 57,  7, 56, 13, 27, 20, 48,
104                                  28, 51, 39,  2, 59, 16, 17, 45)]
105  
106  cb1cmsym = bitpermbit_cm_sym(cb0sym)
107  print(' '.join(split_by_n(''.join(cb1cmsym),8)))
108  
109  # symbolically looks completely different