# 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