Skip to content
Snippets Groups Projects
Commit 1fbdfc21 authored by inso's avatar inso
Browse files

Added fixed pylibscrypt to load libsodium.dll

parent 87051196
Branches
Tags 0.7.1
No related merge requests found
Showing
with 1557 additions and 0 deletions
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Scrypt for Python"""
__version__ = '1.4.0-git'
# First, try loading libscrypt
_done = False
try:
from .pylibscrypt import *
except ImportError:
pass
else:
_done = True
# If that didn't work, try the scrypt module
if not _done:
try:
from .pyscrypt import *
except ImportError:
pass
else:
_done = True
# Next: libsodium
if not _done:
try:
from .pylibsodium import *
except ImportError:
pass
else:
_done = True
# Unless we are on pypy, we want to try libsodium_salsa as well
if not _done:
import platform
if platform.python_implementation() != 'PyPy':
try:
from .pylibsodium_salsa import *
except ImportError:
pass
else:
_done = True
# If that didn't work either, the inlined Python version
if not _done:
from .pypyscrypt_inline import *
__all__ = ['scrypt', 'scrypt_mcf', 'scrypt_mcf_check']
File added
File added
File added
File added
File added
File added
File added
File added
File added
File added
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Simple benchmark of python vs c scrypt"""
import time
from .common import *
from .pylibscrypt import scrypt
from .pypyscrypt_inline import scrypt as pyscrypt
from .pylibsodium_salsa import scrypt as pcscrypt
# Benchmark time in seconds
tmin = 5
Nmax = 20
t1 = time.time()
for i in xrange(1, Nmax+1):
pyscrypt(b'password', b'NaCl', N=2**i)
if time.time() - t1 > tmin:
Nmax = i
break
t1 = time.time() - t1
print('Using N = 2,4,..., 2**%d' % Nmax)
print('Python scrypt took %.2fs' % t1)
t2 = time.time()
for i in xrange(1, Nmax+1):
pcscrypt(b'password', b'NaCl', N=2**i)
t2 = time.time() - t2
print('Py + C scrypt took %.2fs' % t2)
t3 = time.time()
for i in xrange(1, Nmax+1):
scrypt(b'password', b'NaCl', N=2**i)
t3 = time.time() - t3
print('C scrypt took %.2fs' % t3)
print('Python scrypt took %.2f times as long as C' % (t1 / t3))
print('Py + C scrypt took %.2f times as long as C' % (t2 / t3))
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Common variables and functions used by scrypt implementations"""
import numbers
SCRYPT_MCF_PREFIX_7 = b'$7$'
SCRYPT_MCF_PREFIX_s1 = b'$s1$'
SCRYPT_MCF_PREFIX_DEFAULT = b'$s1$'
SCRYPT_MCF_PREFIX_ANY = None
SCRYPT_N = 1<<14
SCRYPT_r = 8
SCRYPT_p = 1
# 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.
xrange = xrange if 'xrange' in globals() else range
def check_args(password, salt, N, r, p, olen=64):
if not isinstance(password, bytes):
raise TypeError('password must be a byte string')
if not isinstance(salt, bytes):
raise TypeError('salt must be a byte string')
if not isinstance(N, numbers.Integral):
raise TypeError('N must be an integer')
if not isinstance(r, numbers.Integral):
raise TypeError('r must be an integer')
if not isinstance(p, numbers.Integral):
raise TypeError('p must be an integer')
if not isinstance(olen, numbers.Integral):
raise TypeError('length must be an integer')
if N > 2**63:
raise ValueError('N cannot be larger than 2**63')
if (N & (N - 1)) or N < 2:
raise ValueError('N must be a power of two larger than 1')
if r <= 0:
raise ValueError('r must be positive')
if p <= 0:
raise ValueError('p must be positive')
if r * p >= 2**30:
raise ValueError('r * p must be less than 2 ** 30')
if olen <= 0:
raise ValueError('length must be positive')
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Fuzzes scrypt function input, comparing two implementations"""
import itertools
import random
from random import randrange as rr
import unittest
class Skip(Exception):
pass
class Fuzzer(object):
"""Fuzzes function input"""
def __init__(self, f, args, g=None, pass_good=None, pass_bad=None):
self.f = f
self.g = g
self.args = args
self.pass_good = pass_good
self.pass_bad = pass_bad
@staticmethod
def get_random_int():
return int((1<<rr(66)) * 1.3)
@staticmethod
def get_random_bytes(lrange=None, skip=None):
if lrange is None:
v = bytearray(rr(2**rr(10)))
else:
v = bytearray(rr(*lrange))
for i in range(len(v)):
v[i] = rr(256)
while v[i] == skip:
v[i] = rr(256)
return bytes(v)
def get_good_args(self):
kwargs = {}
for a in self.args:
assert isinstance(a, dict)
if 'opt' in a and a['opt'] and random.randrange(2):
continue
if 'val' in a:
kwargs[a['name']] = a['val']
elif 'vals' in a:
kwargs[a['name']] = random.choice(a['vals'])
elif 'valf' in a:
kwargs[a['name']] = a['valf']()
elif 'type' in a and a['type'] == 'int':
kwargs[a['name']] = self.get_random_int()
elif 'type' in a and a['type'] == 'bytes':
kwargs[a['name']] = self.get_random_bytes()
else:
raise ValueError
if 'none' in a and not random.randrange(10):
kwargs[a['name']] = None
if 'skip' in a and a['skip'](kwargs[a['name']]):
if 'opt' in a and a['opt']:
del kwargs[a['name']]
else:
raise Skip
return kwargs
def get_bad_args(self, kwargs=None):
kwargs = kwargs or self.get_good_args()
a = random.choice(self.args)
if not 'opt' in a:
if not random.randrange(10):
del kwargs[a['name']]
return kwargs
if not 'type' in a:
return self.get_bad_args(kwargs)
if not random.randrange(10):
wrongtype = [
self.get_random_int(), self.get_random_bytes(), None,
1.1*self.get_random_int(), 1.0*self.get_random_int()
]
if a['type'] == 'int':
del wrongtype[0]
elif a['type'] == 'bytes':
del wrongtype[1]
v = random.choice(wrongtype)
try:
if 'valf' in a:
if a['valf'](v):
return self.get_bad_args(kwargs)
if 'skip' in a and a['skip'](v):
return self.get_bad_args(kwargs)
except TypeError:
pass # Surely bad enough
kwargs[a['name']] = v
return kwargs
if a['type'] == 'int':
v = self.get_random_int()
if 'valf' in a:
if a['valf'](v):
return self.get_bad_args(kwargs)
if 'skip' in a and a['skip'](v):
return self.get_bad_args(kwargs)
kwargs[a['name']] = v
return kwargs
if a['type'] == 'bytes' and 'valf' in a:
v = self.get_random_bytes()
if not a['valf'](v):
kwargs[a['name']] = v
return kwargs
return self.get_bad_args(kwargs)
def fuzz_good_run(self, tc):
try:
kwargs = self.get_good_args()
r1 = self.f(**kwargs)
r2 = self.g(**kwargs) if self.g is not None else None
if self.g is not None:
r2 = self.g(**kwargs)
except Skip:
tc.skipTest('slow')
except Exception as e:
assert False, ('unexpected exception', kwargs, e)
try:
if self.pass_good:
tc.assertTrue(self.pass_good(r1, r2, kwargs),
msg=('unexpected output', r1, r2, kwargs))
else:
if self.g is not None:
assert r1 == r2, ('f and g mismatch', kwargs, r1, r2)
tc.assertTrue(r1)
except Exception as e:
print ('unexpected exception', kwargs, r1, r2, e)
raise
def fuzz_bad(self, f=None, kwargs=None):
f = f or self.f
kwargs = kwargs or self.get_bad_args()
return f(**kwargs)
def fuzz_bad_run(self, tc):
try:
kwargs = self.get_bad_args()
except Skip:
tc.skipTest('slow')
for f in ((self.f,) if not self.g else (self.f, self.g)):
try:
r = self.fuzz_bad(f, kwargs)
assert False, ('no exception', kwargs, r)
except Skip:
tc.skipTest('slow')
except AssertionError:
raise
except Exception:
pass
def testcase_good(self, tests=1, name='FuzzTestGood'):
testfs = {}
for i in range(tests):
testfs['test_fuzz_good_%d' % i] = lambda s: self.fuzz_good_run(s)
t = type(name, (unittest.TestCase,), testfs)
return t
def testcase_bad(self, tests=1, name='FuzzTestBad'):
testfs = {}
for i in range(tests):
testfs['test_fuzz_bad_%d' % i] = lambda s: self.fuzz_bad_run(s)
t = type(name, (unittest.TestCase,), testfs)
return t
def generate_tests(self, suite, count, name):
loader = unittest.defaultTestLoader
suite.addTest(
loader.loadTestsFromTestCase(
self.testcase_good(count, name + 'Good')
)
)
suite.addTest(
loader.loadTestsFromTestCase(
self.testcase_bad(count, name + 'Bad')
)
)
if __name__ == "__main__":
import argparse
parser = argparse.ArgumentParser(description='Fuzz testing')
parser.add_argument('-c', '--count', type=int, default=100)
parser.add_argument('-f', '--failfast', action='store_true')
clargs = parser.parse_args()
modules = []
try:
from . import pylibscrypt
modules.append((pylibscrypt, 'pylibscrypt'))
except ImportError:
pass
try:
from . import pyscrypt
modules.append((pyscrypt, 'pyscrypt'))
except ImportError:
pass
try:
from . import pylibsodium_salsa
modules.append((pylibsodium_salsa, 'pylibsodium_salsa'))
except ImportError:
pass
try:
from . import pylibsodium
modules.append((pylibsodium, 'pylibsodium'))
except ImportError:
pass
try:
from . import pypyscrypt_inline as pypyscrypt
modules.append((pypyscrypt, 'pypyscrypt'))
except ImportError:
pass
scrypt_args = (
{'name':'password', 'type':'bytes'},
{'name':'salt', 'type':'bytes'},
{
'name':'N', 'type':'int', 'opt':False,
'valf':(lambda N=None: 2**rr(1,6) if N is None else
1 < N < 2**64 and not (N & (N - 1))),
'skip':(lambda N: (N & (N - 1)) == 0 and N > 32 and N < 2**64)
},
{
'name':'r', 'type':'int', 'opt':True,
'valf':(lambda r=None: rr(1, 16) if r is None else 0<r<2**30),
'skip':(lambda r: r > 16 and r < 2**30)
},
{
'name':'p', 'type':'int', 'opt':True,
'valf':(lambda p=None: rr(1, 16) if p is None else 0<p<2**30),
'skip':(lambda p: p > 16 and p < 2**30)
},
{
'name':'olen', 'type':'int', 'opt':True,
'valf':(lambda l=None: rr(1, 1000) if l is None else l >= 0),
'skip':(lambda l: l < 0 or l > 1024)
},
)
scrypt_mcf_args = (
{
'name':'password', 'type':'bytes',
'valf':(lambda p=None: Fuzzer.get_random_bytes(skip=0) if p is None
else not b'\0' in p)
},
{
'name':'salt', 'type':'bytes', 'opt':False, 'none':True,
'valf':(lambda s=None: Fuzzer.get_random_bytes((1,17)) if s is None
else 1 <= len(s) <= 16)
},
{
'name':'N', 'type':'int', 'opt':False,
'valf':(lambda N=None: 2**rr(1,6) if N is None else
1 < N < 2**64 and not (N & (N - 1))),
'skip':(lambda N: (N & (N - 1)) == 0 and N > 32 and N < 2**64)
},
{
'name':'r', 'type':'int', 'opt':True,
'valf':(lambda r=None: rr(1, 16) if r is None else 0<r<2**30),
'skip':(lambda r: r > 16 and r < 2**30)
},
{
'name':'p', 'type':'int', 'opt':True,
'valf':(lambda p=None: rr(1, 16) if p is None else 0<p<2**30),
'skip':(lambda p: p > 16 and p < 2**30)
},
{
'name':'prefix', 'type':'bytes', 'opt':True,
'vals':(b'$s1$', b'$7$'),
'skip':(lambda p: p is None)
}
)
random.shuffle(modules)
suite = unittest.TestSuite()
loader = unittest.defaultTestLoader
for m1, m2 in itertools.permutations(modules, 2):
Fuzzer(
m1[0].scrypt, scrypt_args, m2[0].scrypt,
pass_good=lambda r1, r2, a: (
isinstance(r1, bytes) and
(r2 is None or r1 == r2) and
(len(r1) == 64 if 'olen' not in a else len(r1) == a['olen'])
)
).generate_tests(suite, clargs.count, m1[1])
Fuzzer(
m1[0].scrypt_mcf, scrypt_mcf_args, m2[0].scrypt_mcf,
pass_good=lambda r1, r2, a: (
m2[0].scrypt_mcf_check(r1, a['password']) and
(r2 is None or m1[0].scrypt_mcf_check(r2, a['password'])) and
(r2 is None or 'salt' not in a or a['salt'] is None or r1 == r2)
)
).generate_tests(suite, clargs.count, m1[1])
unittest.TextTestRunner(failfast=clargs.failfast).run(suite)
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Inlines the salsa20 core lines into salsa20_8"""
of = open('pylibscrypt/pypyscrypt_inline.py', 'w')
assert of
def indent(line):
i = 0
while i < len(line) and line[i] == ' ':
i += 1
return i
with open('pylibscrypt/pypyscrypt.py', 'r') as f:
in_loop = False
loop_indent = 0
lc = 0
rl = []
skipping = False
for line in f:
lc += 1
i = indent(line)
if line[i:].startswith('def R('):
skipping = True
elif line[i:].startswith('def array_overwrite('):
skipping = True
elif skipping:
if line[i:].startswith('def'):
of.write(line)
skipping = False
elif line[i:].startswith('R('):
parts = line.split(';')
rl += parts
if len(rl) == 32:
# Interleave to reduce dependencies for pypy
rl1 = rl[:16]
rl2 = rl[16:]
rl1 = rl1[0::4] + rl1[1::4] + rl1[2::4] + rl1[3::4]
rl2 = rl2[0::4] + rl2[1::4] + rl2[2::4] + rl2[3::4]
rl = rl1 + rl2
for p, q in zip(rl[::2], rl[1::2]):
pvals = p.split(',')[1:]
pvals = [int(v.strip(' )\n')) for v in pvals]
qvals = q.split(',')[1:]
qvals = [int(v.strip(' )\n')) for v in qvals]
of.write(' '*i)
of.write('a = (x[%d]+x[%d]) & 0xffffffff\n' %
(pvals[1], pvals[2]))
of.write(' '*i)
of.write('b = (x[%d]+x[%d]) & 0xffffffff\n' %
(qvals[1], qvals[2]))
of.write(' '*i)
of.write('x[%d] ^= (a << %d) | (a >> %d)\n' %
(pvals[0], pvals[3], 32 - pvals[3]))
of.write(' '*i)
of.write('x[%d] ^= (b << %d) | (b >> %d)\n' %
(qvals[0], qvals[3], 32 - qvals[3]))
elif line[i:].startswith('array_overwrite('):
vals = line.split(',')
vals[0] = vals[0].split('(')[1]
vals[-1] = vals[-1].split(')')[0]
vals = [v.strip() for v in vals]
assert len(vals) == 5
of.write(' '*i)
of.write(vals[2] + '[' + vals[3] + ':(' + vals[3] + ')+(' +
vals[4] + ')] = ' + vals[0] + '[' + vals[1] + ':(' +
vals[1] + ')+(' + vals[4] + ')]\n')
else:
of.write(line)
if lc == 1:
of.write('\n# Automatically generated file, see inline.py\n')
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Modular Crypt Format support for scrypt
Compatible with libscrypt scrypt_mcf_check also supports the $7$ format.
libscrypt format:
$s1$NNrrpp$salt$hash
NN - hex encoded N log2 (two hex digits)
rr - hex encoded r in 1-255
pp - hex encoded p in 1-255
salt - base64 encoded salt 1-16 bytes decoded
hash - base64 encoded 64-byte scrypt hash
$7$ format:
$7$Nrrrrrpppppsalt$hash
N - crypt base64 N log2
rrrrr - crypt base64 r (little-endian 30 bits)
ppppp - crypt base64 p (little-endian 30 bits)
salt - raw salt (0-43 bytes that should be limited to crypt base64)
hash - crypt base64 encoded 32-byte scrypt hash (43 bytes)
(crypt base64 is base64 with the alphabet: ./0-9A-Za-z)
When reading, we are more lax, allowing salts and hashes to be longer and
incorrectly encoded, since the worst that can happen is that the password does
not verify.
"""
import base64, binascii
import os
import struct
from .common import *
def _scrypt_mcf_encode_s1(N, r, p, salt, hash):
h64 = base64.b64encode(hash)
s64 = base64.b64encode(salt)
t = 1
while 2**t < N:
t += 1
params = p + (r << 8) + (t << 16)
return (
b'$s1' +
('$%06x' % params).encode() +
b'$' + s64 +
b'$' + h64
)
def _b64decode(b64):
for b in (b64, b64 + b'=', b64 + b'=='):
try:
return base64.b64decode(b)
except (TypeError, binascii.Error):
pass
raise ValueError('Incorrect base64 in MCF')
def _scrypt_mcf_decode_s1(mcf):
s = mcf.split(b'$')
if not (mcf.startswith(b'$s1$') and len(s) == 5):
return None
params, s64, h64 = s[2:]
params = base64.b16decode(params, True)
salt = _b64decode(s64)
hash = _b64decode(h64)
if len(params) != 3:
raise ValueError('Unrecognized MCF parameters')
t, r, p = struct.unpack('3B', params)
N = 2 ** t
return N, r, p, salt, hash, len(hash)
# Crypt base 64
_cb64 = b'./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz'
_cb64a = bytearray(_cb64)
_icb64 = (
[None] * 46 +
[
0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, None, None, None, None, None,
None, None, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25,
26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, None, None, None,
None, None, None, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63
] +
[None] * 133
)
def _cb64enc(arr):
arr = bytearray(arr)
out = bytearray()
val = bits = pos = 0
for b in arr:
val += b << bits
bits += 8
while bits >= 0:
out.append(_cb64a[val & 0x3f])
bits -= 6
val = val >> 6
return bytes(out)
def _scrypt_mcf_encode_7(N, r, p, salt, hash):
t = 1
while 2**t < N:
t += 1
return (
b'$7$' +
# N
_cb64[t::64] +
# r
_cb64[r & 0x3f::64] + _cb64[(r >> 6) & 0x3f::64] +
_cb64[(r >> 12) & 0x3f::64] + _cb64[(r >> 18) & 0x3f::64] +
_cb64[(r >> 24) & 0x3f::64] +
# p
_cb64[p & 0x3f::64] + _cb64[(p >> 6) & 0x3f::64] +
_cb64[(p >> 12) & 0x3f::64] + _cb64[(p >> 18) & 0x3f::64] +
_cb64[(p >> 24) & 0x3f::64] +
# rest
salt +
b'$' + _cb64enc(hash)
)
def _cb64dec(arr):
out = bytearray()
val = bits = pos = 0
for b in arr:
val += _icb64[b] << bits
bits += 6
if bits >= 8:
out.append(val & 0xff)
bits -= 8
val >>= 8
return out
def _scrypt_mcf_decode_7(mcf):
s = mcf.split(b'$')
if not (mcf.startswith(b'$7$') and len(s) == 4):
return None
s64 = bytearray(s[2])
h64 = bytearray(s[3])
try:
N = 2 ** _icb64[s64[0]]
r = (_icb64[s64[1]] + (_icb64[s64[2]] << 6) + (_icb64[s64[3]] << 12) +
(_icb64[s64[4]] << 18) + (_icb64[s64[5]] << 24))
p = (_icb64[s64[6]] + (_icb64[s64[7]] << 6) + (_icb64[s64[8]] << 12) +
(_icb64[s64[9]] << 18) + (_icb64[s64[10]] << 24))
salt = bytes(s64[11:])
hash = bytes(_cb64dec(h64))
except (IndexError, TypeError):
raise ValueError('Unrecognized MCF format')
return N, r, p, salt, hash, len(hash)
def _scrypt_mcf_7_is_standard(mcf):
params = _scrypt_mcf_decode_7(mcf)
if params is None:
return False
N, r, p, salt, hash, hlen = params
return len(salt) == 43 and hlen == 32
def _scrypt_mcf_decode(mcf):
params = _scrypt_mcf_decode_s1(mcf)
if params is None:
params = _scrypt_mcf_decode_7(mcf)
if params is None:
raise ValueError('Unrecognized MCF hash')
return params
def scrypt_mcf(scrypt, 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 given
Expects the signature:
scrypt(password, salt, N=SCRYPT_N, r=SCRYPT_r, p=SCRYPT_p, olen=64)
If no salt is given, a random salt of 128+ bits is used. (Recommended.)
"""
if salt is not None and not (1 <= len(salt) <= 16):
raise ValueError('salt must be 1-16 bytes')
if r > 255:
raise ValueError('scrypt_mcf r out of range [1,255]')
if p > 255:
raise ValueError('scrypt_mcf p out of range [1,255]')
if N > 2**31:
raise ValueError('scrypt_mcf N out of range [2,2**31]')
if b'\0' in password:
raise ValueError('scrypt_mcf password must not contain zero bytes')
if prefix == SCRYPT_MCF_PREFIX_s1:
if salt is None:
salt = os.urandom(16)
hash = scrypt(password, salt, N, r, p)
return _scrypt_mcf_encode_s1(N, r, p, salt, hash)
elif prefix == SCRYPT_MCF_PREFIX_7 or prefix == SCRYPT_MCF_PREFIX_ANY:
if salt is None:
salt = os.urandom(32)
salt = _cb64enc(salt)
hash = scrypt(password, salt, N, r, p, 32)
return _scrypt_mcf_encode_7(N, r, p, salt, hash)
else:
raise ValueError("Unrecognized MCF format")
def scrypt_mcf_check(scrypt, mcf, password):
"""Returns True if the password matches the given MCF hash
Supports both the libscrypt $s1$ format and the $7$ format.
"""
if not isinstance(mcf, bytes):
raise TypeError
if not isinstance(password, bytes):
raise TypeError
N, r, p, salt, hash, hlen = _scrypt_mcf_decode(mcf)
h = scrypt(password, salt, N=N, r=r, p=p, olen=hlen)
return hash == h
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""PBKDF2 in pure Python, compatible with Python3.4 hashlib.pbkdf2_hmac"""
import hashlib
import hmac
import struct
from .common import *
def pbkdf2_hmac(name, password, salt, rounds, dklen=None):
"""Returns the result of the Password-Based Key Derivation Function 2"""
h = hmac.new(key=password, digestmod=lambda d=b'': hashlib.new(name, d))
hs = h.copy()
hs.update(salt)
blocks = bytearray()
dklen = hs.digest_size if dklen is None else dklen
block_count, last_size = divmod(dklen, hs.digest_size)
block_count += last_size > 0
for block_number in xrange(1, block_count + 1):
hb = hs.copy()
hb.update(struct.pack('>L', block_number))
U = bytearray(hb.digest())
if rounds > 1:
Ui = U
for i in xrange(rounds - 1):
hi = h.copy()
hi.update(Ui)
Ui = bytearray(hi.digest())
for j in xrange(hs.digest_size):
U[j] ^= Ui[j]
blocks.extend(U)
if last_size:
del blocks[dklen:]
return bytes(blocks)
if __name__ == "__main__":
import sys
from . import tests
tests.run_pbkdf2_suite(sys.modules[__name__])
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Scrypt implementation that calls into system libscrypt"""
import base64
import ctypes, ctypes.util
from ctypes import c_char_p, c_size_t, c_uint64, c_uint32
import os
from .common import *
from . import mcf as mcf_mod
_libscrypt_soname = ctypes.util.find_library('scrypt')
if _libscrypt_soname is None:
raise ImportError('Unable to find libscrypt')
try:
_libscrypt = ctypes.CDLL(_libscrypt_soname)
_libscrypt_scrypt = _libscrypt.libscrypt_scrypt
_libscrypt_mcf = _libscrypt.libscrypt_mcf
_libscrypt_check = _libscrypt.libscrypt_check
except OSError:
raise ImportError('Unable to load libscrypt: ' + _libscrypt_soname)
except AttributeError:
raise ImportError('Incompatible libscrypt: ' + _libscrypt_soname)
_libscrypt_scrypt.argtypes = [
c_char_p, # password
c_size_t, # password length
c_char_p, # salt
c_size_t, # salt length
c_uint64, # N
c_uint32, # r
c_uint32, # p
c_char_p, # out
c_size_t, # out length
]
_libscrypt_mcf.argtypes = [
c_uint64, # N
c_uint32, # r
c_uint32, # p
c_char_p, # salt
c_char_p, # hash
c_char_p, # out (125+ bytes)
]
_libscrypt_check.argtypes = [
c_char_p, # mcf (modified)
c_char_p, # hash
]
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)
out = ctypes.create_string_buffer(olen)
ret = _libscrypt_scrypt(password, len(password), salt, len(salt),
N, r, p, out, len(out))
if ret:
raise ValueError
return out.raw
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.)
"""
if (prefix != SCRYPT_MCF_PREFIX_s1 and prefix != SCRYPT_MCF_PREFIX_ANY):
return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix)
if salt is None:
salt = os.urandom(16)
elif not (1 <= len(salt) <= 16):
raise ValueError('salt must be 1-16 bytes')
if N > 2**31:
raise ValueError('N > 2**31 not supported')
if b'\0' in password:
raise ValueError('scrypt_mcf password must not contain zero bytes')
hash = scrypt(password, salt, N, r, p)
h64 = base64.b64encode(hash)
s64 = base64.b64encode(salt)
out = ctypes.create_string_buffer(125)
ret = _libscrypt_mcf(N, r, p, s64, h64, out)
if not ret:
raise ValueError
out = out.raw.strip(b'\0')
# XXX: Hack to support old libscrypt (like in Ubuntu 14.04)
if len(out) == 123:
out = out + b'='
return out
def scrypt_mcf_check(mcf, password):
"""Returns True if the password matches the given MCF hash"""
if not isinstance(mcf, bytes):
raise TypeError
if not isinstance(password, bytes):
raise TypeError
if len(mcf) != 124 or b'\0' in password:
return mcf_mod.scrypt_mcf_check(scrypt, mcf, password)
mcfbuf = ctypes.create_string_buffer(mcf)
ret = _libscrypt_check(mcfbuf, password)
if ret < 0:
return mcf_mod.scrypt_mcf_check(scrypt, mcf, password)
return bool(ret)
if __name__ == "__main__":
import sys
from . import tests
tests.run_scrypt_suite(sys.modules[__name__])
#!/usr/bin/env python
# Copyright (c) 2014, Jan Varho
#
# Permission to use, copy, modify, and/or distribute this software for any
# purpose with or without fee is hereby granted, provided that the above
# copyright notice and this permission notice appear in all copies.
#
# THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
# WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
# MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
# ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
# WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
# ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
# OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
"""Scrypt implementation that calls into system libsodium"""
import base64
import ctypes, ctypes.util
from ctypes import c_char_p, c_size_t, c_uint64, c_uint32, c_void_p
import hashlib, hmac
import numbers
import platform
import struct
import sys
from . import mcf as mcf_mod
from .common import *
if platform.python_implementation() == 'PyPy':
from . import pypyscrypt_inline as scr_mod
else:
from . import pylibsodium_salsa as scr_mod
def _get_libsodium():
'''
Locate the nacl c libs to use
'''
__SONAMES = (13, 10, 5, 4)
# Import libsodium from system
sys_sodium = ctypes.util.find_library('sodium')
if sys_sodium is None:
sys_sodium = ctypes.util.find_library('libsodium')
if sys_sodium:
return ctypes.CDLL(sys_sodium)
# Import from local path
if sys.platform.startswith('win'):
try:
return ctypes.cdll.LoadLibrary('libsodium')
except OSError:
pass
for soname_ver in __SONAMES:
try:
return ctypes.cdll.LoadLibrary(
'libsodium-{0}'.format(soname_ver)
)
except OSError:
pass
elif sys.platform.startswith('darwin'):
try:
return ctypes.cdll.LoadLibrary('libsodium.dylib')
except OSError:
pass
else:
try:
return ctypes.cdll.LoadLibrary('libsodium.so')
except OSError:
pass
for soname_ver in __SONAMES:
try:
return ctypes.cdll.LoadLibrary(
'libsodium.so.{0}'.format(soname_ver)
)
except OSError:
pass
_lib = _get_libsodium()
if _lib is None:
raise ImportError('Unable to load libsodium')
try:
_scrypt_ll = _lib.crypto_pwhash_scryptsalsa208sha256_ll
_scrypt_ll.argtypes = [
c_void_p, # passwd
c_size_t, # passwdlen
c_void_p, # salt
c_size_t, # saltlen
c_uint64, # N
c_uint32, # r
c_uint32, # p
c_void_p, # buf
c_size_t, # buflen
]
except AttributeError:
_scrypt_ll = None
try:
_scrypt = _lib.crypto_pwhash_scryptsalsa208sha256
_scrypt_str = _lib.crypto_pwhash_scryptsalsa208sha256_str
_scrypt_str_chk = _lib.crypto_pwhash_scryptsalsa208sha256_str_verify
_scrypt_str_bytes = _lib.crypto_pwhash_scryptsalsa208sha256_strbytes()
_scrypt_salt = _lib.crypto_pwhash_scryptsalsa208sha256_saltbytes()
if _scrypt_str_bytes != 102 and not _scrypt_ll:
raise ImportError('Incompatible libsodium: ' + _lib_soname)
except AttributeError:
try:
_scrypt = _lib.crypto_pwhash_scryptxsalsa208sha256
_scrypt_str = _lib.crypto_pwhash_scryptxsalsa208sha256_str
_scrypt_str_chk = _lib.crypto_pwhash_scryptxsalsa208sha256_str_verify
_scrypt_str_bytes = _lib.crypto_pwhash_scryptxsalsa208sha256_strbytes()
_scrypt_salt = _lib.crypto_pwhash_scryptxsalsa208sha256_saltbytes
_scrypt_salt = _scrypt_salt()
if _scrypt_str_bytes != 102 and not _scrypt_ll:
raise ImportError('Incompatible libsodium: ' + _lib_soname)
except AttributeError:
if not _scrypt_ll:
raise ImportError('Incompatible libsodium: ' + _lib_soname)
_scrypt.argtypes = [
c_void_p, # out
c_uint64, # outlen
c_void_p, # passwd
c_uint64, # passwdlen
c_void_p, # salt
c_uint64, # opslimit
c_size_t, # memlimit
]
_scrypt_str.argtypes = [
c_void_p, # out (102 bytes)
c_void_p, # passwd
c_uint64, # passwdlen
c_uint64, # opslimit
c_size_t, # memlimit
]
_scrypt_str_chk.argtypes = [
c_char_p, # str (102 bytes)
c_void_p, # passwd
c_uint64, # passwdlen
]
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)
if _scrypt_ll:
out = ctypes.create_string_buffer(olen)
if _scrypt_ll(password, len(password), salt, len(salt),
N, r, p, out, olen):
raise ValueError
return out.raw
if len(salt) != _scrypt_salt or r != 8 or (p & (p - 1)) or (N*p <= 512):
return scr_mod.scrypt(password, salt, N, r, p, olen)
s = next(i for i in range(1, 64) if 2**i == N)
t = next(i for i in range(0, 30) if 2**i == p)
m = 2**(10 + s)
o = 2**(5 + t + s)
if s > 53 or t + s > 58:
raise ValueError
out = ctypes.create_string_buffer(olen)
if _scrypt(out, olen, password, len(password), salt, o, m) != 0:
raise ValueError
return out.raw
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.)
"""
if N < 2 or (N & (N - 1)):
raise ValueError('scrypt N must be a power of 2 greater than 1')
if p > 255 or p < 1:
raise ValueError('scrypt_mcf p out of range [1,255]')
if N > 2**31:
raise ValueError('scrypt_mcf N out of range [2,2**31]')
if (salt is not None or r != 8 or (p & (p - 1)) or (N*p <= 512) or
prefix not in (SCRYPT_MCF_PREFIX_7, SCRYPT_MCF_PREFIX_s1,
SCRYPT_MCF_PREFIX_ANY) or
_scrypt_ll):
return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix)
s = next(i for i in range(1, 32) if 2**i == N)
t = next(i for i in range(0, 8) if 2**i == p)
m = 2**(10 + s)
o = 2**(5 + t + s)
mcf = ctypes.create_string_buffer(102)
if _scrypt_str(mcf, password, len(password), o, m) != 0:
return mcf_mod.scrypt_mcf(scrypt, password, salt, N, r, p, prefix)
if prefix in (SCRYPT_MCF_PREFIX_7, SCRYPT_MCF_PREFIX_ANY):
return mcf.raw.strip(b'\0')
_N, _r, _p, salt, hash, olen = mcf_mod._scrypt_mcf_decode_7(mcf.raw[:-1])
assert _N == N and _r == r and _p == p, (_N, _r, _p, N, r, p, o, m)
return mcf_mod._scrypt_mcf_encode_s1(N, r, p, salt, hash)
def scrypt_mcf_check(mcf, password):
"""Returns True if the password matches the given MCF hash"""
if mcf_mod._scrypt_mcf_7_is_standard(mcf) and not _scrypt_ll:
return _scrypt_str_chk(mcf, password, len(password)) == 0
return mcf_mod.scrypt_mcf_check(scrypt, mcf, password)
if __name__ == "__main__":
import sys
from . import tests
try:
from . import pylibscrypt
scr_mod = pylibscrypt
except ImportError:
pass
tests.run_scrypt_suite(sys.modules[__name__])
#!/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.
"""Scrypt implementation that calls into system libsodium"""
import base64
import ctypes, ctypes.util
from ctypes import c_char_p, c_size_t, c_uint64, c_uint32, c_void_p
import hashlib, hmac
import numbers
import struct
import sys
from . import mcf as mcf_mod
from .common import *
def _get_libsodium():
'''
Locate the nacl c libs to use
'''
__SONAMES = (13, 10, 5, 4)
# Import libsodium from system
sys_sodium = ctypes.util.find_library('sodium')
if sys_sodium is None:
sys_sodium = ctypes.util.find_library('libsodium')
if sys_sodium:
return ctypes.CDLL(sys_sodium)
# Import from local path
if sys.platform.startswith('win'):
try:
return ctypes.cdll.LoadLibrary('libsodium')
except OSError:
pass
for soname_ver in __SONAMES:
try:
return ctypes.cdll.LoadLibrary(
'libsodium-{0}'.format(soname_ver)
)
except OSError:
pass
elif sys.platform.startswith('darwin'):
try:
return ctypes.cdll.LoadLibrary('libsodium.dylib')
except OSError:
pass
else:
try:
return ctypes.cdll.LoadLibrary('libsodium.so')
except OSError:
pass
for soname_ver in __SONAMES:
try:
return ctypes.cdll.LoadLibrary(
'libsodium.so.{0}'.format(soname_ver)
)
except OSError:
pass
_libsodium = _get_libsodium()
if _libsodium is None:
raise ImportError('Unable to load libsodium')
try:
_libsodium_salsa20_8 = _libsodium.crypto_core_salsa208
except AttributeError:
raise ImportError('Incompatible libsodium: ')
_libsodium_salsa20_8.argtypes = [
c_void_p, # out (16*4 bytes)
c_void_p, # in (4*4 bytes)
c_void_p, # k (8*4 bytes)
c_void_p, # c (4*4 bytes)
]
# 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 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.
"""
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) * 8
return B[Bi] & 0xffffffff
def salsa20_8(B, x):
"""Salsa 20/8 using libsodium
NaCL/libsodium includes crypto_core_salsa208, but unfortunately it
expects the data in a different order, so we need to mix it up a bit.
"""
hi = 0xffffffff00000000
lo = 0x00000000ffffffff
struct.pack_into('<9Q', x, 0,
(B[0] & lo) + (B[2] & hi), (B[5] & lo) + (B[7] & hi), # c
B[3], B[4], # in
B[0], B[1], (B[2] & lo) + (B[5] & hi), # pad k pad
B[6], B[7],
)
c = ctypes.addressof(x)
i = c + 4*4
k = c + 9*4
_libsodium_salsa20_8(c, i, k, c)
B[:] = struct.unpack('<8Q8x', x)
def blockmix_salsa8(BY, Yi, r):
"""Blockmix; Used by SMix"""
start = (2 * r - 1) * 8
X = BY[start:start+8] # BlockMix - 1
x = ctypes.create_string_buffer(8*9)
for i in xrange(2 * r): # BlockMix - 2
blockxor(BY, i * 8, X, 0, 8) # BlockMix - 3(inner)
salsa20_8(X, x) # BlockMix - 3(outer)
array_overwrite(X, 0, BY, Yi + (i * 8), 8) # BlockMix - 4
for i in xrange(r): # BlockMix - 6
array_overwrite(BY, Yi + (i * 2) * 8, BY, i * 8, 8)
array_overwrite(BY, Yi + (i*2 + 1) * 8, BY, (i + r) * 8, 8)
def smix(B, Bi, r, N, V, X):
"""SMix; a specific case of ROMix based on Salsa20/8"""
array_overwrite(B, Bi, X, 0, 16 * r) # ROMix - 1
for i in xrange(N): # ROMix - 2
array_overwrite(X, 0, V, i * (16 * r), 16 * r) # ROMix - 3
blockmix_salsa8(X, 16 * r, r) # ROMix - 4
for i in xrange(N): # ROMix - 6
j = integerify(X, r) & (N - 1) # ROMix - 7
blockxor(V, j * (16 * r), X, 0, 16 * r) # ROMix - 8(inner)
blockmix_salsa8(X, 16 * r, r) # ROMix - 9(outer)
array_overwrite(X, 0, B, Bi, 16 * r) # ROMix - 10
check_args(password, salt, N, r, p, olen)
# Everything is lists of 64-bit uints for all but pbkdf2
try:
B = _pbkdf2('sha256', password, salt, 1, p * 128 * r)
B = list(struct.unpack('<%dQ' % (len(B) // 8), B))
XY = [0] * (32 * r)
V = [0] * (16 * r * N)
except (MemoryError, OverflowError):
raise ValueError("scrypt parameters don't fit in memory")
for i in xrange(p):
smix(B, i * 16 * r, r, N, V, XY)
B = struct.pack('<%dQ' % 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=b'$s1$'):
"""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)
if __name__ == "__main__":
import sys
from . import tests
tests.run_scrypt_suite(sys.modules[__name__])
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment