KangarooTwelve.py 7.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. # ===================================================================
  2. #
  3. # Copyright (c) 2021, Legrandin <helderijs@gmail.com>
  4. # All rights reserved.
  5. #
  6. # Redistribution and use in source and binary forms, with or without
  7. # modification, are permitted provided that the following conditions
  8. # are met:
  9. #
  10. # 1. Redistributions of source code must retain the above copyright
  11. # notice, this list of conditions and the following disclaimer.
  12. # 2. Redistributions in binary form must reproduce the above copyright
  13. # notice, this list of conditions and the following disclaimer in
  14. # the documentation and/or other materials provided with the
  15. # distribution.
  16. #
  17. # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
  18. # "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
  19. # LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
  20. # FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
  21. # COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
  22. # INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
  23. # BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
  24. # LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
  25. # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
  26. # LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
  27. # ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
  28. # POSSIBILITY OF SUCH DAMAGE.
  29. # ===================================================================
  30. from Crypto.Util.number import long_to_bytes
  31. from Crypto.Util.py3compat import bchr
  32. from . import TurboSHAKE128
  33. def _length_encode(x):
  34. if x == 0:
  35. return b'\x00'
  36. S = long_to_bytes(x)
  37. return S + bchr(len(S))
  38. # Possible states for a KangarooTwelve instance, which depend on the amount of data processed so far.
  39. SHORT_MSG = 1 # Still within the first 8192 bytes, but it is not certain we will exceed them.
  40. LONG_MSG_S0 = 2 # Still within the first 8192 bytes, and it is certain we will exceed them.
  41. LONG_MSG_SX = 3 # Beyond the first 8192 bytes.
  42. SQUEEZING = 4 # No more data to process.
  43. class K12_XOF(object):
  44. """A KangarooTwelve hash object.
  45. Do not instantiate directly.
  46. Use the :func:`new` function.
  47. """
  48. def __init__(self, data, custom):
  49. if custom == None:
  50. custom = b''
  51. self._custom = custom + _length_encode(len(custom))
  52. self._state = SHORT_MSG
  53. self._padding = None # Final padding is only decided in read()
  54. # Internal hash that consumes FinalNode
  55. # The real domain separation byte will be known before squeezing
  56. self._hash1 = TurboSHAKE128.new(domain=1)
  57. self._length1 = 0
  58. # Internal hash that produces CV_i (reset each time)
  59. self._hash2 = None
  60. self._length2 = 0
  61. # Incremented by one for each 8192-byte block
  62. self._ctr = 0
  63. if data:
  64. self.update(data)
  65. def update(self, data):
  66. """Hash the next piece of data.
  67. .. note::
  68. For better performance, submit chunks with a length multiple of 8192 bytes.
  69. Args:
  70. data (byte string/byte array/memoryview): The next chunk of the
  71. message to hash.
  72. """
  73. if self._state == SQUEEZING:
  74. raise TypeError("You cannot call 'update' after the first 'read'")
  75. if self._state == SHORT_MSG:
  76. next_length = self._length1 + len(data)
  77. if next_length + len(self._custom) <= 8192:
  78. self._length1 = next_length
  79. self._hash1.update(data)
  80. return self
  81. # Switch to tree hashing
  82. self._state = LONG_MSG_S0
  83. if self._state == LONG_MSG_S0:
  84. data_mem = memoryview(data)
  85. assert(self._length1 < 8192)
  86. dtc = min(len(data), 8192 - self._length1)
  87. self._hash1.update(data_mem[:dtc])
  88. self._length1 += dtc
  89. if self._length1 < 8192:
  90. return self
  91. # Finish hashing S_0 and start S_1
  92. assert(self._length1 == 8192)
  93. divider = b'\x03' + b'\x00' * 7
  94. self._hash1.update(divider)
  95. self._length1 += 8
  96. self._hash2 = TurboSHAKE128.new(domain=0x0B)
  97. self._length2 = 0
  98. self._ctr = 1
  99. self._state = LONG_MSG_SX
  100. return self.update(data_mem[dtc:])
  101. # LONG_MSG_SX
  102. assert(self._state == LONG_MSG_SX)
  103. index = 0
  104. len_data = len(data)
  105. # All iteractions could actually run in parallel
  106. data_mem = memoryview(data)
  107. while index < len_data:
  108. new_index = min(index + 8192 - self._length2, len_data)
  109. self._hash2.update(data_mem[index:new_index])
  110. self._length2 += new_index - index
  111. index = new_index
  112. if self._length2 == 8192:
  113. cv_i = self._hash2.read(32)
  114. self._hash1.update(cv_i)
  115. self._length1 += 32
  116. self._hash2._reset()
  117. self._length2 = 0
  118. self._ctr += 1
  119. return self
  120. def read(self, length):
  121. """
  122. Produce more bytes of the digest.
  123. .. note::
  124. You cannot use :meth:`update` anymore after the first call to
  125. :meth:`read`.
  126. Args:
  127. length (integer): the amount of bytes this method must return
  128. :return: the next piece of XOF output (of the given length)
  129. :rtype: byte string
  130. """
  131. custom_was_consumed = False
  132. if self._state == SHORT_MSG:
  133. self._hash1.update(self._custom)
  134. self._padding = 0x07
  135. self._state = SQUEEZING
  136. if self._state == LONG_MSG_S0:
  137. self.update(self._custom)
  138. custom_was_consumed = True
  139. assert(self._state == LONG_MSG_SX)
  140. if self._state == LONG_MSG_SX:
  141. if not custom_was_consumed:
  142. self.update(self._custom)
  143. # Is there still some leftover data in hash2?
  144. if self._length2 > 0:
  145. cv_i = self._hash2.read(32)
  146. self._hash1.update(cv_i)
  147. self._length1 += 32
  148. self._hash2._reset()
  149. self._length2 = 0
  150. self._ctr += 1
  151. trailer = _length_encode(self._ctr - 1) + b'\xFF\xFF'
  152. self._hash1.update(trailer)
  153. self._padding = 0x06
  154. self._state = SQUEEZING
  155. self._hash1._domain = self._padding
  156. return self._hash1.read(length)
  157. def new(self, data=None, custom=b''):
  158. return type(self)(data, custom)
  159. def new(data=None, custom=None):
  160. """Return a fresh instance of a KangarooTwelve object.
  161. Args:
  162. data (bytes/bytearray/memoryview):
  163. Optional.
  164. The very first chunk of the message to hash.
  165. It is equivalent to an early call to :meth:`update`.
  166. custom (bytes):
  167. Optional.
  168. A customization byte string.
  169. :Return: A :class:`K12_XOF` object
  170. """
  171. return K12_XOF(data, custom)