mirror of
https://gitlab.com/MoonTestUse1/AdministrationItDepartmens.git
synced 2025-08-14 00:25:46 +02:00
Все подряд
This commit is contained in:
383
.venv2/Lib/site-packages/passlib/handlers/scrypt.py
Normal file
383
.venv2/Lib/site-packages/passlib/handlers/scrypt.py
Normal file
@@ -0,0 +1,383 @@
|
||||
"""passlib.handlers.scrypt -- scrypt password hash"""
|
||||
#=============================================================================
|
||||
# imports
|
||||
#=============================================================================
|
||||
from __future__ import with_statement, absolute_import
|
||||
# core
|
||||
import logging; log = logging.getLogger(__name__)
|
||||
# site
|
||||
# pkg
|
||||
from passlib.crypto import scrypt as _scrypt
|
||||
from passlib.utils import h64, to_bytes
|
||||
from passlib.utils.binary import h64, b64s_decode, b64s_encode
|
||||
from passlib.utils.compat import u, bascii_to_str, suppress_cause
|
||||
from passlib.utils.decor import classproperty
|
||||
import passlib.utils.handlers as uh
|
||||
# local
|
||||
__all__ = [
|
||||
"scrypt",
|
||||
]
|
||||
|
||||
#=============================================================================
|
||||
# scrypt format identifiers
|
||||
#=============================================================================
|
||||
|
||||
IDENT_SCRYPT = u("$scrypt$") # identifier used by passlib
|
||||
IDENT_7 = u("$7$") # used by official scrypt spec
|
||||
|
||||
_UDOLLAR = u("$")
|
||||
|
||||
#=============================================================================
|
||||
# handler
|
||||
#=============================================================================
|
||||
class scrypt(uh.ParallelismMixin, uh.HasRounds, uh.HasRawSalt, uh.HasRawChecksum, uh.HasManyIdents,
|
||||
uh.GenericHandler):
|
||||
"""This class implements an SCrypt-based password [#scrypt-home]_ hash, and follows the :ref:`password-hash-api`.
|
||||
|
||||
It supports a variable-length salt, a variable number of rounds,
|
||||
as well as some custom tuning parameters unique to scrypt (see below).
|
||||
|
||||
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
|
||||
|
||||
:type salt: str
|
||||
:param salt:
|
||||
Optional salt string.
|
||||
If specified, the length must be between 0-1024 bytes.
|
||||
If not specified, one will be auto-generated (this is recommended).
|
||||
|
||||
:type salt_size: int
|
||||
:param salt_size:
|
||||
Optional number of bytes to use when autogenerating new salts.
|
||||
Defaults to 16 bytes, but can be any value between 0 and 1024.
|
||||
|
||||
:type rounds: int
|
||||
:param rounds:
|
||||
Optional number of rounds to use.
|
||||
Defaults to 16, but must be within ``range(1,32)``.
|
||||
|
||||
.. warning::
|
||||
|
||||
Unlike many hash algorithms, increasing the rounds value
|
||||
will increase both the time *and memory* required to hash a password.
|
||||
|
||||
:type block_size: int
|
||||
:param block_size:
|
||||
Optional block size to pass to scrypt hash function (the ``r`` parameter).
|
||||
Useful for tuning scrypt to optimal performance for your CPU architecture.
|
||||
Defaults to 8.
|
||||
|
||||
:type parallelism: int
|
||||
:param parallelism:
|
||||
Optional parallelism to pass to scrypt hash function (the ``p`` parameter).
|
||||
Defaults to 1.
|
||||
|
||||
:type relaxed: bool
|
||||
:param relaxed:
|
||||
By default, providing an invalid value for one of the other
|
||||
keywords will result in a :exc:`ValueError`. If ``relaxed=True``,
|
||||
and the error can be corrected, a :exc:`~passlib.exc.PasslibHashWarning`
|
||||
will be issued instead. Correctable errors include ``rounds``
|
||||
that are too small or too large, and ``salt`` strings that are too long.
|
||||
|
||||
.. note::
|
||||
|
||||
The underlying scrypt hash function has a number of limitations
|
||||
on it's parameter values, which forbids certain combinations of settings.
|
||||
The requirements are:
|
||||
|
||||
* ``linear_rounds = 2**<some positive integer>``
|
||||
* ``linear_rounds < 2**(16 * block_size)``
|
||||
* ``block_size * parallelism <= 2**30-1``
|
||||
|
||||
.. todo::
|
||||
|
||||
This class currently does not support configuring default values
|
||||
for ``block_size`` or ``parallelism`` via a :class:`~passlib.context.CryptContext`
|
||||
configuration.
|
||||
"""
|
||||
|
||||
#===================================================================
|
||||
# class attrs
|
||||
#===================================================================
|
||||
|
||||
#------------------------
|
||||
# PasswordHash
|
||||
#------------------------
|
||||
name = "scrypt"
|
||||
setting_kwds = ("ident", "salt", "salt_size", "rounds", "block_size", "parallelism")
|
||||
|
||||
#------------------------
|
||||
# GenericHandler
|
||||
#------------------------
|
||||
# NOTE: scrypt supports arbitrary output sizes. since it's output runs through
|
||||
# pbkdf2-hmac-sha256 before returning, and this could be raised eventually...
|
||||
# but a 256-bit digest is more than sufficient for password hashing.
|
||||
# XXX: make checksum size configurable? could merge w/ argon2 code that does this.
|
||||
checksum_size = 32
|
||||
|
||||
#------------------------
|
||||
# HasManyIdents
|
||||
#------------------------
|
||||
default_ident = IDENT_SCRYPT
|
||||
ident_values = (IDENT_SCRYPT, IDENT_7)
|
||||
|
||||
#------------------------
|
||||
# HasRawSalt
|
||||
#------------------------
|
||||
default_salt_size = 16
|
||||
max_salt_size = 1024
|
||||
|
||||
#------------------------
|
||||
# HasRounds
|
||||
#------------------------
|
||||
# TODO: would like to dynamically pick this based on system
|
||||
default_rounds = 16
|
||||
min_rounds = 1
|
||||
max_rounds = 31 # limited by scrypt alg
|
||||
rounds_cost = "log2"
|
||||
|
||||
# TODO: make default block size configurable via using(), and deprecatable via .needs_update()
|
||||
|
||||
#===================================================================
|
||||
# instance attrs
|
||||
#===================================================================
|
||||
|
||||
#: default parallelism setting (min=1 currently hardcoded in mixin)
|
||||
parallelism = 1
|
||||
|
||||
#: default block size setting
|
||||
block_size = 8
|
||||
|
||||
#===================================================================
|
||||
# variant constructor
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def using(cls, block_size=None, **kwds):
|
||||
subcls = super(scrypt, cls).using(**kwds)
|
||||
if block_size is not None:
|
||||
if isinstance(block_size, uh.native_string_types):
|
||||
block_size = int(block_size)
|
||||
subcls.block_size = subcls._norm_block_size(block_size, relaxed=kwds.get("relaxed"))
|
||||
|
||||
# make sure param combination is valid for scrypt()
|
||||
try:
|
||||
_scrypt.validate(1 << cls.default_rounds, cls.block_size, cls.parallelism)
|
||||
except ValueError as err:
|
||||
raise suppress_cause(ValueError("scrypt: invalid settings combination: " + str(err)))
|
||||
|
||||
return subcls
|
||||
|
||||
#===================================================================
|
||||
# parsing
|
||||
#===================================================================
|
||||
|
||||
@classmethod
|
||||
def from_string(cls, hash):
|
||||
return cls(**cls.parse(hash))
|
||||
|
||||
@classmethod
|
||||
def parse(cls, hash):
|
||||
ident, suffix = cls._parse_ident(hash)
|
||||
func = getattr(cls, "_parse_%s_string" % ident.strip(_UDOLLAR), None)
|
||||
if func:
|
||||
return func(suffix)
|
||||
else:
|
||||
raise uh.exc.InvalidHashError(cls)
|
||||
|
||||
#
|
||||
# passlib's format:
|
||||
# $scrypt$ln=<logN>,r=<r>,p=<p>$<salt>[$<digest>]
|
||||
# where:
|
||||
# logN, r, p -- decimal-encoded positive integer, no zero-padding
|
||||
# logN -- log cost setting
|
||||
# r -- block size setting (usually 8)
|
||||
# p -- parallelism setting (usually 1)
|
||||
# salt, digest -- b64-nopad encoded bytes
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def _parse_scrypt_string(cls, suffix):
|
||||
# break params, salt, and digest sections
|
||||
parts = suffix.split("$")
|
||||
if len(parts) == 3:
|
||||
params, salt, digest = parts
|
||||
elif len(parts) == 2:
|
||||
params, salt = parts
|
||||
digest = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls, "malformed hash")
|
||||
|
||||
# break params apart
|
||||
parts = params.split(",")
|
||||
if len(parts) == 3:
|
||||
nstr, bstr, pstr = parts
|
||||
assert nstr.startswith("ln=")
|
||||
assert bstr.startswith("r=")
|
||||
assert pstr.startswith("p=")
|
||||
else:
|
||||
raise uh.exc.MalformedHashError(cls, "malformed settings field")
|
||||
|
||||
return dict(
|
||||
ident=IDENT_SCRYPT,
|
||||
rounds=int(nstr[3:]),
|
||||
block_size=int(bstr[2:]),
|
||||
parallelism=int(pstr[2:]),
|
||||
salt=b64s_decode(salt.encode("ascii")),
|
||||
checksum=b64s_decode(digest.encode("ascii")) if digest else None,
|
||||
)
|
||||
|
||||
#
|
||||
# official format specification defined at
|
||||
# https://gitlab.com/jas/scrypt-unix-crypt/blob/master/unix-scrypt.txt
|
||||
# format:
|
||||
# $7$<N><rrrrr><ppppp><salt...>[$<digest>]
|
||||
# 0 12345 67890 1
|
||||
# where:
|
||||
# All bytes use h64-little-endian encoding
|
||||
# N: 6-bit log cost setting
|
||||
# r: 30-bit block size setting
|
||||
# p: 30-bit parallelism setting
|
||||
# salt: variable length salt bytes
|
||||
# digest: fixed 32-byte digest
|
||||
#
|
||||
|
||||
@classmethod
|
||||
def _parse_7_string(cls, suffix):
|
||||
# XXX: annoyingly, official spec embeds salt *raw*, yet doesn't specify a hash encoding.
|
||||
# so assuming only h64 chars are valid for salt, and are ASCII encoded.
|
||||
|
||||
# split into params & digest
|
||||
parts = suffix.encode("ascii").split(b"$")
|
||||
if len(parts) == 2:
|
||||
params, digest = parts
|
||||
elif len(parts) == 1:
|
||||
params, = parts
|
||||
digest = None
|
||||
else:
|
||||
raise uh.exc.MalformedHashError()
|
||||
|
||||
# parse params & return
|
||||
if len(params) < 11:
|
||||
raise uh.exc.MalformedHashError(cls, "params field too short")
|
||||
return dict(
|
||||
ident=IDENT_7,
|
||||
rounds=h64.decode_int6(params[:1]),
|
||||
block_size=h64.decode_int30(params[1:6]),
|
||||
parallelism=h64.decode_int30(params[6:11]),
|
||||
salt=params[11:],
|
||||
checksum=h64.decode_bytes(digest) if digest else None,
|
||||
)
|
||||
|
||||
#===================================================================
|
||||
# formatting
|
||||
#===================================================================
|
||||
def to_string(self):
|
||||
ident = self.ident
|
||||
if ident == IDENT_SCRYPT:
|
||||
return "$scrypt$ln=%d,r=%d,p=%d$%s$%s" % (
|
||||
self.rounds,
|
||||
self.block_size,
|
||||
self.parallelism,
|
||||
bascii_to_str(b64s_encode(self.salt)),
|
||||
bascii_to_str(b64s_encode(self.checksum)),
|
||||
)
|
||||
else:
|
||||
assert ident == IDENT_7
|
||||
salt = self.salt
|
||||
try:
|
||||
salt.decode("ascii")
|
||||
except UnicodeDecodeError:
|
||||
raise suppress_cause(NotImplementedError("scrypt $7$ hashes dont support non-ascii salts"))
|
||||
return bascii_to_str(b"".join([
|
||||
b"$7$",
|
||||
h64.encode_int6(self.rounds),
|
||||
h64.encode_int30(self.block_size),
|
||||
h64.encode_int30(self.parallelism),
|
||||
self.salt,
|
||||
b"$",
|
||||
h64.encode_bytes(self.checksum)
|
||||
]))
|
||||
|
||||
#===================================================================
|
||||
# init
|
||||
#===================================================================
|
||||
def __init__(self, block_size=None, **kwds):
|
||||
super(scrypt, self).__init__(**kwds)
|
||||
|
||||
# init block size
|
||||
if block_size is None:
|
||||
assert uh.validate_default_value(self, self.block_size, self._norm_block_size,
|
||||
param="block_size")
|
||||
else:
|
||||
self.block_size = self._norm_block_size(block_size)
|
||||
|
||||
# NOTE: if hash contains invalid complex constraint, relying on error
|
||||
# being raised by scrypt call in _calc_checksum()
|
||||
|
||||
@classmethod
|
||||
def _norm_block_size(cls, block_size, relaxed=False):
|
||||
return uh.norm_integer(cls, block_size, min=1, param="block_size", relaxed=relaxed)
|
||||
|
||||
def _generate_salt(self):
|
||||
salt = super(scrypt, self)._generate_salt()
|
||||
if self.ident == IDENT_7:
|
||||
# this format doesn't support non-ascii salts.
|
||||
# as workaround, we take raw bytes, encoded to base64
|
||||
salt = b64s_encode(salt)
|
||||
return salt
|
||||
|
||||
#===================================================================
|
||||
# backend configuration
|
||||
# NOTE: this following HasManyBackends' API, but provides it's own implementation,
|
||||
# which actually switches the backend that 'passlib.crypto.scrypt.scrypt()' uses.
|
||||
#===================================================================
|
||||
|
||||
@classproperty
|
||||
def backends(cls):
|
||||
return _scrypt.backend_values
|
||||
|
||||
@classmethod
|
||||
def get_backend(cls):
|
||||
return _scrypt.backend
|
||||
|
||||
@classmethod
|
||||
def has_backend(cls, name="any"):
|
||||
try:
|
||||
cls.set_backend(name, dryrun=True)
|
||||
return True
|
||||
except uh.exc.MissingBackendError:
|
||||
return False
|
||||
|
||||
@classmethod
|
||||
def set_backend(cls, name="any", dryrun=False):
|
||||
_scrypt._set_backend(name, dryrun=dryrun)
|
||||
|
||||
#===================================================================
|
||||
# digest calculation
|
||||
#===================================================================
|
||||
def _calc_checksum(self, secret):
|
||||
secret = to_bytes(secret, param="secret")
|
||||
return _scrypt.scrypt(secret, self.salt, n=(1 << self.rounds), r=self.block_size,
|
||||
p=self.parallelism, keylen=self.checksum_size)
|
||||
|
||||
#===================================================================
|
||||
# hash migration
|
||||
#===================================================================
|
||||
|
||||
def _calc_needs_update(self, **kwds):
|
||||
"""
|
||||
mark hash as needing update if rounds is outside desired bounds.
|
||||
"""
|
||||
# XXX: for now, marking all hashes which don't have matching block_size setting
|
||||
if self.block_size != type(self).block_size:
|
||||
return True
|
||||
return super(scrypt, self)._calc_needs_update(**kwds)
|
||||
|
||||
#===================================================================
|
||||
# eoc
|
||||
#===================================================================
|
||||
|
||||
#=============================================================================
|
||||
# eof
|
||||
#=============================================================================
|
Reference in New Issue
Block a user