Skip to content
Snippets Groups Projects

Compare revisions

Changes are shown as if the source revision was being merged into the target revision. Learn more about comparing revisions.

Source

Select target project
No results found

Target

Select target project
  • cebash/sakia
  • santiago/sakia
  • jonas/sakia
3 results
Show changes
Showing
with 0 additions and 3023 deletions
#!/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__])
#!/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__])
#!/usr/bin/env python
# Automatically generated file, see inline.py
# 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 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 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):
a = (x[0]+x[12]) & 0xffffffff
b = (x[5]+x[1]) & 0xffffffff
x[4] ^= (a << 7) | (a >> 25)
x[9] ^= (b << 7) | (b >> 25)
a = (x[10]+x[6]) & 0xffffffff
b = (x[15]+x[11]) & 0xffffffff
x[14] ^= (a << 7) | (a >> 25)
x[3] ^= (b << 7) | (b >> 25)
a = (x[4]+x[0]) & 0xffffffff
b = (x[9]+x[5]) & 0xffffffff
x[8] ^= (a << 9) | (a >> 23)
x[13] ^= (b << 9) | (b >> 23)
a = (x[14]+x[10]) & 0xffffffff
b = (x[3]+x[15]) & 0xffffffff
x[2] ^= (a << 9) | (a >> 23)
x[7] ^= (b << 9) | (b >> 23)
a = (x[8]+x[4]) & 0xffffffff
b = (x[13]+x[9]) & 0xffffffff
x[12] ^= (a << 13) | (a >> 19)
x[1] ^= (b << 13) | (b >> 19)
a = (x[2]+x[14]) & 0xffffffff
b = (x[7]+x[3]) & 0xffffffff
x[6] ^= (a << 13) | (a >> 19)
x[11] ^= (b << 13) | (b >> 19)
a = (x[12]+x[8]) & 0xffffffff
b = (x[1]+x[13]) & 0xffffffff
x[0] ^= (a << 18) | (a >> 14)
x[5] ^= (b << 18) | (b >> 14)
a = (x[6]+x[2]) & 0xffffffff
b = (x[11]+x[7]) & 0xffffffff
x[10] ^= (a << 18) | (a >> 14)
x[15] ^= (b << 18) | (b >> 14)
a = (x[0]+x[3]) & 0xffffffff
b = (x[5]+x[4]) & 0xffffffff
x[1] ^= (a << 7) | (a >> 25)
x[6] ^= (b << 7) | (b >> 25)
a = (x[10]+x[9]) & 0xffffffff
b = (x[15]+x[14]) & 0xffffffff
x[11] ^= (a << 7) | (a >> 25)
x[12] ^= (b << 7) | (b >> 25)
a = (x[1]+x[0]) & 0xffffffff
b = (x[6]+x[5]) & 0xffffffff
x[2] ^= (a << 9) | (a >> 23)
x[7] ^= (b << 9) | (b >> 23)
a = (x[11]+x[10]) & 0xffffffff
b = (x[12]+x[15]) & 0xffffffff
x[8] ^= (a << 9) | (a >> 23)
x[13] ^= (b << 9) | (b >> 23)
a = (x[2]+x[1]) & 0xffffffff
b = (x[7]+x[6]) & 0xffffffff
x[3] ^= (a << 13) | (a >> 19)
x[4] ^= (b << 13) | (b >> 19)
a = (x[8]+x[11]) & 0xffffffff
b = (x[13]+x[12]) & 0xffffffff
x[9] ^= (a << 13) | (a >> 19)
x[14] ^= (b << 13) | (b >> 19)
a = (x[3]+x[2]) & 0xffffffff
b = (x[4]+x[7]) & 0xffffffff
x[0] ^= (a << 18) | (a >> 14)
x[5] ^= (b << 18) | (b >> 14)
a = (x[9]+x[8]) & 0xffffffff
b = (x[14]+x[13]) & 0xffffffff
x[10] ^= (a << 18) | (a >> 14)
x[15] ^= (b << 18) | (b >> 14)
# 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
BY[i * 16:(i * 16)+(16)] = BY[Yi + (i * 2) * 16:(Yi + (i * 2) * 16)+(16)]
BY[(i + r) * 16:((i + r) * 16)+(16)] = BY[Yi + (i*2 + 1) * 16:(Yi + (i*2 + 1) * 16)+(16)]
def smix(B, Bi, r, N, V, X):
"""SMix; a specific case of ROMix based on Salsa20/8"""
X[0:(0)+(32 * r)] = B[Bi:(Bi)+(32 * r)]
for i in xrange(N): # ROMix - 2
V[i * (32 * r):(i * (32 * r))+(32 * r)] = X[0:(0)+(32 * r)]
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)
B[Bi:(Bi)+(32 * r)] = X[0:(0)+(32 * r)]
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__])
#!/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 the 'scrypt' python module"""
import numbers
from scrypt import hash as _scrypt
from . import mcf as mcf_mod
from .common import *
# scrypt < 0.6 doesn't support hash length
try:
_scrypt(b'password', b'NaCl', N=2, r=1, p=1, buflen=42)
except TypeError:
raise ImportError('scrypt module version unsupported, 0.6+ required')
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)
try:
return _scrypt(password=password, salt=salt, N=N, r=r, p=p, buflen=olen)
except:
raise ValueError
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)
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.
"""Tests scrypt and PBKDF2 implementations"""
import base64
import hashlib
import unittest
class ScryptTests(unittest.TestCase):
"""Tests an scrypt implementation from module"""
set_up_lambda = None
tear_down_lambda = None
def setUp(self):
if not self.module:
self.skipTest('module not tested')
if self.set_up_lambda:
self.set_up_lambda()
def tearDown(self):
if self.tear_down_lambda:
self.tear_down_lambda()
def _test_vector(self, vector):
pw, s, N, r, p, h, m = vector
self.assertEqual(
self.module.scrypt(pw, s, N, r, p),
base64.b16decode(h, True)
)
if m is not None:
self.assertEqual(
self.module.scrypt_mcf(pw, s, N, r, p),
m
)
self.assertTrue(self.module.scrypt_mcf_check(m, pw))
self.assertFalse(self.module.scrypt_mcf_check(m, b'x' + pw))
def test_vector0(self):
self._test_vector((
b'', b'', 16, 1, 1,
b'77d6576238657b203b19ca42c18a0497f16b4844e3074ae8dfdffa3fede21442'
b'fcd0069ded0948f8326a753a0fc81f17e8d3e0fb2e0d3628cf35e20c38d18906',
None
))
def test_vector1(self):
if self.fast:
self.skipTest('slow testcase')
self._test_vector((
b'password', b'NaCl', 1024, 8, 16,
b'fdbabe1c9d3472007856e7190d01e9fe7c6ad7cbc8237830e77376634b373162'
b'2eaf30d92e22a3886ff109279d9830dac727afb94a83ee6d8360cbdfa2cc0640',
b'$s1$0a0810$TmFDbA==$/bq+HJ00cgB4VucZDQHp/nxq18vII3gw53N2Y0s3MWIu'
b'rzDZLiKjiG/xCSedmDDaxyevuUqD7m2DYMvfoswGQA=='
))
def test_vector2(self):
if self.fast:
self.skipTest('slow testcase')
self._test_vector((
b'pleaseletmein', b'SodiumChloride', 16384, 8, 1,
b'7023bdcb3afd7348461c06cd81fd38ebfda8fbba904f8e3ea9b543f6545da1f2'
b'd5432955613f0fcf62d49705242a9af9e61e85dc0d651e40dfcf017b45575887',
b'$s1$0e0801$U29kaXVtQ2hsb3JpZGU=$cCO9yzr9c0hGHAbNgf046/2o+7qQT44+'
b'qbVD9lRdofLVQylVYT8Pz2LUlwUkKpr55h6F3A1lHkDfzwF7RVdYhw=='
))
def test_vector3(self):
self._test_vector((
b'password', b'NaCl', 2, 8, 1,
b'e5ed8edc019edfef2d3ced0896faf9eec6921dcc68125ce81c10d53474ce'
b'1be545979159700d324e77c68d34c553636a8429c4f3c99b9566466877f9'
b'dca2b92b',
b'$s1$010801$TmFDbA==$5e2O3AGe3+8tPO0Ilvr57saSHcxoElzoHBDVNHTO'
b'G+VFl5FZcA0yTnfGjTTFU2NqhCnE88mblWZGaHf53KK5Kw=='
))
def test_vector4(self):
self._test_vector((
b'pleaseletmein', b'SodiumChloride', 4, 1, 1,
b'BB1D77016C543A99FE632C9C43C60180FD05E0CAC8B29374DBD1854569CB'
b'534F487240CFC069D6A59A35F2FA5C7428B21D9BE9F84315446D5371119E'
b'016FEDF7',
b'$s1$020101$U29kaXVtQ2hsb3JpZGU=$ux13AWxUOpn+YyycQ8YBgP0F4MrI'
b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9w=='
))
def test_vector5(self):
if self.fast:
self.skipTest('slow testcase')
self._test_vector((
b'pleaseletmein', b'X'*32, 2**10, 8, 1,
b'cd81f46bd79125651e017a1bf5a28295f68d4b68d397815514bfdc2f3684'
b'f034ae2a5df332a48e915f7567306df2d401387b70d8f02f83bd6f4c69ff'
b'89d2663c',
None
))
def test_vector6(self):
self._test_vector((
b'pa\0ss', b'salt'*4, 32, 2, 2,
b'76c5260f1dc6339512ae87143d799089f5b508c823c870a3d55f641efa84'
b'63a813221050c93a44255ac8027804c49a87c1ecc9911356b9fc17e06eda'
b'85f23ff5',
None
))
def test_vector7(self):
if self.fast:
self.skipTest('slow testcase')
self._test_vector((
b'pleaseletmein', b'X'*32, 2**10, 8, 2,
b'1693cc02b680b18b3d0a874d459d23a5f63ff4f9de0fb5917ef899226af6'
b'bd33d3a3dfe569d3b6f4f762f0cb64f5f406d485aca501a54645d7389fe6'
b'e28e261e',
None
))
def test_bytes_enforced(self):
self.assertRaises(TypeError, self.module.scrypt, u'pass', b'salt')
self.assertRaises(TypeError, self.module.scrypt, 42, b'salt')
self.assertRaises(TypeError, self.module.scrypt, b'pass', None)
self.assertRaises(TypeError, self.module.scrypt_mcf, u'mcf', b'pass')
self.assertRaises(TypeError, self.module.scrypt_mcf, object, b'pass')
def test_salt_length_mcf(self):
pw = b'pass'
self.assertRaises(ValueError, self.module.scrypt_mcf, pw, b'')
self.assertRaises(ValueError, self.module.scrypt_mcf, pw, b'a'*17)
def test_salt_generation(self):
pw, N = b'pass', 2
m1 = self.module.scrypt_mcf(pw, N=N)
m2 = self.module.scrypt_mcf(pw, N=N)
self.assertNotEqual(m1, m2)
self.assertTrue(self.module.scrypt_mcf_check(m1, pw))
self.assertTrue(self.module.scrypt_mcf_check(m2, pw))
def test_invalid_N(self):
pw, s = b'password', b'salt'*8
self.assertRaises(TypeError, self.module.scrypt, pw, s, 7.5)
self.assertRaises(ValueError, self.module.scrypt, pw, s, -1)
self.assertRaises(ValueError, self.module.scrypt, pw, s, 1)
self.assertRaises(ValueError, self.module.scrypt, pw, s, 42)
self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**66)
self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**66+2)
self.assertRaises(ValueError, self.module.scrypt_mcf, pw, None, 1)
self.assertRaises(ValueError, self.module.scrypt_mcf, pw, None, 2**32)
def test_huge_N(self):
pw, s = b'password', b'salt'*8
self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**50)
self.assertRaises(ValueError, self.module.scrypt, pw, s, 2**60)
self.assertRaises(ValueError, self.module.scrypt_mcf, pw,
N=2**31, prefix=b'$7$')
def test_invalid_r(self):
pw, s, N = b'password', b'salt', 2
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 0)
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, -1)
self.assertRaises(TypeError, self.module.scrypt, pw, s, N, 7.5)
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 2**31)
self.assertRaises(ValueError, self.module.scrypt_mcf, pw, s, N, 256)
def test_invalid_p(self):
pw, s, N = b'password', b'salt', 2
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 1, 0)
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 1, -2**31)
self.assertRaises(TypeError, self.module.scrypt, pw, s, N, 1, 7.5)
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, 2**35)
self.assertRaises(ValueError, self.module.scrypt_mcf, pw, s, N, 1, 256)
def test_olen(self):
pw, s, N = b'password', b'salt', 2
self.assertEquals(len(self.module.scrypt(pw, s, N, olen=42)), 42)
self.assertEquals(len(self.module.scrypt(pw, s, N, olen=100)), 100)
self.assertRaises(TypeError, self.module.scrypt, pw, s, N, olen=b'7')
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, olen=-1)
def test_invalid_olen(self):
pw, s, N = b'password', b'salt', 2**10
self.assertRaises(TypeError, self.module.scrypt, pw, s, N, olen=b'7')
self.assertRaises(ValueError, self.module.scrypt, pw, s, N, olen=-1)
def test_mcf(self):
pw = b'password'
self.assertRaises(ValueError, self.module.scrypt_mcf_check, b'', pw)
self.assertRaises(ValueError, self.module.scrypt_mcf_check,
b'$s1$ffffffff$aaaa$bbbb', pw)
self.assertRaises(TypeError, self.module.scrypt_mcf_check, u'mcf', pw)
self.assertRaises(TypeError, self.module.scrypt_mcf_check, b'mcf', 42)
def test_mcf_padding(self):
if self.fast:
self.skipTest('slow testcase')
pw = b'pleaseletmein'
m1 = (
b'$s1$020101$U29kaXVtQ2hsb3JpZGU$ux13AWxUOpn+YyycQ8YBgP0F4MrI'
b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9w=='
)
m2 = (
b'$s1$020101$U29kaXVtQ2hsb3JpZGU=$ux13AWxUOpn+YyycQ8YBgP0F4MrI'
b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9w='
)
m3 = (
b'$s1$020101$U29kaXVtQ2hsb3JpZGU=$ux13AWxUOpn+YyycQ8YBgP0F4MrI'
b'spN029GFRWnLU09IckDPwGnWpZo18vpcdCiyHZvp+EMVRG1TcRGeAW/t9'
)
self.assertTrue(self.module.scrypt_mcf_check(m1, pw))
self.assertTrue(self.module.scrypt_mcf_check(m2, pw))
self.assertRaises(ValueError, self.module.scrypt_mcf_check, m3, pw)
def test_mcf_nonstandard(self):
pw = b'pass'
m1 = ( # empty salt
b'$s1$010801$$WA1vBj+HFlIk7pG/OPS5bY4NKHBGeGIxEY99farnu2C9uOHxKe'
b'LWP3sCXRvP98F7lVi2JNT/Bmte38iodf81VEYB0Nu3pBw9JqTwiCAqMwL+2kqB'
)
m2 = ( # 31 byte hash
b'$7$16..../....l/htqjrI38qNowkQZL8RxFVxS8JV9PPJr1+A/WTQWiU'
b'$wOcPY0vsHHshxa0u87FDhmTo42WZr0JbSHY2w2Zkyr1'
)
m3 = ( # 44 byte salt, 31 byte hash
b'$7$12..../....aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa'
b'$14hkhieutTQcbq.iU1FDZzYz1vW8NPYowy4WERDM70'
)
self.assertTrue(self.module.scrypt_mcf_check(m1, pw))
self.assertTrue(self.module.scrypt_mcf_check(m2, pw))
self.assertTrue(self.module.scrypt_mcf_check(m3, pw))
def test_mcf_7(self):
if self.fast:
self.skipTest('slow testcase')
p, m = b'pleaseletmein', (
b'$7$C6..../....SodiumChloride'
b'$kBGj9fHznVYFQMEn/qDCfrDevf9YDtcDdKvEqHJLV8D'
)
self.assertTrue(self.module.scrypt_mcf_check(m, p))
self.assertFalse(self.module.scrypt_mcf_check(m, b'X'+p))
self.assertRaises(ValueError, self.module.scrypt_mcf_check,
b'$7$$', p
)
self.assertRaises(ValueError, self.module.scrypt_mcf_check,
b'$7$$$', p
)
def test_mcf_7_2(self):
if self.fast:
self.skipTest('slow testcase')
p = b'pleaseletmein'
m1 = self.module.scrypt_mcf(p, None, 2**10, 8, 2, b'$7$')
self.assertTrue(m1.startswith(b'$7$'))
self.assertTrue(self.module.scrypt_mcf_check(m1, p))
m2 = self.module.scrypt_mcf(p, None, 2**10, 8, 2, b'$s1$')
self.assertTrue(m2.startswith(b'$s1$'))
self.assertTrue(self.module.scrypt_mcf_check(m1, p))
def test_mcf_7_fast(self):
p, m1 = b'pleaseletmein', (
b'$7$06..../....SodiumChloride'
b'$ENlyo6fGw4PCcDBOFepfSZjFUnVatHzCcW55.ZGz3B0'
)
self.assertTrue(self.module.scrypt_mcf_check(m1, p))
m2 = self.module.scrypt_mcf(p, b'NaCl', 4, 8, 1, b'$7$')
self.assertTrue(self.module.scrypt_mcf_check(m2, p))
def test_mcf_unknown(self):
p = b'pleaseletmein'
self.assertRaises(ValueError, self.module.scrypt_mcf, p, prefix=b'$$')
def test_mcf_null(self):
p1, p2, p3 = b'please', b'please\0letmein', b'pleaseletmein'
self.assertRaises(ValueError, self.module.scrypt_mcf, p2, N=4)
m = (
b'$s1$020801$m8/OZVv4hi8rHFVTvOH3tQ==$jwi4vgiCjyqrZKOaksMFks5A'
b'M9ZRcrVPhAwqT1iRMTqXYrwkTngwjR2rwbAet9cSGdFfSverOEVLiLuUzG4k'
b'Hg=='
)
self.assertTrue(self.module.scrypt_mcf_check(m, p2))
self.assertFalse(self.module.scrypt_mcf_check(m, p1))
self.assertFalse(self.module.scrypt_mcf_check(m, p3))
def load_scrypt_suite(name, module, fast=True):
loader = unittest.defaultTestLoader
tests = type(name, (ScryptTests,), {'module': module, 'fast': fast})
return unittest.defaultTestLoader.loadTestsFromTestCase(tests)
def run_scrypt_suite(module, fast=False):
suite = unittest.TestSuite()
suite.addTest(load_scrypt_suite('scryptTests', module, fast))
unittest.TextTestRunner().run(suite)
class PBKDF2Tests(unittest.TestCase):
"""Tests a PBKDF2 implementation from module"""
def setUp(self):
if not self.module:
self.skipTest('module not tested')
def _test_vector(self, vector):
n, p, s, c, l, h = vector
h = base64.b16decode(h, True)
self.assertEquals(self.module.pbkdf2_hmac(n, p, s, c, l), h)
def test_vector1(self):
self._test_vector(('sha1', b'password', b'salt', 1, 20,
b'0c60c80f961f0e71f3a9b524af6012062fe037a6'))
def test_vector2(self):
self._test_vector(('sha1', b'pass\0word', b'sa\0lt', 4096, 16,
b'56fa6aa75548099dcc37d7f03425e0c3'))
def test_vector3(self):
self._test_vector(('sha256', b'password', b'NaCl', 7, 42,
b'8cb94b8721e20e643be099f3c31d332456b4c26f55'
b'b6403950267dc2b3c0806bda709a3f2d7f6107db73'))
def test_long_key(self):
self.module.pbkdf2_hmac('sha256', b'pass'*100, b'NaCl', 2, 20)
def load_pbkdf2_suite(name, module):
loader = unittest.defaultTestLoader
tests = type(name, (PBKDF2Tests,), {'module': module})
return unittest.defaultTestLoader.loadTestsFromTestCase(tests)
def run_pbkdf2_suite(module, fast=False):
suite = unittest.TestSuite()
suite.addTest(load_pbkdf2_suite('scryptTests', module))
unittest.TextTestRunner().run(suite)
if __name__ == "__main__":
suite = unittest.TestSuite()
try:
from . import pylibscrypt
suite.addTest(load_scrypt_suite('pylibscryptTests', pylibscrypt, True))
except ImportError:
suite.addTest(load_scrypt_suite('pylibscryptTests', None, True))
try:
from . import pyscrypt
suite.addTest(load_scrypt_suite('pyscryptTests', pyscrypt, True))
except ImportError:
suite.addTest(load_scrypt_suite('pyscryptTests', None, True))
try:
from . import pylibsodium
suite.addTest(load_scrypt_suite('pylibsodiumTests',
pylibsodium, True))
from . import pylibscrypt
loader = unittest.defaultTestLoader
def set_up_ll(self):
if not self.module._scrypt_ll:
self.skipTest('no ll')
self.tmp_ll = self.module._scrypt_ll
self.tmp_scr = self.module.scr_mod
self.module._scrypt_ll = None
self.module.scr_mod = pylibscrypt
def tear_down_ll(self):
self.module._scrypt_ll = self.tmp_ll
self.module.scr_mod = self.tmp_scr
tmp = type(
'pylibsodiumFallbackTests', (ScryptTests,),
{
'module': pylibsodium, 'fast': False,
'set_up_lambda': set_up_ll,
'tear_down_lambda': tear_down_ll,
}
)
suite.addTest(unittest.defaultTestLoader.loadTestsFromTestCase(tmp))
except ImportError:
suite.addTest(load_scrypt_suite('pylibsodiumTests', None, True))
try:
from . import pylibsodium_salsa
suite.addTest(load_scrypt_suite('pylibsodium_salsaTests',
pylibsodium_salsa, True))
except ImportError:
suite.addTest(load_scrypt_suite('pylibsodium_salsaTests', None, True))
try:
from . import pypyscrypt_inline as pypyscrypt
suite.addTest(load_scrypt_suite('pypyscryptTests', pypyscrypt, True))
except ImportError:
suite.addTest(load_scrypt_suite('pypyscryptTests', None, True))
try:
from . import pbkdf2
suite.addTest(load_pbkdf2_suite('pbkdf2', pbkdf2))
except ImportError:
suite.addTest(load_pbkdf2_suite('pbkdf2', None))
if 'pbkdf2_hmac' in dir(hashlib):
suite.addTest(load_pbkdf2_suite('hashlib_pbkdf2', hashlib))
else:
suite.addTest(load_pbkdf2_suite('hashlib_pbkdf2', None))
unittest.TextTestRunner().run(suite)
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
PROTOCOL_VERSION="1"
MANAGED_API=["BASIC_MERKLED_API"]
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
__all__ = ['api']
__author__ = 'Caner Candan'
__version__ = '0.10.0'
__nonsense__ = 'uCoin'
import requests, logging, json
# import pylibscrypt
logger = logging.getLogger("ucoin")
class ConnectionHandler(object):
"""Helper class used by other API classes to ease passing server connection information."""
def __init__(self, server, port):
"""
Arguments:
- `server`: server hostname
- `port`: port number
"""
self.server = server
self.port = port
def __str__(self):
return 'connection info: %s:%d' % (self.server, self.port)
class API(object):
"""APIRequest is a class used as an interface. The intermediate derivated classes are the modules and the leaf classes are the API requests."""
def __init__(self, connection_handler, module):
"""
Asks a module in order to create the url used then by derivated classes.
Arguments:
- `module`: module name
- `connection_handler`: connection handler
"""
self.module = module
self.connection_handler = connection_handler
self.headers = {}
def reverse_url(self, path):
"""
Reverses the url using self.url and path given in parameter.
Arguments:
- `path`: the request path
"""
server, port = self.connection_handler.server, self.connection_handler.port
url = 'http://%s:%d/%s' % (server, port, self.module)
return url + path
def get(self, **kwargs):
"""wrapper of overloaded __get__ method."""
return self.__get__(**kwargs)
def post(self, **kwargs):
"""wrapper of overloaded __post__ method."""
logger.debug('do some work with')
data = self.__post__(**kwargs)
logger.debug('and send back')
return data
def __get__(self, **kwargs):
"""interface purpose for GET request"""
pass
def __post__(self, **kwargs):
"""interface purpose for POST request"""
pass
def requests_get(self, path, **kwargs):
"""
Requests GET wrapper in order to use API parameters.
Arguments:
- `path`: the request path
"""
response = requests.get(self.reverse_url(path), params=kwargs,
headers=self.headers, timeout=15)
if response.status_code != 200:
raise ValueError('status code != 200 => %d (%s)' % (response.status_code, response.text))
return response
def requests_post(self, path, **kwargs):
"""
Requests POST wrapper in order to use API parameters.
Arguments:
- `path`: the request path
"""
if 'self_' in kwargs:
kwargs['self'] = kwargs.pop('self_')
logging.debug("POST : {0}".format(kwargs))
response = requests.post(self.reverse_url(path), data=kwargs, headers=self.headers,
timeout=15)
if response.status_code != 200:
raise ValueError('status code != 200 => %d (%s)' % (response.status_code, response.text))
return response
def merkle_easy_parser(self, path, begin=None, end=None):
root = self.requests_get(path, leaves='true').json()
for leaf in root['leaves'][begin:end]:
yield self.requests_get(path, leaf=leaf).json()['leaf']
from . import network, blockchain, tx, wot
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import API, logging
logger = logging.getLogger("ucoin/blockchain")
class Blockchain(API):
def __init__(self, connection_handler, module='blockchain'):
super(Blockchain, self).__init__(connection_handler, module)
class Parameters(Blockchain):
"""GET the blockchain parameters used by this node."""
def __get__(self, **kwargs):
return self.requests_get('/parameters', **kwargs).json()
class Membership(Blockchain):
"""GET/POST a Membership document."""
def __init__(self, connection_handler, search=None):
super().__init__(connection_handler)
self.search = search
def __post__(self, **kwargs):
assert 'membership' in kwargs
return self.requests_post('/membership', **kwargs).json()
def __get__(self, **kwargs):
assert self.search is not None
return self.requests_get('/memberships/%s' % self.search, **kwargs).json()
class Block(Blockchain):
"""GET/POST a block from/to the blockchain."""
def __init__(self, connection_handler, number=None):
"""
Use the number parameter in order to select a block number.
Arguments:
- `number`: block number to select
"""
super(Block, self).__init__(connection_handler)
self.number = number
def __get__(self, **kwargs):
assert self.number is not None
return self.requests_get('/block/%d' % self.number, **kwargs).json()
def __post__(self, **kwargs):
assert 'block' in kwargs
assert 'signature' in kwargs
return self.requests_post('/block', **kwargs).json()
class Current(Blockchain):
"""GET, same as block/[number], but return last accepted block."""
def __get__(self, **kwargs):
return self.requests_get('/current', **kwargs).json()
class Hardship(Blockchain):
"""GET hardship level for given member's fingerprint for writing next block."""
def __init__(self, connection_handler, fingerprint):
"""
Use the number parameter in order to select a block number.
Arguments:
- `fingerprint`: member fingerprint
"""
super(Hardship, self).__init__(connection_handler)
self.fingerprint = fingerprint
def __get__(self, **kwargs):
assert self.fingerprint is not None
return self.requests_get('/hardship/%s' % self.fingerprint.upper(), **kwargs).json()
class Newcomers(Blockchain):
"""GET, return block numbers containing newcomers."""
def __get__(self, **kwargs):
return self.requests_get('/with/newcomers', **kwargs).json()
class Certifications(Blockchain):
"""GET, return block numbers containing certifications."""
def __get__(self, **kwargs):
return self.requests_get('/with/certs', **kwargs).json()
class Joiners(Blockchain):
"""GET, return block numbers containing joiners."""
def __get__(self, **kwargs):
return self.requests_get('/with/joiners', **kwargs).json()
class Actives(Blockchain):
"""GET, return block numbers containing actives."""
def __get__(self, **kwargs):
return self.requests_get('/with/actives', **kwargs).json()
class Leavers(Blockchain):
"""GET, return block numbers containing leavers."""
def __get__(self, **kwargs):
return self.requests_get('/with/leavers', **kwargs).json()
class Excluded(Blockchain):
"""GET, return block numbers containing excluded."""
def __get__(self, **kwargs):
return self.requests_get('/with/excluded', **kwargs).json()
class UD(Blockchain):
"""GET, return block numbers containing universal dividend."""
def __get__(self, **kwargs):
return self.requests_get('/with/ud', **kwargs).json()
class TX(Blockchain):
"""GET, return block numbers containing transactions."""
def __get__(self, **kwargs):
return self.requests_get('/with/tx', **kwargs).json()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import API, logging
logger = logging.getLogger("ucoin/network")
class Network(API):
def __init__(self, connection_handler, module='network'):
super(Network, self).__init__(connection_handler, module)
class Peering(Network):
"""GET peering information about a peer."""
def __get__(self, **kwargs):
return self.requests_get('/peering', **kwargs).json()
from . import peering
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import Network, logging
logger = logging.getLogger("ucoin/network/peering")
class Base(Network):
def __init__(self, connection_handler):
super(Base, self).__init__(connection_handler, 'network/peering')
class Peers(Base):
"""GET peering entries of every node inside the currency network."""
def __get__(self, **kwargs):
"""creates a generator with one peering entry per iteration."""
return self.merkle_easy_parser('/peers')
def __post__(self, **kwargs):
assert 'entry' in kwargs
assert 'signature' in kwargs
return self.requests_post('/peers', **kwargs).json()
class Status(Base):
"""POST a network status document to this node in order notify of its status."""
def __post__(self, **kwargs):
assert 'status' in kwargs
assert 'signature' in kwargs
return self.requests_post('/status', **kwargs).json()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import API, logging
logger = logging.getLogger("ucoin/tx")
class Tx(API):
def __init__(self, connection_handler, module='tx'):
super(Tx, self).__init__(connection_handler, module)
class Process(Tx):
"""POST a transaction."""
def __post__(self, **kwargs):
assert 'transaction' in kwargs
return self.requests_post('/process', **kwargs).json()
class Sources(Tx):
"""Get transaction sources."""
def __init__(self, connection_handler, pubkey, module='tx'):
super(Tx, self).__init__(connection_handler, module)
self.pubkey = pubkey
def __get__(self, **kwargs):
assert self.pubkey is not None
return self.requests_get('/sources/%s' % self.pubkey, **kwargs).json()
#
# This program is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program. If not, see <http://www.gnu.org/licenses/>.
#
# Authors:
# Caner Candan <caner@candan.fr>, http://caner.candan.fr
#
from .. import API, logging
logger = logging.getLogger("ucoin/wot")
class WOT(API):
def __init__(self, connection_handler, module='wot'):
super(WOT, self).__init__(connection_handler, module)
class Add(WOT):
"""POST Public key data."""
def __post__(self, **kwargs):
assert 'pubkey' in kwargs
assert 'self_' in kwargs
assert 'other' in kwargs
return self.requests_post('/add', **kwargs).json()
class Lookup(WOT):
"""GET Public key data."""
def __init__(self, connection_handler, search, module='wot'):
super(WOT, self).__init__(connection_handler, module)
self.search = search
def __get__(self, **kwargs):
assert self.search is not None
return self.requests_get('/lookup/%s' % self.search, **kwargs).json()
class CertifiersOf(WOT):
"""GET Certification data over a member."""
def __init__(self, connection_handler, search, module='wot'):
super(WOT, self).__init__(connection_handler, module)
self.search = search
def __get__(self, **kwargs):
assert self.search is not None
return self.requests_get('/certifiers-of/%s' % self.search, **kwargs).json()
class CertifiedBy(WOT):
"""GET Certification data from a member."""
def __init__(self, connection_handler, search, module='wot'):
super(WOT, self).__init__(connection_handler, module)
self.search = search
def __get__(self, **kwargs):
assert self.search is not None
return self.requests_get('/certified-by/%s' % self.search, **kwargs).json()
class Members(WOT):
"""GET List all current members of the Web of Trust."""
def __init__(self, connection_handler, module='wot'):
super(WOT, self).__init__(connection_handler, module)
def __get__(self, **kwargs):
return self.requests_get('/members', **kwargs).json()
'''
Created on 3 déc. 2014
@author: inso
'''
import base58
import base64
import re
import logging
from ..key import Base58Encoder
class Document:
re_version = re.compile("Version: ([0-9]+)\n")
re_currency = re.compile("Currency: ([^\n]+)\n")
re_signature = re.compile("([A-Za-z0-9+/]+(?:=|==)?)\n")
def __init__(self, version, currency, signatures):
self.version = version
self.currency = currency
if signatures:
self.signatures = [s for s in signatures if s is not None]
else:
self.signatures = []
def sign(self, keys):
'''
Sign the current document.
Warning : current signatures will be replaced with the new ones.
'''
self.signatures = []
for key in keys:
signing = base64.b64encode(key.signature(bytes(self.raw(), 'ascii')))
logging.debug("Signature : \n{0}".format(signing.decode("ascii")))
self.signatures.append(signing.decode("ascii"))
def signed_raw(self):
'''
If keys are None, returns the raw + current signatures
If keys are present, returns the raw signed by these keys
'''
raw = self.raw()
signed = "\n".join(self.signatures)
signed_raw = raw + signed + "\n"
return signed_raw
'''
Created on 2 déc. 2014
@author: inso
'''
from .. import PROTOCOL_VERSION
from . import Document
from .certification import SelfCertification, Certification
from .membership import Membership
from .transaction import Transaction
import re
import logging
class Block(Document):
'''
Version: VERSION
Type: Block
Currency: CURRENCY
Nonce: NONCE
Number: BLOCK_NUMBER
PoWMin: NUMBER_OF_ZEROS
Time: GENERATED_ON
MedianTime: MEDIAN_DATE
UniversalDividend: DIVIDEND_AMOUNT
Issuer: ISSUER_KEY
PreviousHash: PREVIOUS_HASH
PreviousIssuer: PREVIOUS_ISSUER_KEY
Parameters: PARAMETERS
MembersCount: WOT_MEM_COUNT
Identities:
PUBLIC_KEY:SIGNATURE:TIMESTAMP:USER_ID
...
Joiners:
PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
...
Actives:
PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
...
Leavers:
PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
...
Excluded:
PUBLIC_KEY
...
Certifications:
PUBKEY_FROM:PUBKEY_TO:BLOCK_NUMBER:SIGNATURE
...
Transactions:
COMPACT_TRANSACTION
...
BOTTOM_SIGNATURE
'''
re_type = re.compile("Type: (Block)\n")
re_noonce = re.compile("Nonce: ([0-9]+)\n")
re_number = re.compile("Number: ([0-9]+)\n")
re_powmin = re.compile("PoWMin: ([0-9]+)\n")
re_time = re.compile("Time: ([0-9]+)\n")
re_mediantime = re.compile("MedianTime: ([0-9]+)\n")
re_universaldividend = re.compile("UniversalDividend: ([0-9]+)\n")
re_issuer = re.compile("Issuer: ([1-9A-Za-z][^OIl]{42,45})\n")
re_previoushash = re.compile("PreviousHash: ([0-9a-fA-F]{5,40})\n")
re_previousissuer = re.compile("PreviousIssuer: ([1-9A-Za-z][^OIl]{42,45})\n")
re_parameters = re.compile("Parameters: ([0-9]+\.[0-9]+):([0-9]+):([0-9]+):([0-9]+):\
([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):([0-9]+):\
([0-9]+\.[0-9]+)\n")
re_memberscount = re.compile("MembersCount: ([0-9]+)\n")
re_identities = re.compile("Identities:\n")
re_joiners = re.compile("Joiners:\n")
re_actives = re.compile("Actives:\n")
re_leavers = re.compile("Leavers:\n")
re_excluded = re.compile("Excluded:\n")
re_exclusion = re.compile("([1-9A-Za-z][^OIl]{42,45})\n")
re_certifications = re.compile("Certifications:\n")
re_transactions = re.compile("Transactions:\n")
def __init__(self, version, currency, noonce, number, powmin, time,
mediantime, ud, issuer, prev_hash, prev_issuer,
parameters, members_count, identities, joiners,
actives, leavers, excluded, certifications,
transactions, signature):
'''
Constructor
'''
super().__init__(version, currency, [signature])
self.noonce = noonce
self.number = number
self.powmin = powmin
self.time = time
self.mediantime = mediantime
self.ud = ud
self.issuer = issuer
self.prev_hash = prev_hash
self.prev_issuer = prev_issuer
self.parameters = parameters
self.members_count = members_count
self.identities = identities
self.joiners = joiners
self.actives = actives
self.leavers = leavers
self.excluded = excluded
self.certifications = certifications
self.transactions = transactions
@classmethod
def from_signed_raw(cls, raw):
lines = raw.splitlines(True)
n = 0
version = int(Block.re_version.match(lines[n]).group(1))
n = n + 1
Block.re_type.match(lines[n]).group(1)
n = n + 1
currency = Block.re_currency.match(lines[n]).group(1)
n = n + 1
noonce = int(Block.re_noonce.match(lines[n]).group(1))
n = n + 1
number = int(Block.re_number.match(lines[n]).group(1))
n = n + 1
powmin = int(Block.re_powmin.match(lines[n]).group(1))
n = n + 1
time = int(Block.re_time.match(lines[n]).group(1))
n = n + 1
mediantime = int(Block.re_mediantime.match(lines[n]).group(1))
n = n + 1
ud = Block.re_universaldividend.match(lines[n])
if ud is not None:
ud = int(ud.group(1))
n = n + 1
issuer = Block.re_issuer.match(lines[n]).group(1)
n = n + 1
prev_hash = None
prev_issuer = None
if number > 0:
prev_hash = Block.re_previoushash.match(lines[n]).group(1)
n = n + 1
prev_issuer = Block.re_previousissuer.match(lines[n]).group(1)
n = n + 1
parameters = None
if number == 0:
parameters = Block.re_parameters.match(lines[n]).groups()
n = n + 1
members_count = int(Block.re_memberscount.match(lines[n]).group(1))
n = n + 1
identities = []
joiners = []
actives = []
leavers = []
excluded = []
certifications = []
transactions = []
if Block.re_identities.match(lines[n]) is not None:
n = n + 1
while Block.re_joiners.match(lines[n]) is None:
selfcert = SelfCertification.from_inline(version, currency, lines[n])
identities.append(selfcert)
n = n + 1
if Block.re_joiners.match(lines[n]):
n = n + 1
while Block.re_actives.match(lines[n]) is None:
membership = Membership.from_inline(version, currency, "IN", lines[n])
joiners.append(membership)
n = n + 1
if Block.re_actives.match(lines[n]):
n = n + 1
while Block.re_leavers.match(lines[n]) is None:
membership = Membership.from_inline(version, currency, "IN", lines[n])
actives.append(membership)
n = n + 1
if Block.re_leavers.match(lines[n]):
n = n + 1
while Block.re_excluded.match(lines[n]) is None:
membership = Membership.from_inline(version, currency, "OUT", lines[n])
leavers.append(membership)
n = n + 1
if Block.re_excluded.match(lines[n]):
n = n + 1
while Block.re_certifications.match(lines[n]) is None:
membership = Block.re_exclusion.match(lines[n]).group(1)
excluded.append(membership)
n = n + 1
if Block.re_certifications.match(lines[n]):
n = n + 1
while Block.re_transactions.match(lines[n]) is None:
certification = Certification.from_inline(version, currency,
prev_hash, lines[n])
certifications.append(certification)
n = n + 1
if Block.re_transactions.match(lines[n]):
n = n + 1
while not Block.re_signature.match(lines[n]):
tx_lines = ""
header_data = Transaction.re_header.match(lines[n])
version = int(header_data.group(1))
issuers_num = int(header_data.group(2))
inputs_num = int(header_data.group(3))
outputs_num = int(header_data.group(4))
has_comment = int(header_data.group(5))
tx_max = n+issuers_num*2+inputs_num+outputs_num+has_comment+1
for i in range(n, tx_max):
tx_lines += lines[n]
n = n + 1
transaction = Transaction.from_compact(currency, tx_lines)
transactions.append(transaction)
signature = Block.re_signature.match(lines[n]).group(1)
return cls(version, currency, noonce, number, powmin, time,
mediantime, ud, issuer, prev_hash, prev_issuer,
parameters, members_count, identities, joiners,
actives, leavers, excluded, certifications,
transactions, signature)
def raw(self):
doc = """Version: {0}
Type: Block
Currency: {1}
Nonce: {2}
Number: {3}
PoWMin: {4}
Time: {5}
MedianTime: {6}
""".format(self.version,
self.currency,
self.noonce,
self.number,
self.powmin,
self.time,
self.mediantime)
if self.ud:
doc += "UniversalDividend: {0}\n".format(self.ud)
doc += "Issuer: {0}\n".format(self.issuer)
if self.number == 0:
str_params = ":".join(self.parameters)
doc += "Parameters: {0}\n".format(str_params)
else:
doc += "PreviousHash: {0}\n\
PreviousIssuer: {1}\n".format(self.prev_hash, self.prev_issuer)
doc += "MembersCount: {0}\n".format(self.members_count)
doc += "Identities:\n"
for identity in self.identities:
doc += "{0}\n".format(identity.inline())
doc += "Joiners:\n"
for joiner in self.joiners:
doc += "{0}\n".format(joiner.inline())
doc += "Actives:\n"
for active in self.actives:
doc += "{0}\n".format(active.inline())
doc += "Leavers:\n"
for leaver in self.leavers:
doc += "{0]\n".format(leaver.inline())
doc += "Excluded:\n"
for exclude in self.excluded:
doc += "{0}\n".format(exclude)
doc += "Certifications:\n"
for cert in self.certifications:
doc += "{0}\n".format(cert.inline())
doc += "Transactions:\n"
for transaction in self.transactions:
doc += "{0}\n".format(transaction.inline())
return doc
'''
Created on 2 déc. 2014
@author: inso
'''
import re
import base64
import logging
from . import Document
class SelfCertification(Document):
'''
A document discribing a self certification.
'''
re_inline = re.compile("([1-9A-Za-z][^OIl]{42,45}):([A-Za-z0-9+/]+(?:=|==)?):([0-9]+):([^\n]+)\n")
re_uid = re.compile("UID:([^\n]+)\n")
re_timestamp = re.compile("META:TS:([0-9]+)\n")
def __init__(self, version, currency, pubkey, ts, uid, signature):
if signature:
super().__init__(version, currency, [signature])
else:
super().__init__(version, currency, [])
self.pubkey = pubkey
self.timestamp = ts
self.uid = uid
@classmethod
def from_inline(cls, version, currency, inline):
selfcert_data = SelfCertification.re_inline.match(inline)
pubkey = selfcert_data.group(1)
signature = selfcert_data.group(2)
ts = int(selfcert_data.group(3))
uid = selfcert_data.group(4)
return cls(version, currency, pubkey, ts, uid, signature)
def raw(self):
return """UID:{0}
META:TS:{1}
""".format(self.uid, self.timestamp)
def inline(self):
return "{0}:{1}:{2}:{3}".format(self.pubkey, self.signatures[0],
self.timestamp, self.uid)
class Certification(Document):
'''
A document describing a certification.
'''
re_inline = re.compile("([1-9A-Za-z][^OIl]{42,45}):\
([1-9A-Za-z][^OIl]{42,45}):([0-9]+):([A-Za-z0-9+/]+(?:=|==)?)\n")
re_timestamp = re.compile("META:TS:([0-9]+)-([0-9a-fA-F]{5,40})\n")
def __init__(self, version, currency, pubkey_from, pubkey_to,
blocknumber, blockhash, signature):
'''
Constructor
'''
super().__init__(version, currency, [signature])
self.pubkey_from = pubkey_from
self.pubkey_to = pubkey_to
self.blockhash = blockhash
self.blocknumber = blocknumber
@classmethod
def from_inline(cls, version, currency, blockhash, inline):
cert_data = Certification.re_inline.match(inline)
pubkey_from = cert_data.group(1)
pubkey_to = cert_data.group(2)
blocknumber = int(cert_data.group(3))
if blocknumber == 0:
blockhash = "DA39A3EE5E6B4B0D3255BFEF95601890AFD80709"
signature = cert_data.group(4)
return cls(version, currency, pubkey_from, pubkey_to,
blockhash, blocknumber, signature)
def raw(self, selfcert):
return """{0}META:TS:{1}-{2}
""".format(selfcert.signed_raw(), self.blocknumber, self.blockhash)
def sign(self, selfcert, keys):
'''
Sign the current document.
Warning : current signatures will be replaced with the new ones.
'''
self.signatures = []
for key in keys:
signing = base64.b64encode(key.signature(bytes(self.raw(selfcert), 'ascii')))
logging.debug("Signature : \n{0}".format(signing.decode("ascii")))
self.signatures.append(signing.decode("ascii"))
def signed_raw(self, selfcert):
raw = self.raw(selfcert)
signed = "\n".join(self.signatures)
signed_raw = raw + signed + "\n"
return signed_raw
def inline(self):
return "{0}:{1}:{2}:{3}".format(self.pubkey_from, self.pubkey_to,
self.blocknumber, self.signatures[0])
'''
Created on 2 déc. 2014
@author: inso
'''
from .. import PROTOCOL_VERSION
from . import Document
import re
class Membership(Document):
'''
This is a utility class to generate membership documents :
Version: VERSION
Type: Membership
Currency: CURRENCY_NAME
Issuer: ISSUER
Block: NUMBER-HASH
Membership: MEMBERSHIP_TYPE
UserID: USER_ID
CertTS: CERTIFICATION_TS
'''
# PUBLIC_KEY:SIGNATURE:NUMBER:HASH:TIMESTAMP:USER_ID
re_inline = re.compile("([1-9A-Za-z][^OIl]{42,45}):([A-Za-z0-9+/]+(?:=|==)?):\
([0-9]+):([0-9a-fA-F]{5,40}):([0-9]+):([^\n]+)\n")
re_type = re.compile("Type: (Membership)")
re_issuer = re.compile("Issuer: ([1-9A-Za-z][^OIl]{42,45})\n")
re_block = re.compile("Block: ([0-9]+)-([0-9a-fA-F]{5,40})\n")
re_membership_type = re.compile("Membership: (IN|OUT)")
re_userid = re.compile("UserID: ([^\n]+)\n")
re_certts = re.compile("CertTS: ([0-9]+)\n")
def __init__(self, version, currency, issuer, block_number, block_hash,
membership_type, uid, cert_ts, signature):
'''
Constructor
'''
super().__init__(version, currency, [signature])
self.issuer = issuer
self.block_number = block_number
self.block_hash = block_hash
self.membership_type = membership_type
self.uid = uid
self.cert_ts = cert_ts
@classmethod
def from_inline(cls, version, currency, membership_type, inline):
data = Membership.re_inline.match(inline)
issuer = data.group(1)
signature = data.group(2)
block_number = int(data.group(3))
block_hash = data.group(4)
cert_ts = int(data.group(5))
uid = data.group(6)
return cls(version, currency, issuer, block_number,
block_hash, membership_type, uid, cert_ts, signature)
@classmethod
def from_signed_raw(cls, raw, signature=None):
lines = raw.splitlines(True)
n = 0
version = int(Membership.re_version.match(lines[n]).group(1))
n = n + 1
Membership.re_type.match(lines[n]).group(1)
n = n + 1
currency = Membership.re_currency.match(lines[n]).group(1)
n = n + 1
issuer = Membership.re_issuer.match(lines[n]).group(1)
n = n + 1
blockid = Membership.re_block.match(lines[n])
blocknumber = int(blockid.group(1))
blockhash = blockid.group(2)
n = n + 1
membership_type = Membership.re_membership_type.match(lines[n]).group(1)
n = n + 1
uid = Membership.re_userid.match(lines[n]).group(1)
n = n + 1
cert_ts = int(Membership.re_certts.match(lines[n]).group(1))
n = n + 1
signature = Membership.re_signature.match(lines[n]).group(1)
n = n + 1
return cls(version, currency, issuer, blocknumber, blockhash,
membership_type, uid, cert_ts, signature)
def raw(self):
return """Version: {0}
Type: Membership
Currency: {1}
Issuer: {2}
Block: {3}-{4}
Membership: {5}
UserID: {6}
CertTS: {7}
""".format(self.version,
self.currency,
self.issuer,
self.block_number, self.block_hash,
self.membership_type,
self.uid,
self.cert_ts)
def inline(self):
return "{0}:{1}:{2}:{3}:{4}:{5}".format(self.issuer,
self.signatures[0],
self.block_number,
self.block_hash,
self.cert_ts,
self.uid)