Implement a secure ICS protocol targeting LoRa Node151 microcontroller for controlling irrigation.
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 
 
 

265 lines
9.4 KiB

  1. """
  2. Keccak and Keccak modes.
  3. Copyright (c) Mike Hamburg, Cryptography Research, 2016.
  4. I will need to contact legal to get a license for this; in the mean time it is
  5. for testing purposes only.
  6. """
  7. import itertools
  8. import unittest
  9. from math import log
  10. class KeccakError(Exception):
  11. pass
  12. class KeccakF(object):
  13. """Keccak-f[n] on byte arrays."""
  14. def __init__(self,bits=1600,trace=False):
  15. """
  16. Initialize at a given bit length.
  17. If trace is set, then print out call,delta,return when called.
  18. """
  19. if bits not in [200,400,800,1600]:
  20. raise KeccakError("KeccakF bits must be in [200,400,800,1600]")
  21. self.bits = bits
  22. self.nbytes = bits//8
  23. self._trace = trace
  24. self._last = None
  25. def __repr__(self): return "KeccakF(%d)" % self.bits
  26. def copy(self):
  27. """Copy this F object"""
  28. ret = KeccakF(bits=self.bits,trace=self._trace)
  29. if self._last is not None: ret._last = bytearray(self._last)
  30. return ret
  31. def __call__(self, data):
  32. """Return KeccakF[n](data)"""
  33. if self._trace:
  34. if self._last is not None:
  35. print("Del KeccakF:",\
  36. "".join(("%02x" % (d^e) for d,e in zip(data,self._last))))
  37. print("Call KeccakF:", "".join(("%02x" % d for d in data)))
  38. WORD = self.bits//25
  39. A = [ [ sum(( data[(y*5+x)*WORD//8+o//8]<<o
  40. for o in range(0,WORD,8)))
  41. for y in range(5)]
  42. for x in range(5)]
  43. def rot(x,n): return (x<<n | x>>(WORD-n)) & (1<<WORD)-1
  44. LFSR = 0x01
  45. B = [[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0],[0,0,0,0,0]]
  46. for i in range(12 + 2*int(log(self.bits//25,2))):
  47. # Theta
  48. C = [ A[x][0]^A[x][1]^A[x][2]^A[x][3]^A[x][4] for x in range(5) ]
  49. D = [ C[(x-1)%5] ^ rot(C[(x+1)%5],1) for x in range(5) ]
  50. for x in range(5):
  51. for y in range(5):
  52. A[x][y] ^= D[x]
  53. # Rho pi
  54. x,y = 1,0
  55. for j in range(1,25):
  56. tmp = A[x][y]
  57. x,y = y,(2*x+3*y)%5
  58. B[x][y] = rot(tmp, (j*(j+1)//2) % WORD)
  59. B[0][0] = A[0][0]
  60. # Chi
  61. for x in range(5):
  62. for y in range(5):
  63. A[x][y] = B[x][y]^((~B[(x+1)%5][y]) & B[(x+2)%5][y])
  64. # Iota
  65. for l in range(7):
  66. A[0][0] ^= ((1<<WORD)-1) & ((LFSR&1)<<(2**l-1))
  67. LFSR = (LFSR<<1) ^ (LFSR>>7)*0x171
  68. ret = bytearray(( A[x][y]>>o & 0xFF
  69. for y in range(5)
  70. for x in range(5)
  71. for o in range(0,WORD,8) ))
  72. if self._trace:
  73. self._last = bytearray(ret)
  74. print("Ret KeccakF:", "".join(("%02x" % d for d in ret)))
  75. return ret
  76. class KeccakHash(object):
  77. """
  78. Keccak mode such as SHA3, SHAKE, cSHAKE, CMAC, etc
  79. """
  80. def __init__(self,rate_bytes=None,suffix=None,
  81. S=bytearray(), # distinguisher
  82. N=bytearray(), # NIST function name
  83. prefix=bytearray(),
  84. out_bytes=None,F=None,
  85. copy_of=None):
  86. if copy_of is None:
  87. if F is None: F = KeccakF()
  88. self._F = F
  89. self._st = bytearray(F.nbytes)
  90. if rate_bytes is None and out_bytes is not None:
  91. # Like SHA-3
  92. rate_bytes = F.nbytes - 2*out_bytes
  93. elif rate_bytes is None:
  94. raise KeccakError("Need a rate")
  95. self._pos = 0
  96. self.rate_bytes = rate_bytes
  97. self.out_bytes = out_bytes
  98. if len(S) or len(N):
  99. if suffix is None: suffix = 0x4
  100. self.update(self._bytepad(self._encode_string(N)
  101. + self._encode_string(S)))
  102. if suffix is None: suffix = 0x1
  103. self._suffix = suffix
  104. self.update(prefix)
  105. else:
  106. self._F = copy_of._F.copy()
  107. self._st = copy_of._st.copy()
  108. self._pos = copy_of._pos
  109. self._suffix = copy_of._suffix
  110. # rate_bytes and out_bytes should be public, I guess?
  111. self.rate_bytes = copy_of.rate_bytes
  112. self.out_bytes = copy_of.out_bytes
  113. def copy(self):
  114. """Copy the state of the hash"""
  115. return KeccakHash(copy_of=self)
  116. @staticmethod
  117. def _encode_string(string):
  118. return (bytearray(KeccakHash._left_encode(8*len(string)))
  119. + bytearray(string))
  120. def update(self,string):
  121. """Update the hash with a new state"""
  122. for b in string:
  123. if isinstance(b,str): b = ord(b[0])
  124. self._st[self._pos] ^= b
  125. self._pos += 1
  126. if self._pos >= self.rate_bytes:
  127. self._pos = 0
  128. self._st = self._F(self._st)
  129. @staticmethod
  130. def _left_encode(n):
  131. output = []
  132. while n > 0 or len(output)==0:
  133. output = [int(n % 256)] + output
  134. n >>= 8
  135. return bytearray([len(output)] + output)
  136. def _bytepad(self,string):
  137. w = self.rate_bytes
  138. string = self._left_encode(w) + bytearray(string)
  139. extra = (w - (len(string) % w)) % w
  140. string = string + bytearray(extra)
  141. return string
  142. def digest_it(self):
  143. """
  144. Return the output of the hash, as an iterator.
  145. Does not modify or destroy the context.
  146. """
  147. assert self._pos < self.rate_bytes
  148. i = 0
  149. st = bytearray(self._st)
  150. st[self._pos] ^= self._suffix
  151. st[self.rate_bytes-1] ^= 0x80
  152. while True:
  153. if i % self.rate_bytes == 0: st = self._F(st)
  154. yield st[i % self.rate_bytes]
  155. i += 1
  156. if self.out_bytes is not None and i == self.out_bytes:
  157. return
  158. def digest(self,length=None):
  159. """
  160. Return [length] bytes of the output of the hash.
  161. Does not modify or destroy the context.
  162. If length and out_bytes are not defined, return an iterator.
  163. """
  164. if length is None and self.out_bytes is None:
  165. return self.digest_it()
  166. elif length is None:
  167. length = self.out_bytes
  168. elif self.out_bytes is None:
  169. pass
  170. elif self.out_bytes < length:
  171. raise KeccakError("Requested output is too long")
  172. return bytearray(itertools.islice(self.digest_it(),length))
  173. @classmethod
  174. def hash(cls,string,length=None,*args,**kwargs):
  175. """Output the hash of a string."""
  176. obj = cls(*args,**kwargs)
  177. obj.update(string)
  178. return obj.digest(length)
  179. def KeccakMode(name,*args,**kwargs):
  180. """
  181. Keccak hasher with mode filled in
  182. """
  183. class Derived(KeccakHash):
  184. def __init__(self):
  185. super(Derived,self).__init__(*args,**kwargs)
  186. def copy(self): return Derived(copy_of=self)
  187. Derived.__name__ = name
  188. return Derived
  189. SHA3_224 = KeccakMode("SHA3_224",out_bytes=224//8,suffix=6)
  190. SHA3_256 = KeccakMode("SHA3_256",out_bytes=256//8,suffix=6)
  191. SHA3_384 = KeccakMode("SHA3_384",out_bytes=384//8,suffix=6)
  192. SHA3_512 = KeccakMode("SHA3_512",out_bytes=512//8,suffix=6)
  193. SHAKE128 = KeccakMode("SHAKE128",rate_bytes=200-128//4,suffix=0x1F)
  194. SHAKE256 = KeccakMode("SHAKE256",rate_bytes=200-256//4,suffix=0x1F)
  195. def cSHAKE128(S,N=""):
  196. return KeccakMode("cSHAKE128",S=S,N=N,rate_bytes=200-128//4)
  197. def cSHAKE256(S,N=""):
  198. return KeccakMode("cSHAKE256",S=S,N=N,rate_bytes=200-256/4)
  199. class SimpleTestVectors(unittest.TestCase):
  200. def test(self):
  201. message = "abcdbcdecdefdefgefghfghighijhijkijkljklmklmnlmnomnopnopq"
  202. self.assertEqual(SHA3_224.hash(message),
  203. "8a24108b154ada21c9fd5574494479ba5c7e7ab76ef264ead0fcce33".decode("hex"))
  204. self.assertEqual(SHA3_256.hash(message),
  205. "41c0dba2a9d6240849100376a8235e2c82e1b9998a999e21db32dd97496d3376".decode("hex"))
  206. self.assertEqual(SHA3_384.hash(message),
  207. "991c665755eb3a4b6bbdfb75c78a492e8c56a22c5c4d7e429bfdbc32b9d4ad5aa04a1f076e62fea19eef51acd0657c22".decode("hex"))
  208. self.assertEqual(SHA3_512.hash(message),
  209. ("04a371e84ecfb5b8b77cb48610fca8182dd457ce6f326a0fd3d7ec2f1e91636d"
  210. +"ee691fbe0c985302ba1b0d8dc78c086346b533b49c030d99a27daf1139d6e75e").decode("hex"))
  211. self.assertEqual(SHAKE128.hash(message,128/4),
  212. "1a96182b50fb8c7e74e0a707788f55e98209b8d91fade8f32f8dd5cff7bf21f5".decode("hex"))
  213. self.assertEqual(SHAKE256.hash(message,256/4),
  214. ("4d8c2dd2435a0128eefbb8c36f6f87133a7911e18d979ee1ae6be5d4fd2e3329"
  215. +"40d8688a4e6a59aa8060f1f9bc996c05aca3c696a8b66279dc672c740bb224ec").decode("hex"))
  216. self.assertEqual(cSHAKE128("Email Signature").hash(bytearray((i for i in xrange(0x04))),32),
  217. "c1c36925b6409a04f1b504fcbca9d82b4017277cb5ed2b2065fc1d3814d5aaf5".decode("hex"))
  218. self.assertEqual(cSHAKE128("Email Signature").hash(bytearray((i for i in xrange(0xc8))),32),
  219. "c5221d50e4f822d96a2e8881a961420f294b7b24fe3d2094baed2c6524cc166b".decode("hex"))
  220. # TODO: test cSHAKE256; more vectors; Monte Carlo