1 2 3 4 5 6 7 8 9 10 11 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 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 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121
|
import passlib.utils.handlers as uh
from passlib.crypto.digest import compile_hmac
from passlib.utils.binary import h64
__all__: list[str] = []
_BNULL = b"\x00"
class sha1_crypt( # type: ignore[misc]
uh.HasManyBackends,
uh.HasRounds,
uh.HasSalt,
uh.GenericHandler,
):
"""This class implements the SHA1-Crypt password hash, and follows the :ref:`password-hash-api`.
It supports a variable-length salt, and a variable number of rounds.
The :meth:`~passlib.ifc.PasswordHash.using` method accepts the following optional keywords:
:type salt: str
:param salt:
Optional salt string.
If not specified, an 8 character one will be autogenerated (this is recommended).
If specified, it must be 0-64 characters, drawn from the regexp range ``[./0-9A-Za-z]``.
:type salt_size: int
:param salt_size:
Optional number of bytes to use when autogenerating new salts.
Defaults to 8 bytes, but can be any value between 0 and 64.
:type rounds: int
:param rounds:
Optional number of rounds to use.
Defaults to 480000, must be between 1 and 4294967295, inclusive.
: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.
.. versionadded:: 1.6
"""
# --GenericHandler--
name = "sha1_crypt"
setting_kwds = ("salt", "salt_size", "rounds")
ident = "$sha1$"
checksum_size = 28
checksum_chars = uh.HASH64_CHARS
# --HasSalt--
default_salt_size = 8
max_salt_size = 64
salt_chars = uh.HASH64_CHARS
# --HasRounds--
default_rounds = 480000 # current passlib default
min_rounds = 1 # really, this should be higher.
max_rounds = 4294967295 # 32-bit integer limit
rounds_cost = "linear"
@classmethod
def from_string(cls, hash):
rounds, salt, chk = uh.parse_mc3(hash, cls.ident, handler=cls)
return cls(rounds=rounds, salt=salt, checksum=chk)
def to_string(self, config=False):
chk = None if config else self.checksum
return uh.render_mc3(self.ident, self.rounds, self.salt, chk)
backends = ("builtin",)
# ---------------------------------------------------------------
# builtin backend
# ---------------------------------------------------------------
@classmethod
def _load_backend_builtin(cls):
cls._set_calc_checksum_backend(cls._calc_checksum_builtin)
return True
def _calc_checksum_builtin(self, secret):
if isinstance(secret, str):
secret = secret.encode("utf-8")
if _BNULL in secret:
raise uh.exc.NullPasswordError(self)
rounds = self.rounds
# NOTE: this seed value is NOT the same as the config string
result = (f"{self.salt}$sha1${rounds}").encode("ascii")
# NOTE: this algorithm is essentially PBKDF1, modified to use HMAC.
keyed_hmac = compile_hmac("sha1", secret)
for _ in range(rounds):
result = keyed_hmac(result)
return h64.encode_transposed_bytes(result, self._chk_offsets).decode("ascii")
_chk_offsets = [
2,
1,
0,
5,
4,
3,
8,
7,
6,
11,
10,
9,
14,
13,
12,
17,
16,
15,
0,
19,
18,
]
|