|
|
|
|
|
""" |
|
Low-level functions if you want to build your own higher level abstractions. |
|
|
|
.. warning:: |
|
This is a "Hazardous Materials" module. You should **ONLY** use it if |
|
you're 100% absolutely sure that you know what you're doing because this |
|
module is full of land mines, dragons, and dinosaurs with laser guns. |
|
""" |
|
|
|
from __future__ import annotations |
|
|
|
from enum import Enum |
|
from typing import Any |
|
|
|
from _argon2_cffi_bindings import ffi, lib |
|
|
|
from ._typing import Literal |
|
from .exceptions import HashingError, VerificationError, VerifyMismatchError |
|
|
|
|
|
__all__ = [ |
|
"ARGON2_VERSION", |
|
"Type", |
|
"ffi", |
|
"hash_secret", |
|
"hash_secret_raw", |
|
"verify_secret", |
|
] |
|
|
|
ARGON2_VERSION = lib.ARGON2_VERSION_NUMBER |
|
""" |
|
The latest version of the Argon2 algorithm that is supported (and used by |
|
default). |
|
|
|
.. versionadded:: 16.1.0 |
|
""" |
|
|
|
|
|
class Type(Enum): |
|
""" |
|
Enum of Argon2 variants. |
|
|
|
Please see :doc:`parameters` on how to pick one. |
|
""" |
|
|
|
D = lib.Argon2_d |
|
I = lib.Argon2_i |
|
ID = lib.Argon2_id |
|
|
|
|
|
def hash_secret( |
|
secret: bytes, |
|
salt: bytes, |
|
time_cost: int, |
|
memory_cost: int, |
|
parallelism: int, |
|
hash_len: int, |
|
type: Type, |
|
version: int = ARGON2_VERSION, |
|
) -> bytes: |
|
""" |
|
Hash *secret* and return an **encoded** hash. |
|
|
|
An encoded hash can be directly passed into :func:`verify_secret` as it |
|
contains all parameters and the salt. |
|
|
|
:param bytes secret: Secret to hash. |
|
:param bytes salt: A salt_. Should be random and different for each |
|
secret. |
|
:param Type type: Which Argon2 variant to use. |
|
:param int version: Which Argon2 version to use. |
|
|
|
For an explanation of the Argon2 parameters see |
|
:class:`argon2.PasswordHasher`. |
|
|
|
:rtype: bytes |
|
|
|
:raises argon2.exceptions.HashingError: If hashing fails. |
|
|
|
.. versionadded:: 16.0.0 |
|
|
|
.. _salt: https://en.wikipedia.org/wiki/Salt_(cryptography) |
|
.. _kibibytes: https://en.wikipedia.org/wiki/Binary_prefix#kibi |
|
""" |
|
size = ( |
|
lib.argon2_encodedlen( |
|
time_cost, |
|
memory_cost, |
|
parallelism, |
|
len(salt), |
|
hash_len, |
|
type.value, |
|
) |
|
+ 1 |
|
) |
|
buf = ffi.new("char[]", size) |
|
rv = lib.argon2_hash( |
|
time_cost, |
|
memory_cost, |
|
parallelism, |
|
ffi.new("uint8_t[]", secret), |
|
len(secret), |
|
ffi.new("uint8_t[]", salt), |
|
len(salt), |
|
ffi.NULL, |
|
hash_len, |
|
buf, |
|
size, |
|
type.value, |
|
version, |
|
) |
|
if rv != lib.ARGON2_OK: |
|
raise HashingError(error_to_str(rv)) |
|
|
|
return ffi.string(buf) |
|
|
|
|
|
def hash_secret_raw( |
|
secret: bytes, |
|
salt: bytes, |
|
time_cost: int, |
|
memory_cost: int, |
|
parallelism: int, |
|
hash_len: int, |
|
type: Type, |
|
version: int = ARGON2_VERSION, |
|
) -> bytes: |
|
""" |
|
Hash *password* and return a **raw** hash. |
|
|
|
This function takes the same parameters as :func:`hash_secret`. |
|
|
|
.. versionadded:: 16.0.0 |
|
""" |
|
buf = ffi.new("uint8_t[]", hash_len) |
|
|
|
rv = lib.argon2_hash( |
|
time_cost, |
|
memory_cost, |
|
parallelism, |
|
ffi.new("uint8_t[]", secret), |
|
len(secret), |
|
ffi.new("uint8_t[]", salt), |
|
len(salt), |
|
buf, |
|
hash_len, |
|
ffi.NULL, |
|
0, |
|
type.value, |
|
version, |
|
) |
|
if rv != lib.ARGON2_OK: |
|
raise HashingError(error_to_str(rv)) |
|
|
|
return bytes(ffi.buffer(buf, hash_len)) |
|
|
|
|
|
def verify_secret(hash: bytes, secret: bytes, type: Type) -> Literal[True]: |
|
""" |
|
Verify whether *secret* is correct for *hash* of *type*. |
|
|
|
:param bytes hash: An encoded Argon2 hash as returned by |
|
:func:`hash_secret`. |
|
:param bytes secret: The secret to verify whether it matches the one |
|
in *hash*. |
|
:param Type type: Type for *hash*. |
|
|
|
:raises argon2.exceptions.VerifyMismatchError: If verification fails |
|
because *hash* is not valid for *secret* of *type*. |
|
:raises argon2.exceptions.VerificationError: If verification fails for |
|
other reasons. |
|
|
|
:return: ``True`` on success, raise |
|
:exc:`~argon2.exceptions.VerificationError` otherwise. |
|
:rtype: bool |
|
|
|
.. versionadded:: 16.0.0 |
|
.. versionchanged:: 16.1.0 |
|
Raise :exc:`~argon2.exceptions.VerifyMismatchError` on mismatches |
|
instead of its more generic superclass. |
|
""" |
|
rv = lib.argon2_verify( |
|
ffi.new("char[]", hash), |
|
ffi.new("uint8_t[]", secret), |
|
len(secret), |
|
type.value, |
|
) |
|
|
|
if rv == lib.ARGON2_OK: |
|
return True |
|
|
|
if rv == lib.ARGON2_VERIFY_MISMATCH: |
|
raise VerifyMismatchError(error_to_str(rv)) |
|
|
|
raise VerificationError(error_to_str(rv)) |
|
|
|
|
|
def core(context: Any, type: int) -> int: |
|
""" |
|
Direct binding to the ``argon2_ctx`` function. |
|
|
|
.. warning:: |
|
This is a strictly advanced function working on raw C data structures. |
|
Both Argon2's and *argon2-cffi*'s higher-level bindings do a lot of |
|
sanity checks and housekeeping work that *you* are now responsible for |
|
(e.g. clearing buffers). The structure of the *context* object can, |
|
has, and will change with *any* release! |
|
|
|
Use at your own peril; *argon2-cffi* does *not* use this binding |
|
itself. |
|
|
|
:param context: A CFFI Argon2 context object (i.e. an ``struct |
|
Argon2_Context`` / ``argon2_context``). |
|
:param int type: Which Argon2 variant to use. You can use the ``value`` |
|
field of :class:`Type`'s fields. |
|
|
|
:rtype: int |
|
:return: An Argon2 error code. Can be transformed into a string using |
|
:func:`error_to_str`. |
|
|
|
.. versionadded:: 16.0.0 |
|
""" |
|
return lib.argon2_ctx(context, type) |
|
|
|
|
|
def error_to_str(error: int) -> str: |
|
""" |
|
Convert an Argon2 error code into a native string. |
|
|
|
:param int error: An Argon2 error code as returned by :func:`core`. |
|
|
|
:rtype: str |
|
|
|
.. versionadded:: 16.0.0 |
|
""" |
|
return ffi.string(lib.argon2_error_message(error)).decode("ascii") |
|
|