Something went wrong on our end
#!/usr/bin/env python
# Copyright (c) 2014 Richard Moore
# Copyright (c) 2014 Jan Varho
#
# Permission is hereby granted, free of charge, to any person obtaining a copy
# of this software and associated documentation files (the "Software"), to deal
# in the Software without restriction, including without limitation the rights
# to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
# copies of the Software, and to permit persons to whom the Software is
# furnished to do so, subject to the following conditions:
#
# The above copyright notice and this permission notice shall be included in
# all copies or substantial portions of the Software.
#
# THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
# IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
# FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
# AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
# LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
# OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
# THE SOFTWARE.
"""Python implementation of Scrypt password-based key derivation function"""
# Scrypt definition:
# http://www.tarsnap.com/scrypt/scrypt.pdf
# It was originally written for a pure-Python Litecoin CPU miner:
# https://github.com/ricmoo/nightminer
# Imported to this project from:
# https://github.com/ricmoo/pyscrypt
# And owes thanks to:
# https://github.com/wg/scrypt
import hashlib, hmac
import struct
from . import mcf as mcf_mod
from .common import *
# Python 3.4+ have PBKDF2 in hashlib, so use it...
if 'pbkdf2_hmac' in dir(hashlib):
_pbkdf2 = hashlib.pbkdf2_hmac
else:
# but fall back to Python implementation in < 3.4
from pbkdf2 import pbkdf2_hmac as _pbkdf2
def array_overwrite(source, s_start, dest, d_start, length):
dest[d_start:d_start + length] = source[s_start:s_start + length]
def blockxor(source, s_start, dest, d_start, length):
for i in xrange(length):
dest[d_start + i] ^= source[s_start + i]
def integerify(B, r):
"""A bijection from ({0, 1} ** k) to {0, ..., (2 ** k) - 1"""
Bi = (2 * r - 1) * 16
return B[Bi]
def R(X, destination, a1, a2, b):
"""A single Salsa20 row operation"""
a = (X[a1] + X[a2]) & 0xffffffff
X[destination] ^= ((a << b) | (a >> (32 - b)))
def salsa20_8(B, x, src, s_start, dest, d_start):
"""Salsa20/8 http://en.wikipedia.org/wiki/Salsa20"""
# Merged blockxor for speed
for i in xrange(16):
x[i] = B[i] = B[i] ^ src[s_start + i]
# This is the actual Salsa 20/8: four identical double rounds
for i in xrange(4):
R(x, 4, 0,12, 7);R(x, 8, 4, 0, 9);R(x,12, 8, 4,13);R(x, 0,12, 8,18)
R(x, 9, 5, 1, 7);R(x,13, 9, 5, 9);R(x, 1,13, 9,13);R(x, 5, 1,13,18)
R(x,14,10, 6, 7);R(x, 2,14,10, 9);R(x, 6, 2,14,13);R(x,10, 6, 2,18)
R(x, 3,15,11, 7);R(x, 7, 3,15, 9);R(x,11, 7, 3,13);R(x,15,11, 7,18)
R(x, 1, 0, 3, 7);R(x, 2, 1, 0, 9);R(x, 3, 2, 1,13);R(x, 0, 3, 2,18)
R(x, 6, 5, 4, 7);R(x, 7, 6, 5, 9);R(x, 4, 7, 6,13);R(x, 5, 4, 7,18)
R(x,11,10, 9, 7);R(x, 8,11,10, 9);R(x, 9, 8,11,13);R(x,10, 9, 8,18)
R(x,12,15,14, 7);R(x,13,12,15, 9);R(x,14,13,12,13);R(x,15,14,13,18)
# While we are handling the data, write it to the correct dest.
# The latter half is still part of salsa20
for i in xrange(16):
dest[d_start + i] = B[i] = (x[i] + B[i]) & 0xffffffff
def blockmix_salsa8(BY, Yi, r):
"""Blockmix; Used by SMix"""
start = (2 * r - 1) * 16
X = BY[start:start+16] # BlockMix - 1
tmp = [0]*16
for i in xrange(2 * r): # BlockMix - 2
#blockxor(BY, i * 16, X, 0, 16) # BlockMix - 3(inner)
salsa20_8(X, tmp, BY, i * 16, BY, Yi + i*16) # BlockMix - 3(outer)
#array_overwrite(X, 0, BY, Yi + (i * 16), 16) # BlockMix - 4
for i in xrange(r): # BlockMix - 6
array_overwrite(BY, Yi + (i * 2) * 16, BY, i * 16, 16)
array_overwrite(BY, Yi + (i*2 + 1) * 16, BY, (i + r) * 16, 16)
def smix(B, Bi, r, N, V, X):
"""SMix; a specific case of ROMix based on Salsa20/8"""
array_overwrite(B, Bi, X, 0, 32 * r) # ROMix - 1
for i in xrange(N): # ROMix - 2
array_overwrite(X, 0, V, i * (32 * r), 32 * r) # ROMix - 3
blockmix_salsa8(X, 32 * r, r) # ROMix - 4
for i in xrange(N): # ROMix - 6
j = integerify(X, r) & (N - 1) # ROMix - 7
blockxor(V, j * (32 * r), X, 0, 32 * r) # ROMix - 8(inner)
blockmix_salsa8(X, 32 * r, r) # ROMix - 9(outer)
array_overwrite(X, 0, B, Bi, 32 * r) # ROMix - 10
def scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64):
"""Returns a key derived using the scrypt key-derivarion function
N must be a power of two larger than 1 but no larger than 2 ** 63 (insane)
r and p must be positive numbers such that r * p < 2 ** 30
The default values are:
N -- 2**14 (~16k)
r -- 8
p -- 1
Memory usage is proportional to N*r. Defaults require about 16 MiB.
Time taken is proportional to N*p. Defaults take <100ms of a recent x86.
The last one differs from libscrypt defaults, but matches the 'interactive'
work factor from the original paper. For long term storage where runtime of
key derivation is not a problem, you could use 16 as in libscrypt or better
yet increase N if memory is plentiful.
"""
check_args(password, salt, N, r, p, olen)
# Everything is lists of 32-bit uints for all but pbkdf2
try:
B = _pbkdf2('sha256', password, salt, 1, p * 128 * r)
B = list(struct.unpack('<%dI' % (len(B) // 4), B))
XY = [0] * (64 * r)
V = [0] * (32 * r * N)
except (MemoryError, OverflowError):
raise ValueError("scrypt parameters don't fit in memory")
for i in xrange(p):
smix(B, i * 32 * r, r, N, V, XY)
B = struct.pack('<%dI' % len(B), *B)
return _pbkdf2('sha256', password, B, 1, olen)
def scrypt_mcf(password, salt=None, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p,
prefix=SCRYPT_MCF_PREFIX_DEFAULT):
"""Derives a Modular Crypt Format hash using the scrypt KDF
Parameter space is smaller than for scrypt():
N must be a power of two larger than 1 but no larger than 2 ** 31
r and p must be positive numbers between 1 and 255
Salt must be a byte string 1-16 bytes long.
If no salt is given, a random salt of 128+ bits is used. (Recommended.)
"""
return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix)
def scrypt_mcf_check(mcf, password):
"""Returns True if the password matches the given MCF hash"""
return mcf_mod.scrypt_mcf_check(scrypt, mcf, password)
__all__ = ['scrypt', 'scrypt_mcf', 'scrypt_mcf_check']
if __name__ == "__main__":
import sys
from . import tests
tests.run_scrypt_suite(sys.modules[__name__])