256 lines
6.6 KiB
Python
256 lines
6.6 KiB
Python
# -*- coding: utf-8 -*-
|
||
"""
|
||
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 absolute_import, division, print_function
|
||
|
||
from enum import Enum
|
||
|
||
from six import PY3
|
||
|
||
from ._ffi import ffi, lib
|
||
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
|
||
r"""
|
||
Argon2\ **d** is faster and uses data-depending memory access, which makes
|
||
it less suitable for hashing secrets and more suitable for cryptocurrencies
|
||
and applications with no threats from side-channel timing attacks.
|
||
"""
|
||
I = lib.Argon2_i
|
||
r"""
|
||
Argon2\ **i** uses data-independent memory access. Argon2i is slower as
|
||
it makes more passes over the memory to protect from tradeoff attacks.
|
||
"""
|
||
ID = lib.Argon2_id
|
||
r"""
|
||
Argon2\ **id** is a hybrid of Argon2i and Argon2d, using a combination of
|
||
data-depending and data-independent memory accesses, which gives some of
|
||
Argon2i's resistance to side-channel cache timing attacks and much of
|
||
Argon2d's resistance to GPU cracking attacks.
|
||
|
||
That makes it the preferred type for password hashing and password-based
|
||
key derivation.
|
||
|
||
.. versionadded:: 16.3.0
|
||
"""
|
||
|
||
|
||
def hash_secret(
|
||
secret,
|
||
salt,
|
||
time_cost,
|
||
memory_cost,
|
||
parallelism,
|
||
hash_len,
|
||
type,
|
||
version=ARGON2_VERSION,
|
||
):
|
||
"""
|
||
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:`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,
|
||
salt,
|
||
time_cost,
|
||
memory_cost,
|
||
parallelism,
|
||
hash_len,
|
||
type,
|
||
version=ARGON2_VERSION,
|
||
):
|
||
"""
|
||
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, secret, type):
|
||
"""
|
||
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
|
||
elif rv == lib.ARGON2_VERIFY_MISMATCH:
|
||
raise VerifyMismatchError(error_to_str(rv))
|
||
else:
|
||
raise VerificationError(error_to_str(rv))
|
||
|
||
|
||
def core(context, type):
|
||
"""
|
||
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):
|
||
"""
|
||
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
|
||
"""
|
||
msg = ffi.string(lib.argon2_error_message(error))
|
||
if PY3:
|
||
msg = msg.decode("ascii")
|
||
return msg
|