-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: add SHA256 password hashers (#14)
* refactor: reorganize tests * feat: add sha256 hasher * style: fix ruff config * 1.0.0
- Loading branch information
1 parent
2a74a1c
commit 4ee912c
Showing
5 changed files
with
108 additions
and
7 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,48 @@ | ||
import hashlib | ||
|
||
from django.contrib.auth.hashers import BasePasswordHasher, mask_hash, must_update_salt | ||
from django.utils.crypto import constant_time_compare | ||
from django.utils.translation import gettext_noop as _ | ||
|
||
|
||
class SHA256PasswordHasher(BasePasswordHasher): | ||
""" | ||
This was based on the Django's SHA1PasswordHasher from Django 5.0.6: | ||
https://github.com/django/django/blob/5.0.6/django/contrib/auth/hashers.py#L645 | ||
""" | ||
|
||
algorithm = "sha256" | ||
|
||
def encode(self, password, salt): | ||
self._check_encode_args(password, salt) | ||
hash = hashlib.sha256((salt + password).encode()).hexdigest() | ||
return "%s$%s$%s" % (self.algorithm, salt, hash) | ||
|
||
def decode(self, encoded): | ||
algorithm, salt, hash = encoded.split("$", 2) | ||
assert algorithm == self.algorithm | ||
return { | ||
"algorithm": algorithm, | ||
"hash": hash, | ||
"salt": salt, | ||
} | ||
|
||
def verify(self, password, encoded): | ||
decoded = self.decode(encoded) | ||
encoded_2 = self.encode(password, decoded["salt"]) | ||
return constant_time_compare(encoded, encoded_2) | ||
|
||
def safe_summary(self, encoded): | ||
decoded = self.decode(encoded) | ||
return { | ||
_("algorithm"): decoded["algorithm"], | ||
_("salt"): mask_hash(decoded["salt"], show=2), | ||
_("hash"): mask_hash(decoded["hash"]), | ||
} | ||
|
||
def must_update(self, encoded): | ||
decoded = self.decode(encoded) | ||
return must_update_salt(decoded["salt"], self.salt_entropy) | ||
|
||
def harden_runtime(self, password, encoded): | ||
pass |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,49 @@ | ||
from django.contrib.auth.hashers import ( | ||
check_password, | ||
identify_hasher, | ||
is_password_usable, | ||
make_password, | ||
) | ||
from django.test.utils import override_settings | ||
|
||
from ninja_apikey.hashers import SHA256PasswordHasher | ||
|
||
|
||
@override_settings(PASSWORD_HASHERS=["ninja_apikey.hashers.SHA256PasswordHasher"]) | ||
def test_sha256(): | ||
""" | ||
Based on https://github.com/django/django/blob/5.0.6/tests/auth_tests/test_hashers.py#L110 | ||
""" | ||
encoded = make_password("lètmein", "seasalt", hasher="sha256") | ||
assert ( | ||
encoded | ||
== "sha256$seasalt$e0327e0c88846ec7f85601380e86c72a5242e3455a1ae0f736f349858f126eb9" | ||
) | ||
assert is_password_usable(encoded) is True | ||
assert check_password("lètmein", encoded) is True | ||
assert check_password("lètmeinz", encoded) is False | ||
assert identify_hasher(encoded).algorithm == "sha256" | ||
|
||
|
||
@override_settings(PASSWORD_HASHERS=["ninja_apikey.hashers.SHA256PasswordHasher"]) | ||
def test_blank_password(): | ||
""" | ||
Based on https://github.com/django/django/blob/5.0.6/tests/auth_tests/test_hashers.py#L110 | ||
""" | ||
blank_encoded = make_password("", "seasalt", "sha256") | ||
assert blank_encoded.startswith("sha256$") | ||
assert is_password_usable(blank_encoded) is True | ||
assert check_password("", blank_encoded) is True | ||
assert check_password(" ", blank_encoded) is False | ||
|
||
|
||
@override_settings(PASSWORD_HASHERS=["ninja_apikey.hashers.SHA256PasswordHasher"]) | ||
def test_entropy_check(): | ||
""" | ||
Based on https://github.com/django/django/blob/5.0.6/tests/auth_tests/test_hashers.py#L110 | ||
""" | ||
hasher = SHA256PasswordHasher() | ||
encoded_weak_salt = make_password("lètmein", "iodizedsalt") | ||
encoded_strong_salt = make_password("lètmein", hasher.salt()) | ||
assert hasher.must_update(encoded_weak_salt) is True | ||
assert hasher.must_update(encoded_strong_salt) is False |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -3,10 +3,11 @@ requires = ["flit_core >=3.2,<4"] | |
build-backend = "flit_core.buildapi" | ||
|
||
|
||
|
||
[project] | ||
name = "ninja-api-key" | ||
description = "Django Ninja API Key Authentication" | ||
version = "0.2.2" | ||
version = "1.0.0" | ||
authors = [ | ||
{name = "Lucas Rangel Cezimbra", email="[email protected]"}, | ||
{name = "Maximilian Wassink", email="[email protected]"}, | ||
|
@@ -55,11 +56,14 @@ test = [ | |
] | ||
|
||
|
||
|
||
[tool.flit.module] | ||
name = "ninja_apikey" | ||
|
||
|
||
[tool.ruff] | ||
line-length = 88 | ||
|
||
[tool.ruff.lint] | ||
select = ["E", "F", "I"] | ||
ignore = ["E501"] | ||
line-length = 88 |