| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244 |
- """passlib.utils.scrypt._builtin -- scrypt() kdf in pure-python"""
- #==========================================================================
- # imports
- #==========================================================================
- # core
- import operator
- import struct
- # pkg
- from passlib.utils.compat import izip
- from passlib.crypto.digest import pbkdf2_hmac
- from passlib.crypto.scrypt._salsa import salsa20
- # local
- __all__ =[
- "ScryptEngine",
- ]
- #==========================================================================
- # scrypt engine
- #==========================================================================
- class ScryptEngine(object):
- """
- helper class used to run scrypt kdf, see scrypt() for frontend
- .. warning::
- this class does NO validation of the input ranges or types.
- it's not intended to be used directly,
- but only as a backend for :func:`passlib.utils.scrypt.scrypt()`.
- """
- #=================================================================
- # instance attrs
- #=================================================================
- # primary scrypt config parameters
- n = 0
- r = 0
- p = 0
- # derived values & objects
- smix_bytes = 0
- iv_bytes = 0
- bmix_len = 0
- bmix_half_len = 0
- bmix_struct = None
- integerify = None
- #=================================================================
- # frontend
- #=================================================================
- @classmethod
- def execute(cls, secret, salt, n, r, p, keylen):
- """create engine & run scrypt() hash calculation"""
- return cls(n, r, p).run(secret, salt, keylen)
- #=================================================================
- # init
- #=================================================================
- def __init__(self, n, r, p):
- # store config
- self.n = n
- self.r = r
- self.p = p
- self.smix_bytes = r << 7 # num bytes in smix input - 2*r*16*4
- self.iv_bytes = self.smix_bytes * p
- self.bmix_len = bmix_len = r << 5 # length of bmix block list - 32*r integers
- self.bmix_half_len = r << 4
- assert struct.calcsize("I") == 4
- self.bmix_struct = struct.Struct("<" + str(bmix_len) + "I")
- # use optimized bmix for certain cases
- if r == 1:
- self.bmix = self._bmix_1
- # pick best integerify function - integerify(bmix_block) should
- # take last 64 bytes of block and return a little-endian integer.
- # since it's immediately converted % n, we only have to extract
- # the first 32 bytes if n < 2**32 - which due to the current
- # internal representation, is already unpacked as a 32-bit int.
- if n <= 0xFFFFffff:
- integerify = operator.itemgetter(-16)
- else:
- assert n <= 0xFFFFffffFFFFffff
- ig1 = operator.itemgetter(-16)
- ig2 = operator.itemgetter(-17)
- def integerify(X):
- return ig1(X) | (ig2(X)<<32)
- self.integerify = integerify
- #=================================================================
- # frontend
- #=================================================================
- def run(self, secret, salt, keylen):
- """
- run scrypt kdf for specified secret, salt, and keylen
- .. note::
- * time cost is ``O(n * r * p)``
- * mem cost is ``O(n * r)``
- """
- # stretch salt into initial byte array via pbkdf2
- iv_bytes = self.iv_bytes
- input = pbkdf2_hmac("sha256", secret, salt, rounds=1, keylen=iv_bytes)
- # split initial byte array into 'p' mflen-sized chunks,
- # and run each chunk through smix() to generate output chunk.
- smix = self.smix
- if self.p == 1:
- output = smix(input)
- else:
- # XXX: *could* use threading here, if really high p values encountered,
- # but would tradeoff for more memory usage.
- smix_bytes = self.smix_bytes
- output = b''.join(
- smix(input[offset:offset+smix_bytes])
- for offset in range(0, iv_bytes, smix_bytes)
- )
- # stretch final byte array into output via pbkdf2
- return pbkdf2_hmac("sha256", secret, output, rounds=1, keylen=keylen)
- #=================================================================
- # smix() helper
- #=================================================================
- def smix(self, input):
- """run SCrypt smix function on a single input block
- :arg input:
- byte string containing input data.
- interpreted as 32*r little endian 4 byte integers.
- :returns:
- byte string containing output data
- derived by mixing input using n & r parameters.
- .. note:: time & mem cost are both ``O(n * r)``
- """
- # gather locals
- bmix = self.bmix
- bmix_struct = self.bmix_struct
- integerify = self.integerify
- n = self.n
- # parse input into 32*r integers ('X' in scrypt source)
- # mem cost -- O(r)
- buffer = list(bmix_struct.unpack(input))
- # starting with initial buffer contents, derive V s.t.
- # V[0]=initial_buffer ... V[i] = bmix(V[i-1], V[i-1]) ... V[n-1] = bmix(V[n-2], V[n-2])
- # final buffer contents should equal bmix(V[n-1], V[n-1])
- #
- # time cost -- O(n * r) -- n loops, bmix is O(r)
- # mem cost -- O(n * r) -- V is n-element array of r-element tuples
- # NOTE: could do time / memory tradeoff to shrink size of V
- def vgen():
- i = 0
- while i < n:
- last = tuple(buffer)
- yield last
- bmix(last, buffer)
- i += 1
- V = list(vgen())
- # generate result from X & V.
- #
- # time cost -- O(n * r) -- loops n times, calls bmix() which has O(r) time cost
- # mem cost -- O(1) -- allocates nothing, calls bmix() which has O(1) mem cost
- get_v_elem = V.__getitem__
- n_mask = n - 1
- i = 0
- while i < n:
- j = integerify(buffer) & n_mask
- result = tuple(a ^ b for a, b in izip(buffer, get_v_elem(j)))
- bmix(result, buffer)
- i += 1
- # # NOTE: we could easily support arbitrary values of ``n``, not just powers of 2,
- # # but very few implementations have that ability, so not enabling it for now...
- # if not n_is_log_2:
- # while i < n:
- # j = integerify(buffer) % n
- # tmp = tuple(a^b for a,b in izip(buffer, get_v_elem(j)))
- # bmix(tmp,buffer)
- # i += 1
- # repack tmp
- return bmix_struct.pack(*buffer)
- #=================================================================
- # bmix() helper
- #=================================================================
- def bmix(self, source, target):
- """
- block mixing function used by smix()
- uses salsa20/8 core to mix block contents.
- :arg source:
- source to read from.
- should be list of 32*r 4-byte integers
- (2*r salsa20 blocks).
- :arg target:
- target to write to.
- should be list with same size as source.
- the existing value of this buffer is ignored.
- .. warning::
- this operates *in place* on target,
- so source & target should NOT be same list.
- .. note::
- * time cost is ``O(r)`` -- loops 16*r times, salsa20() has ``O(1)`` cost.
- * memory cost is ``O(1)`` -- salsa20() uses 16 x uint4,
- all other operations done in-place.
- """
- ## assert source is not target
- # Y[-1] = B[2r-1], Y[i] = hash( Y[i-1] xor B[i])
- # B' <-- (Y_0, Y_2 ... Y_{2r-2}, Y_1, Y_3 ... Y_{2r-1}) */
- half = self.bmix_half_len # 16*r out of 32*r - start of Y_1
- tmp = source[-16:] # 'X' in scrypt source
- siter = iter(source)
- j = 0
- while j < half:
- jn = j+16
- target[j:jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter))
- target[half+j:half+jn] = tmp = salsa20(a ^ b for a, b in izip(tmp, siter))
- j = jn
- def _bmix_1(self, source, target):
- """special bmix() method optimized for ``r=1`` case"""
- B = source[16:]
- target[:16] = tmp = salsa20(a ^ b for a, b in izip(B, iter(source)))
- target[16:] = salsa20(a ^ b for a, b in izip(tmp, B))
- #=================================================================
- # eoc
- #=================================================================
- #==========================================================================
- # eof
- #==========================================================================
|