Skip to content

Commit

Permalink
Hard-deprecate legacy functions
Browse files Browse the repository at this point in the history
  • Loading branch information
hynek committed Aug 15, 2023
1 parent 413e03a commit 456e175
Show file tree
Hide file tree
Showing 4 changed files with 94 additions and 37 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,9 @@ What explicitly *may* change over time are the default hashing parameters and th
- The `InvalidHash` exception is deprecated in favor of `InvalidHashError`.
No plans for removal currently exist and the names can (but shouldn't) be used interchangeably.

- `argon2.hash_password()`, `argon2.hash_password_raw()`, and `argon2.verify_password()` that have been soft-deprecated since 2016 are now hard-deprecated.
They now raise `DeprecationWarning`s and will be removed in 2024.


### Added

Expand Down
2 changes: 1 addition & 1 deletion docs/api.rst
Original file line number Diff line number Diff line change
Expand Up @@ -210,7 +210,7 @@ These APIs are from the first release of *argon2-cffi* and proved to live in an
On one hand they have defaults and check parameters but on the other hand they only consume byte strings.

Therefore the decision has been made to replace them by a high-level (:class:`argon2.PasswordHasher`) and a low-level (:mod:`argon2.low_level`) solution.
There are no immediate plans to remove them though.
They will be removed in 2024.

.. autofunction:: argon2.hash_password
.. autofunction:: argon2.hash_password_raw
Expand Down
13 changes: 13 additions & 0 deletions src/argon2/_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
from __future__ import annotations

import os
import warnings

from ._password_hasher import (
DEFAULT_HASH_LENGTH,
Expand All @@ -19,6 +20,9 @@
from .low_level import Type, hash_secret, hash_secret_raw, verify_secret


_INSTEAD = " is deprecated, use argon2.PasswordHasher instead"


def hash_password(
password: bytes,
salt: bytes | None = None,
Expand All @@ -35,6 +39,9 @@ def hash_password(
.. deprecated:: 16.0.0
Use :class:`argon2.PasswordHasher` for passwords.
"""
warnings.warn(
"argon2.hash_password" + _INSTEAD, DeprecationWarning, stacklevel=2
)
if salt is None:
salt = os.urandom(DEFAULT_RANDOM_SALT_LENGTH)
return hash_secret(
Expand All @@ -58,6 +65,9 @@ def hash_password_raw(
.. deprecated:: 16.0.0
Use :class:`argon2.PasswordHasher` for passwords.
"""
warnings.warn(
"argon2.hash_password_raw" + _INSTEAD, DeprecationWarning, stacklevel=2
)
if salt is None:
salt = os.urandom(DEFAULT_RANDOM_SALT_LENGTH)
return hash_secret_raw(
Expand All @@ -75,4 +85,7 @@ def verify_password(
.. deprecated:: 16.0.0
Use :class:`argon2.PasswordHasher` for passwords.
"""
warnings.warn(
"argon2.verify_password" + _INSTEAD, DeprecationWarning, stacklevel=2
)
return verify_secret(hash, password, type)
113 changes: 77 additions & 36 deletions tests/test_legacy.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,28 +32,41 @@ def test_hash_defaults(self):
"""
Calling without arguments works.
"""
hash_password(b"secret")
with pytest.deprecated_call(
match="argon2.hash_password is deprecated"
) as dc:
hash_password(b"secret")

assert dc.pop().filename.endswith("test_legacy.py")

def test_raw_defaults(self):
"""
Calling without arguments works.
"""
hash_password_raw(b"secret")
with pytest.deprecated_call(
match="argon2.hash_password_raw is deprecated"
) as dc:
hash_password_raw(b"secret")

assert dc.pop().filename.endswith("test_legacy.py")

@i_and_d_encoded
def test_hash_password(self, type, hash):
"""
Creates the same encoded hash as the Argon2 CLI client.
"""
rv = hash_password(
TEST_PASSWORD,
TEST_SALT,
TEST_TIME,
TEST_MEMORY,
TEST_PARALLELISM,
TEST_HASH_LEN,
type,
)
with pytest.deprecated_call(
match="argon2.hash_password is deprecated"
):
rv = hash_password(
TEST_PASSWORD,
TEST_SALT,
TEST_TIME,
TEST_MEMORY,
TEST_PARALLELISM,
TEST_HASH_LEN,
type,
)

assert hash == rv
assert isinstance(rv, bytes)
Expand All @@ -63,15 +76,18 @@ def test_hash_password_raw(self, type, hash):
"""
Creates the same raw hash as the Argon2 CLI client.
"""
rv = hash_password_raw(
TEST_PASSWORD,
TEST_SALT,
TEST_TIME,
TEST_MEMORY,
TEST_PARALLELISM,
TEST_HASH_LEN,
type,
)
with pytest.deprecated_call(
match="argon2.hash_password_raw is deprecated"
):
rv = hash_password_raw(
TEST_PASSWORD,
TEST_SALT,
TEST_TIME,
TEST_MEMORY,
TEST_PARALLELISM,
TEST_HASH_LEN,
type,
)

assert hash == rv
assert isinstance(rv, bytes)
Expand All @@ -80,15 +96,24 @@ def test_hash_nul_bytes(self):
"""
Hashing passwords with NUL bytes works as expected.
"""
rv = hash_password_raw(b"abc\x00", TEST_SALT)
with pytest.deprecated_call(
match="argon2.hash_password_raw is deprecated"
):
rv = hash_password_raw(b"abc\x00", TEST_SALT)

assert rv != hash_password_raw(b"abc", TEST_SALT)
with pytest.deprecated_call(
match="argon2.hash_password_raw is deprecated"
):
assert rv != hash_password_raw(b"abc", TEST_SALT)

def test_random_salt(self):
"""
Omitting a salt, creates a random one.
"""
rv = hash_password(b"secret")
with pytest.deprecated_call(
match="argon2.hash_password is deprecated"
):
rv = hash_password(b"secret")
salt = rv.split(b",")[-1].split(b"$")[1]

assert (
Expand All @@ -101,29 +126,36 @@ def test_hash_wrong_arg_type(self):
"""
Passing an argument of wrong type raises TypeError.
"""
with pytest.raises(TypeError):
with pytest.deprecated_call(
match="argon2.hash_password is deprecated"
), pytest.raises(TypeError):
hash_password("oh no, unicode!")

def test_illegal_argon2_parameter(self):
"""
Raises HashingError if hashing fails.
"""
with pytest.raises(HashingError):
with pytest.deprecated_call(
match="argon2.hash_password is deprecated"
), pytest.raises(HashingError):
hash_password(TEST_PASSWORD, memory_cost=1)

@given(st.binary(max_size=128))
def test_hash_fast(self, password):
"""
Hash various passwords as cheaply as possible.
"""
hash_password(
password,
salt=b"12345678",
time_cost=1,
memory_cost=8,
parallelism=1,
hash_len=8,
)
with pytest.deprecated_call(
match="argon2.hash_password is deprecated"
):
hash_password(
password,
salt=b"12345678",
time_cost=1,
memory_cost=8,
parallelism=1,
hash_len=8,
)


class TestVerify:
Expand All @@ -132,18 +164,27 @@ def test_success(self, type, hash):
"""
Given a valid hash and password and correct type, we succeed.
"""
assert True is verify_password(hash, TEST_PASSWORD, type)
with pytest.deprecated_call(
match="argon2.verify_password is deprecated"
) as dc:
assert True is verify_password(hash, TEST_PASSWORD, type)

assert dc.pop().filename.endswith("test_legacy.py")

def test_fail_wrong_argon2_type(self):
"""
Given a valid hash and password and wrong type, we fail.
"""
with pytest.raises(VerificationError):
with pytest.deprecated_call(
match="argon2.verify_password is deprecated"
), pytest.raises(VerificationError):
verify_password(TEST_HASH_I, TEST_PASSWORD, Type.D)

def test_wrong_arg_type(self):
"""
Passing an argument of wrong type raises TypeError.
"""
with pytest.raises(TypeError):
with pytest.deprecated_call(
match="argon2.verify_password is deprecated"
), pytest.raises(TypeError):
verify_password(TEST_HASH_I, TEST_PASSWORD.decode("ascii"))

0 comments on commit 456e175

Please sign in to comment.