_builtin.py 8.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244
  1. """passlib.utils.scrypt._builtin -- scrypt() kdf in pure-python"""
  2. #==========================================================================
  3. # imports
  4. #==========================================================================
  5. # core
  6. import operator
  7. import struct
  8. # pkg
  9. from passlib.utils.compat import izip
  10. from passlib.crypto.digest import pbkdf2_hmac
  11. from passlib.crypto.scrypt._salsa import salsa20
  12. # local
  13. __all__ =[
  14. "ScryptEngine",
  15. ]
  16. #==========================================================================
  17. # scrypt engine
  18. #==========================================================================
  19. class ScryptEngine(object):
  20. """
  21. helper class used to run scrypt kdf, see scrypt() for frontend
  22. .. warning::
  23. this class does NO validation of the input ranges or types.
  24. it's not intended to be used directly,
  25. but only as a backend for :func:`passlib.utils.scrypt.scrypt()`.
  26. """
  27. #=================================================================
  28. # instance attrs
  29. #=================================================================
  30. # primary scrypt config parameters
  31. n = 0
  32. r = 0
  33. p = 0
  34. # derived values & objects
  35. smix_bytes = 0
  36. iv_bytes = 0
  37. bmix_len = 0
  38. bmix_half_len = 0
  39. bmix_struct = None
  40. integerify = None
  41. #=================================================================
  42. # frontend
  43. #=================================================================
  44. @classmethod
  45. def execute(cls, secret, salt, n, r, p, keylen):
  46. """create engine & run scrypt() hash calculation"""
  47. return cls(n, r, p).run(secret, salt, keylen)
  48. #=================================================================
  49. # init
  50. #=================================================================
  51. def __init__(self, n, r, p):
  52. # store config
  53. self.n = n
  54. self.r = r
  55. self.p = p
  56. self.smix_bytes = r << 7 # num bytes in smix input - 2*r*16*4
  57. self.iv_bytes = self.smix_bytes * p
  58. self.bmix_len = bmix_len = r << 5 # length of bmix block list - 32*r integers
  59. self.bmix_half_len = r << 4
  60. assert struct.calcsize("I") == 4
  61. self.bmix_struct = struct.Struct("<" + str(bmix_len) + "I")
  62. # use optimized bmix for certain cases
  63. if r == 1:
  64. self.bmix = self._bmix_1
  65. # pick best integerify function - integerify(bmix_block) should
  66. # take last 64 bytes of block and return a little-endian integer.
  67. # since it's immediately converted % n, we only have to extract
  68. # the first 32 bytes if n < 2**32 - which due to the current
  69. # internal representation, is already unpacked as a 32-bit int.
  70. if n <= 0xFFFFffff:
  71. integerify = operator.itemgetter(-16)
  72. else:
  73. assert n <= 0xFFFFffffFFFFffff
  74. ig1 = operator.itemgetter(-16)
  75. ig2 = operator.itemgetter(-17)
  76. def integerify(X):
  77. return ig1(X) | (ig2(X)<<32)
  78. self.integerify = integerify
  79. #=================================================================
  80. # frontend
  81. #=================================================================
  82. def run(self, secret, salt, keylen):
  83. """
  84. run scrypt kdf for specified secret, salt, and keylen
  85. .. note::
  86. * time cost is ``O(n * r * p)``
  87. * mem cost is ``O(n * r)``
  88. """
  89. # stretch salt into initial byte array via pbkdf2
  90. iv_bytes = self.iv_bytes
  91. input = pbkdf2_hmac("sha256", secret, salt, rounds=1, keylen=iv_bytes)
  92. # split initial byte array into 'p' mflen-sized chunks,
  93. # and run each chunk through smix() to generate output chunk.
  94. smix = self.smix
  95. if self.p == 1:
  96. output = smix(input)
  97. else:
  98. # XXX: *could* use threading here, if really high p values encountered,
  99. # but would tradeoff for more memory usage.
  100. smix_bytes = self.smix_bytes
  101. output = b''.join(
  102. smix(input[offset:offset+smix_bytes])
  103. for offset in range(0, iv_bytes, smix_bytes)
  104. )
  105. # stretch final byte array into output via pbkdf2
  106. return pbkdf2_hmac("sha256", secret, output, rounds=1, keylen=keylen)
  107. #=================================================================
  108. # smix() helper
  109. #=================================================================
  110. def smix(self, input):
  111. """run SCrypt smix function on a single input block
  112. :arg input:
  113. byte string containing input data.
  114. interpreted as 32*r little endian 4 byte integers.
  115. :returns:
  116. byte string containing output data
  117. derived by mixing input using n & r parameters.
  118. .. note:: time & mem cost are both ``O(n * r)``
  119. """
  120. # gather locals
  121. bmix = self.bmix
  122. bmix_struct = self.bmix_struct
  123. integerify = self.integerify
  124. n = self.n
  125. # parse input into 32*r integers ('X' in scrypt source)
  126. # mem cost -- O(r)
  127. buffer = list(bmix_struct.unpack(input))
  128. # starting with initial buffer contents, derive V s.t.
  129. # V[0]=initial_buffer ... V[i] = bmix(V[i-1], V[i-1]) ... V[n-1] = bmix(V[n-2], V[n-2])
  130. # final buffer contents should equal bmix(V[n-1], V[n-1])
  131. #
  132. # time cost -- O(n * r) -- n loops, bmix is O(r)
  133. # mem cost -- O(n * r) -- V is n-element array of r-element tuples
  134. # NOTE: could do time / memory tradeoff to shrink size of V
  135. def vgen():
  136. i = 0
  137. while i < n:
  138. last = tuple(buffer)
  139. yield last
  140. bmix(last, buffer)
  141. i += 1
  142. V = list(vgen())
  143. # generate result from X & V.
  144. #
  145. # time cost -- O(n * r) -- loops n times, calls bmix() which has O(r) time cost
  146. # mem cost -- O(1) -- allocates nothing, calls bmix() which has O(1) mem cost
  147. get_v_elem = V.__getitem__
  148. n_mask = n - 1
  149. i = 0
  150. while i < n:
  151. j = integerify(buffer) & n_mask
  152. result = tuple(a ^ b for a, b in izip(buffer, get_v_elem(j)))
  153. bmix(result, buffer)
  154. i += 1
  155. # # NOTE: we could easily support arbitrary values of ``n``, not just powers of 2,
  156. # # but very few implementations have that ability, so not enabling it for now...
  157. # if not n_is_log_2:
  158. # while i < n:
  159. # j = integerify(buffer) % n
  160. # tmp = tuple(a^b for a,b in izip(buffer, get_v_elem(j)))
  161. # bmix(tmp,buffer)
  162. # i += 1
  163. # repack tmp
  164. return bmix_struct.pack(*buffer)
  165. #=================================================================
  166. # bmix() helper
  167. #=================================================================
  168. def bmix(self, source, target):
  169. """
  170. block mixing function used by smix()
  171. uses salsa20/8 core to mix block contents.
  172. :arg source:
  173. source to read from.
  174. should be list of 32*r 4-byte integers
  175. (2*r salsa20 blocks).
  176. :arg target:
  177. target to write to.
  178. should be list with same size as source.
  179. the existing value of this buffer is ignored.
  180. .. warning::
  181. this operates *in place* on target,
  182. so source & target should NOT be same list.
  183. .. note::
  184. * time cost is ``O(r)`` -- loops 16*r times, salsa20() has ``O(1)`` cost.
  185. * memory cost is ``O(1)`` -- salsa20() uses 16 x uint4,
  186. all other operations done in-place.
  187. """
  188. ## assert source is not target
  189. # Y[-1] = B[2r-1], Y[i] = hash( Y[i-1] xor B[i])
  190. # B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
  191. half = self.bmix_half_len # 16*r out of 32*r - start of Y_1
  192. tmp = source[-16:] # 'X' in scrypt source
  193. siter = iter(source)
  194. j = 0
  195. while j < half:
  196. jn = j+16
  197. target[j:jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter))
  198. target[half+j:half+jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter))
  199. j = jn
  200. def _bmix_1(self, source, target):
  201. """special bmix() method optimized for ``r=1`` case"""
  202. B = source[16:]
  203. target[:16] = tmp = salsa20(a ^ b for a, b in izip(B, iter(source)))
  204. target[16:] = salsa20(a ^ b for a, b in izip(tmp, B))
  205. #=================================================================
  206. # eoc
  207. #=================================================================
  208. #==========================================================================
  209. # eof
  210. #==========================================================================