impuls/lib/python3.11/site-packages/nacl/bindings/crypto_pwhash.py

601 lines
18 KiB
Python
Raw Normal View History

# Copyright 2013 Donald Stufft and individual contributors
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import sys
from typing import Tuple
import nacl.exceptions as exc
from nacl._sodium import ffi, lib
from nacl.exceptions import ensure
has_crypto_pwhash_scryptsalsa208sha256 = bool(
lib.PYNACL_HAS_CRYPTO_PWHASH_SCRYPTSALSA208SHA256
)
crypto_pwhash_scryptsalsa208sha256_STRPREFIX = b""
crypto_pwhash_scryptsalsa208sha256_SALTBYTES = 0
crypto_pwhash_scryptsalsa208sha256_STRBYTES = 0
crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = 0
crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = 0
crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = 0
crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = 0
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = 0
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = 0
if has_crypto_pwhash_scryptsalsa208sha256:
crypto_pwhash_scryptsalsa208sha256_STRPREFIX = ffi.string(
ffi.cast("char *", lib.crypto_pwhash_scryptsalsa208sha256_strprefix())
)[:]
crypto_pwhash_scryptsalsa208sha256_SALTBYTES = (
lib.crypto_pwhash_scryptsalsa208sha256_saltbytes()
)
crypto_pwhash_scryptsalsa208sha256_STRBYTES = (
lib.crypto_pwhash_scryptsalsa208sha256_strbytes()
)
crypto_pwhash_scryptsalsa208sha256_PASSWD_MIN = (
lib.crypto_pwhash_scryptsalsa208sha256_passwd_min()
)
crypto_pwhash_scryptsalsa208sha256_PASSWD_MAX = (
lib.crypto_pwhash_scryptsalsa208sha256_passwd_max()
)
crypto_pwhash_scryptsalsa208sha256_BYTES_MIN = (
lib.crypto_pwhash_scryptsalsa208sha256_bytes_min()
)
crypto_pwhash_scryptsalsa208sha256_BYTES_MAX = (
lib.crypto_pwhash_scryptsalsa208sha256_bytes_max()
)
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MIN = (
lib.crypto_pwhash_scryptsalsa208sha256_memlimit_min()
)
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_MAX = (
lib.crypto_pwhash_scryptsalsa208sha256_memlimit_max()
)
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MIN = (
lib.crypto_pwhash_scryptsalsa208sha256_opslimit_min()
)
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_MAX = (
lib.crypto_pwhash_scryptsalsa208sha256_opslimit_max()
)
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE = (
lib.crypto_pwhash_scryptsalsa208sha256_opslimit_interactive()
)
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE = (
lib.crypto_pwhash_scryptsalsa208sha256_memlimit_interactive()
)
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE = (
lib.crypto_pwhash_scryptsalsa208sha256_opslimit_sensitive()
)
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE = (
lib.crypto_pwhash_scryptsalsa208sha256_memlimit_sensitive()
)
crypto_pwhash_ALG_ARGON2I13: int = lib.crypto_pwhash_alg_argon2i13()
crypto_pwhash_ALG_ARGON2ID13: int = lib.crypto_pwhash_alg_argon2id13()
crypto_pwhash_ALG_DEFAULT: int = lib.crypto_pwhash_alg_default()
crypto_pwhash_SALTBYTES: int = lib.crypto_pwhash_saltbytes()
crypto_pwhash_STRBYTES: int = lib.crypto_pwhash_strbytes()
crypto_pwhash_PASSWD_MIN: int = lib.crypto_pwhash_passwd_min()
crypto_pwhash_PASSWD_MAX: int = lib.crypto_pwhash_passwd_max()
crypto_pwhash_BYTES_MIN: int = lib.crypto_pwhash_bytes_min()
crypto_pwhash_BYTES_MAX: int = lib.crypto_pwhash_bytes_max()
crypto_pwhash_argon2i_STRPREFIX: bytes = ffi.string(
ffi.cast("char *", lib.crypto_pwhash_argon2i_strprefix())
)[:]
crypto_pwhash_argon2i_MEMLIMIT_MIN: int = (
lib.crypto_pwhash_argon2i_memlimit_min()
)
crypto_pwhash_argon2i_MEMLIMIT_MAX: int = (
lib.crypto_pwhash_argon2i_memlimit_max()
)
crypto_pwhash_argon2i_OPSLIMIT_MIN: int = (
lib.crypto_pwhash_argon2i_opslimit_min()
)
crypto_pwhash_argon2i_OPSLIMIT_MAX: int = (
lib.crypto_pwhash_argon2i_opslimit_max()
)
crypto_pwhash_argon2i_OPSLIMIT_INTERACTIVE: int = (
lib.crypto_pwhash_argon2i_opslimit_interactive()
)
crypto_pwhash_argon2i_MEMLIMIT_INTERACTIVE: int = (
lib.crypto_pwhash_argon2i_memlimit_interactive()
)
crypto_pwhash_argon2i_OPSLIMIT_MODERATE: int = (
lib.crypto_pwhash_argon2i_opslimit_moderate()
)
crypto_pwhash_argon2i_MEMLIMIT_MODERATE: int = (
lib.crypto_pwhash_argon2i_memlimit_moderate()
)
crypto_pwhash_argon2i_OPSLIMIT_SENSITIVE: int = (
lib.crypto_pwhash_argon2i_opslimit_sensitive()
)
crypto_pwhash_argon2i_MEMLIMIT_SENSITIVE: int = (
lib.crypto_pwhash_argon2i_memlimit_sensitive()
)
crypto_pwhash_argon2id_STRPREFIX: bytes = ffi.string(
ffi.cast("char *", lib.crypto_pwhash_argon2id_strprefix())
)[:]
crypto_pwhash_argon2id_MEMLIMIT_MIN: int = (
lib.crypto_pwhash_argon2id_memlimit_min()
)
crypto_pwhash_argon2id_MEMLIMIT_MAX: int = (
lib.crypto_pwhash_argon2id_memlimit_max()
)
crypto_pwhash_argon2id_OPSLIMIT_MIN: int = (
lib.crypto_pwhash_argon2id_opslimit_min()
)
crypto_pwhash_argon2id_OPSLIMIT_MAX: int = (
lib.crypto_pwhash_argon2id_opslimit_max()
)
crypto_pwhash_argon2id_OPSLIMIT_INTERACTIVE: int = (
lib.crypto_pwhash_argon2id_opslimit_interactive()
)
crypto_pwhash_argon2id_MEMLIMIT_INTERACTIVE: int = (
lib.crypto_pwhash_argon2id_memlimit_interactive()
)
crypto_pwhash_argon2id_OPSLIMIT_MODERATE: int = (
lib.crypto_pwhash_argon2id_opslimit_moderate()
)
crypto_pwhash_argon2id_MEMLIMIT_MODERATE: int = (
lib.crypto_pwhash_argon2id_memlimit_moderate()
)
crypto_pwhash_argon2id_OPSLIMIT_SENSITIVE: int = (
lib.crypto_pwhash_argon2id_opslimit_sensitive()
)
crypto_pwhash_argon2id_MEMLIMIT_SENSITIVE: int = (
lib.crypto_pwhash_argon2id_memlimit_sensitive()
)
SCRYPT_OPSLIMIT_INTERACTIVE = (
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_INTERACTIVE
)
SCRYPT_MEMLIMIT_INTERACTIVE = (
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_INTERACTIVE
)
SCRYPT_OPSLIMIT_SENSITIVE = (
crypto_pwhash_scryptsalsa208sha256_OPSLIMIT_SENSITIVE
)
SCRYPT_MEMLIMIT_SENSITIVE = (
crypto_pwhash_scryptsalsa208sha256_MEMLIMIT_SENSITIVE
)
SCRYPT_SALTBYTES = crypto_pwhash_scryptsalsa208sha256_SALTBYTES
SCRYPT_STRBYTES = crypto_pwhash_scryptsalsa208sha256_STRBYTES
SCRYPT_PR_MAX = (1 << 30) - 1
LOG2_UINT64_MAX = 63
UINT64_MAX = (1 << 64) - 1
SCRYPT_MAX_MEM = 32 * (1024 * 1024)
def _check_memory_occupation(
n: int, r: int, p: int, maxmem: int = SCRYPT_MAX_MEM
) -> None:
ensure(r != 0, "Invalid block size", raising=exc.ValueError)
ensure(p != 0, "Invalid parallelization factor", raising=exc.ValueError)
ensure(
(n & (n - 1)) == 0,
"Cost factor must be a power of 2",
raising=exc.ValueError,
)
ensure(n > 1, "Cost factor must be at least 2", raising=exc.ValueError)
ensure(
p <= SCRYPT_PR_MAX / r,
"p*r is greater than {}".format(SCRYPT_PR_MAX),
raising=exc.ValueError,
)
ensure(n < (1 << (16 * r)), raising=exc.ValueError)
Blen = p * 128 * r
i = UINT64_MAX / 128
ensure(n + 2 <= i / r, raising=exc.ValueError)
Vlen = 32 * r * (n + 2) * 4
ensure(Blen <= UINT64_MAX - Vlen, raising=exc.ValueError)
ensure(Blen <= sys.maxsize - Vlen, raising=exc.ValueError)
ensure(
Blen + Vlen <= maxmem,
"Memory limit would be exceeded with the choosen n, r, p",
raising=exc.ValueError,
)
def nacl_bindings_pick_scrypt_params(
opslimit: int, memlimit: int
) -> Tuple[int, int, int]:
"""Python implementation of libsodium's pickparams"""
if opslimit < 32768:
opslimit = 32768
r = 8
if opslimit < (memlimit // 32):
p = 1
maxn = opslimit // (4 * r)
for n_log2 in range(1, 63): # pragma: no branch
if (2 ** n_log2) > (maxn // 2):
break
else:
maxn = memlimit // (r * 128)
for n_log2 in range(1, 63): # pragma: no branch
if (2 ** n_log2) > maxn // 2:
break
maxrp = (opslimit // 4) // (2 ** n_log2)
if maxrp > 0x3FFFFFFF: # pragma: no cover
maxrp = 0x3FFFFFFF
p = maxrp // r
return n_log2, r, p
def crypto_pwhash_scryptsalsa208sha256_ll(
passwd: bytes,
salt: bytes,
n: int,
r: int,
p: int,
dklen: int = 64,
maxmem: int = SCRYPT_MAX_MEM,
) -> bytes:
"""
Derive a cryptographic key using the ``passwd`` and ``salt``
given as input.
The work factor can be tuned by by picking different
values for the parameters
:param bytes passwd:
:param bytes salt:
:param bytes salt: *must* be *exactly* :py:const:`.SALTBYTES` long
:param int dklen:
:param int opslimit:
:param int n:
:param int r: block size,
:param int p: the parallelism factor
:param int maxmem: the maximum available memory available for scrypt's
operations
:rtype: bytes
:raises nacl.exceptions.UnavailableError: If called when using a
minimal build of libsodium.
"""
ensure(
has_crypto_pwhash_scryptsalsa208sha256,
"Not available in minimal build",
raising=exc.UnavailableError,
)
ensure(isinstance(n, int), raising=TypeError)
ensure(isinstance(r, int), raising=TypeError)
ensure(isinstance(p, int), raising=TypeError)
ensure(isinstance(passwd, bytes), raising=TypeError)
ensure(isinstance(salt, bytes), raising=TypeError)
_check_memory_occupation(n, r, p, maxmem)
buf = ffi.new("uint8_t[]", dklen)
ret = lib.crypto_pwhash_scryptsalsa208sha256_ll(
passwd, len(passwd), salt, len(salt), n, r, p, buf, dklen
)
ensure(
ret == 0,
"Unexpected failure in key derivation",
raising=exc.RuntimeError,
)
return ffi.buffer(ffi.cast("char *", buf), dklen)[:]
def crypto_pwhash_scryptsalsa208sha256_str(
passwd: bytes,
opslimit: int = SCRYPT_OPSLIMIT_INTERACTIVE,
memlimit: int = SCRYPT_MEMLIMIT_INTERACTIVE,
) -> bytes:
"""
Derive a cryptographic key using the ``passwd`` and ``salt``
given as input, returning a string representation which includes
the salt and the tuning parameters.
The returned string can be directly stored as a password hash.
See :py:func:`.crypto_pwhash_scryptsalsa208sha256` for a short
discussion about ``opslimit`` and ``memlimit`` values.
:param bytes passwd:
:param int opslimit:
:param int memlimit:
:return: serialized key hash, including salt and tuning parameters
:rtype: bytes
:raises nacl.exceptions.UnavailableError: If called when using a
minimal build of libsodium.
"""
ensure(
has_crypto_pwhash_scryptsalsa208sha256,
"Not available in minimal build",
raising=exc.UnavailableError,
)
buf = ffi.new("char[]", SCRYPT_STRBYTES)
ret = lib.crypto_pwhash_scryptsalsa208sha256_str(
buf, passwd, len(passwd), opslimit, memlimit
)
ensure(
ret == 0,
"Unexpected failure in password hashing",
raising=exc.RuntimeError,
)
return ffi.string(buf)
def crypto_pwhash_scryptsalsa208sha256_str_verify(
passwd_hash: bytes, passwd: bytes
) -> bool:
"""
Verifies the ``passwd`` against the ``passwd_hash`` that was generated.
Returns True or False depending on the success
:param passwd_hash: bytes
:param passwd: bytes
:rtype: boolean
:raises nacl.exceptions.UnavailableError: If called when using a
minimal build of libsodium.
"""
ensure(
has_crypto_pwhash_scryptsalsa208sha256,
"Not available in minimal build",
raising=exc.UnavailableError,
)
ensure(
len(passwd_hash) == SCRYPT_STRBYTES - 1,
"Invalid password hash",
raising=exc.ValueError,
)
ret = lib.crypto_pwhash_scryptsalsa208sha256_str_verify(
passwd_hash, passwd, len(passwd)
)
ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
# all went well, therefore:
return True
def _check_argon2_limits_alg(opslimit: int, memlimit: int, alg: int) -> None:
if alg == crypto_pwhash_ALG_ARGON2I13:
if memlimit < crypto_pwhash_argon2i_MEMLIMIT_MIN:
raise exc.ValueError(
"memlimit must be at least {} bytes".format(
crypto_pwhash_argon2i_MEMLIMIT_MIN
)
)
elif memlimit > crypto_pwhash_argon2i_MEMLIMIT_MAX:
raise exc.ValueError(
"memlimit must be at most {} bytes".format(
crypto_pwhash_argon2i_MEMLIMIT_MAX
)
)
if opslimit < crypto_pwhash_argon2i_OPSLIMIT_MIN:
raise exc.ValueError(
"opslimit must be at least {}".format(
crypto_pwhash_argon2i_OPSLIMIT_MIN
)
)
elif opslimit > crypto_pwhash_argon2i_OPSLIMIT_MAX:
raise exc.ValueError(
"opslimit must be at most {}".format(
crypto_pwhash_argon2i_OPSLIMIT_MAX
)
)
elif alg == crypto_pwhash_ALG_ARGON2ID13:
if memlimit < crypto_pwhash_argon2id_MEMLIMIT_MIN:
raise exc.ValueError(
"memlimit must be at least {} bytes".format(
crypto_pwhash_argon2id_MEMLIMIT_MIN
)
)
elif memlimit > crypto_pwhash_argon2id_MEMLIMIT_MAX:
raise exc.ValueError(
"memlimit must be at most {} bytes".format(
crypto_pwhash_argon2id_MEMLIMIT_MAX
)
)
if opslimit < crypto_pwhash_argon2id_OPSLIMIT_MIN:
raise exc.ValueError(
"opslimit must be at least {}".format(
crypto_pwhash_argon2id_OPSLIMIT_MIN
)
)
elif opslimit > crypto_pwhash_argon2id_OPSLIMIT_MAX:
raise exc.ValueError(
"opslimit must be at most {}".format(
crypto_pwhash_argon2id_OPSLIMIT_MAX
)
)
else:
raise exc.TypeError("Unsupported algorithm")
def crypto_pwhash_alg(
outlen: int,
passwd: bytes,
salt: bytes,
opslimit: int,
memlimit: int,
alg: int,
) -> bytes:
"""
Derive a raw cryptographic key using the ``passwd`` and the ``salt``
given as input to the ``alg`` algorithm.
:param outlen: the length of the derived key
:type outlen: int
:param passwd: The input password
:type passwd: bytes
:param salt:
:type salt: bytes
:param opslimit: computational cost
:type opslimit: int
:param memlimit: memory cost
:type memlimit: int
:param alg: algorithm identifier
:type alg: int
:return: derived key
:rtype: bytes
"""
ensure(isinstance(outlen, int), raising=exc.TypeError)
ensure(isinstance(opslimit, int), raising=exc.TypeError)
ensure(isinstance(memlimit, int), raising=exc.TypeError)
ensure(isinstance(alg, int), raising=exc.TypeError)
ensure(isinstance(passwd, bytes), raising=exc.TypeError)
if len(salt) != crypto_pwhash_SALTBYTES:
raise exc.ValueError(
"salt must be exactly {} bytes long".format(
crypto_pwhash_SALTBYTES
)
)
if outlen < crypto_pwhash_BYTES_MIN:
raise exc.ValueError(
"derived key must be at least {} bytes long".format(
crypto_pwhash_BYTES_MIN
)
)
elif outlen > crypto_pwhash_BYTES_MAX:
raise exc.ValueError(
"derived key must be at most {} bytes long".format(
crypto_pwhash_BYTES_MAX
)
)
_check_argon2_limits_alg(opslimit, memlimit, alg)
outbuf = ffi.new("unsigned char[]", outlen)
ret = lib.crypto_pwhash(
outbuf, outlen, passwd, len(passwd), salt, opslimit, memlimit, alg
)
ensure(
ret == 0,
"Unexpected failure in key derivation",
raising=exc.RuntimeError,
)
return ffi.buffer(outbuf, outlen)[:]
def crypto_pwhash_str_alg(
passwd: bytes,
opslimit: int,
memlimit: int,
alg: int,
) -> bytes:
"""
Derive a cryptographic key using the ``passwd`` given as input
and a random salt, returning a string representation which
includes the salt, the tuning parameters and the used algorithm.
:param passwd: The input password
:type passwd: bytes
:param opslimit: computational cost
:type opslimit: int
:param memlimit: memory cost
:type memlimit: int
:param alg: The algorithm to use
:type alg: int
:return: serialized derived key and parameters
:rtype: bytes
"""
ensure(isinstance(opslimit, int), raising=TypeError)
ensure(isinstance(memlimit, int), raising=TypeError)
ensure(isinstance(passwd, bytes), raising=TypeError)
_check_argon2_limits_alg(opslimit, memlimit, alg)
outbuf = ffi.new("char[]", 128)
ret = lib.crypto_pwhash_str_alg(
outbuf, passwd, len(passwd), opslimit, memlimit, alg
)
ensure(
ret == 0,
"Unexpected failure in key derivation",
raising=exc.RuntimeError,
)
return ffi.string(outbuf)
def crypto_pwhash_str_verify(passwd_hash: bytes, passwd: bytes) -> bool:
"""
Verifies the ``passwd`` against a given password hash.
Returns True on success, raises InvalidkeyError on failure
:param passwd_hash: saved password hash
:type passwd_hash: bytes
:param passwd: password to be checked
:type passwd: bytes
:return: success
:rtype: boolean
"""
ensure(isinstance(passwd_hash, bytes), raising=TypeError)
ensure(isinstance(passwd, bytes), raising=TypeError)
ensure(
len(passwd_hash) <= 127,
"Hash must be at most 127 bytes long",
raising=exc.ValueError,
)
ret = lib.crypto_pwhash_str_verify(passwd_hash, passwd, len(passwd))
ensure(ret == 0, "Wrong password", raising=exc.InvalidkeyError)
# all went well, therefore:
return True
crypto_pwhash_argon2i_str_verify = crypto_pwhash_str_verify