mirror of
https://github.com/django/django.git
synced 2025-08-26 11:49:12 +00:00
Fixed #36546 -- Deprecated django.utils.crypto.constant_time_compare() in favor of hmac.compare_digest().
Signed-off-by: SaJH <wogur981208@gmail.com>
This commit is contained in:
parent
3ba24c18e7
commit
0246f47888
@ -1,3 +1,4 @@
|
|||||||
|
import hmac
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
@ -6,7 +7,6 @@ from django.apps import apps as django_apps
|
|||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
|
||||||
from django.middleware.csrf import rotate_token
|
from django.middleware.csrf import rotate_token
|
||||||
from django.utils.crypto import constant_time_compare
|
|
||||||
from django.utils.deprecation import RemovedInDjango61Warning
|
from django.utils.deprecation import RemovedInDjango61Warning
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from django.views.decorators.debug import sensitive_variables
|
from django.views.decorators.debug import sensitive_variables
|
||||||
@ -175,7 +175,7 @@ def login(request, user, backend=None):
|
|||||||
if SESSION_KEY in request.session:
|
if SESSION_KEY in request.session:
|
||||||
if _get_user_session_key(request) != user.pk or (
|
if _get_user_session_key(request) != user.pk or (
|
||||||
session_auth_hash
|
session_auth_hash
|
||||||
and not constant_time_compare(
|
and not hmac.compare_digest(
|
||||||
request.session.get(HASH_SESSION_KEY, ""), session_auth_hash
|
request.session.get(HASH_SESSION_KEY, ""), session_auth_hash
|
||||||
)
|
)
|
||||||
):
|
):
|
||||||
@ -217,7 +217,7 @@ async def alogin(request, user, backend=None):
|
|||||||
if await request.session.ahas_key(SESSION_KEY):
|
if await request.session.ahas_key(SESSION_KEY):
|
||||||
if await _aget_user_session_key(request) != user.pk or (
|
if await _aget_user_session_key(request) != user.pk or (
|
||||||
session_auth_hash
|
session_auth_hash
|
||||||
and not constant_time_compare(
|
and not hmac.compare_digest(
|
||||||
await request.session.aget(HASH_SESSION_KEY, ""),
|
await request.session.aget(HASH_SESSION_KEY, ""),
|
||||||
session_auth_hash,
|
session_auth_hash,
|
||||||
)
|
)
|
||||||
@ -323,7 +323,7 @@ def get_user(request):
|
|||||||
session_hash_verified = False
|
session_hash_verified = False
|
||||||
else:
|
else:
|
||||||
session_auth_hash = user.get_session_auth_hash()
|
session_auth_hash = user.get_session_auth_hash()
|
||||||
session_hash_verified = constant_time_compare(
|
session_hash_verified = hmac.compare_digest(
|
||||||
session_hash, session_auth_hash
|
session_hash, session_auth_hash
|
||||||
)
|
)
|
||||||
if not session_hash_verified:
|
if not session_hash_verified:
|
||||||
@ -331,7 +331,7 @@ def get_user(request):
|
|||||||
# with the fallback secrets and stop when a matching one is
|
# with the fallback secrets and stop when a matching one is
|
||||||
# found.
|
# found.
|
||||||
if session_hash and any(
|
if session_hash and any(
|
||||||
constant_time_compare(session_hash, fallback_auth_hash)
|
hmac.compare_digest(session_hash, fallback_auth_hash)
|
||||||
for fallback_auth_hash in user.get_session_auth_fallback_hash()
|
for fallback_auth_hash in user.get_session_auth_fallback_hash()
|
||||||
):
|
):
|
||||||
request.session.cycle_key()
|
request.session.cycle_key()
|
||||||
@ -364,7 +364,7 @@ async def aget_user(request):
|
|||||||
session_hash_verified = False
|
session_hash_verified = False
|
||||||
else:
|
else:
|
||||||
session_auth_hash = user.get_session_auth_hash()
|
session_auth_hash = user.get_session_auth_hash()
|
||||||
session_hash_verified = constant_time_compare(
|
session_hash_verified = hmac.compare_digest(
|
||||||
session_hash, session_auth_hash
|
session_hash, session_auth_hash
|
||||||
)
|
)
|
||||||
if not session_hash_verified:
|
if not session_hash_verified:
|
||||||
@ -372,7 +372,7 @@ async def aget_user(request):
|
|||||||
# with the fallback secrets and stop when a matching one is
|
# with the fallback secrets and stop when a matching one is
|
||||||
# found.
|
# found.
|
||||||
if session_hash and any(
|
if session_hash and any(
|
||||||
constant_time_compare(session_hash, fallback_auth_hash)
|
hmac.compare_digest(session_hash, fallback_auth_hash)
|
||||||
for fallback_auth_hash in user.get_session_auth_fallback_hash()
|
for fallback_auth_hash in user.get_session_auth_fallback_hash()
|
||||||
):
|
):
|
||||||
await request.session.acycle_key()
|
await request.session.acycle_key()
|
||||||
|
@ -2,6 +2,7 @@ import base64
|
|||||||
import binascii
|
import binascii
|
||||||
import functools
|
import functools
|
||||||
import hashlib
|
import hashlib
|
||||||
|
import hmac
|
||||||
import importlib
|
import importlib
|
||||||
import math
|
import math
|
||||||
import warnings
|
import warnings
|
||||||
@ -12,12 +13,7 @@ from django.conf import settings
|
|||||||
from django.core.exceptions import ImproperlyConfigured
|
from django.core.exceptions import ImproperlyConfigured
|
||||||
from django.core.signals import setting_changed
|
from django.core.signals import setting_changed
|
||||||
from django.dispatch import receiver
|
from django.dispatch import receiver
|
||||||
from django.utils.crypto import (
|
from django.utils.crypto import RANDOM_STRING_CHARS, get_random_string, pbkdf2
|
||||||
RANDOM_STRING_CHARS,
|
|
||||||
constant_time_compare,
|
|
||||||
get_random_string,
|
|
||||||
pbkdf2,
|
|
||||||
)
|
|
||||||
from django.utils.encoding import force_bytes, force_str
|
from django.utils.encoding import force_bytes, force_str
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.translation import gettext_noop as _
|
from django.utils.translation import gettext_noop as _
|
||||||
@ -349,7 +345,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
|
|||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
encoded_2 = self.encode(password, decoded["salt"], decoded["iterations"])
|
encoded_2 = self.encode(password, decoded["salt"], decoded["iterations"])
|
||||||
return constant_time_compare(encoded, encoded_2)
|
return hmac.compare_digest(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
@ -533,7 +529,7 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
|
|||||||
algorithm, data = encoded.split("$", 1)
|
algorithm, data = encoded.split("$", 1)
|
||||||
assert algorithm == self.algorithm
|
assert algorithm == self.algorithm
|
||||||
encoded_2 = self.encode(password, data.encode("ascii"))
|
encoded_2 = self.encode(password, data.encode("ascii"))
|
||||||
return constant_time_compare(encoded, encoded_2)
|
return hmac.compare_digest(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
@ -628,7 +624,7 @@ class ScryptPasswordHasher(BasePasswordHasher):
|
|||||||
decoded["block_size"],
|
decoded["block_size"],
|
||||||
decoded["parallelism"],
|
decoded["parallelism"],
|
||||||
)
|
)
|
||||||
return constant_time_compare(encoded, encoded_2)
|
return hmac.compare_digest(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
@ -681,7 +677,7 @@ class MD5PasswordHasher(BasePasswordHasher):
|
|||||||
def verify(self, password, encoded):
|
def verify(self, password, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
encoded_2 = self.encode(password, decoded["salt"])
|
encoded_2 = self.encode(password, decoded["salt"])
|
||||||
return constant_time_compare(encoded, encoded_2)
|
return hmac.compare_digest(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
|
@ -1,7 +1,8 @@
|
|||||||
|
import hmac
|
||||||
from datetime import datetime
|
from datetime import datetime
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.crypto import constant_time_compare, salted_hmac
|
from django.utils.crypto import salted_hmac
|
||||||
from django.utils.http import base36_to_int, int_to_base36
|
from django.utils.http import base36_to_int, int_to_base36
|
||||||
|
|
||||||
|
|
||||||
@ -67,7 +68,7 @@ class PasswordResetTokenGenerator:
|
|||||||
|
|
||||||
# Check that the timestamp/uid has not been tampered with
|
# Check that the timestamp/uid has not been tampered with
|
||||||
for secret in [self.secret, *self.secret_fallbacks]:
|
for secret in [self.secret, *self.secret_fallbacks]:
|
||||||
if constant_time_compare(
|
if hmac.compare_digest(
|
||||||
self._make_token_with_timestamp(user, ts, secret),
|
self._make_token_with_timestamp(user, ts, secret),
|
||||||
token,
|
token,
|
||||||
):
|
):
|
||||||
|
@ -36,12 +36,13 @@ These functions make use of all of them.
|
|||||||
|
|
||||||
import base64
|
import base64
|
||||||
import datetime
|
import datetime
|
||||||
|
import hmac
|
||||||
import json
|
import json
|
||||||
import time
|
import time
|
||||||
import zlib
|
import zlib
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
from django.utils.crypto import constant_time_compare, salted_hmac
|
from django.utils.crypto import salted_hmac
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
from django.utils.module_loading import import_string
|
from django.utils.module_loading import import_string
|
||||||
from django.utils.regex_helper import _lazy_re_compile
|
from django.utils.regex_helper import _lazy_re_compile
|
||||||
@ -209,7 +210,7 @@ class Signer:
|
|||||||
raise BadSignature('No "%s" found in value' % self.sep)
|
raise BadSignature('No "%s" found in value' % self.sep)
|
||||||
value, sig = signed_value.rsplit(self.sep, 1)
|
value, sig = signed_value.rsplit(self.sep, 1)
|
||||||
for key in [self.key, *self.fallback_keys]:
|
for key in [self.key, *self.fallback_keys]:
|
||||||
if constant_time_compare(sig, self.signature(value, key)):
|
if hmac.compare_digest(sig, self.signature(value, key)):
|
||||||
return value
|
return value
|
||||||
raise BadSignature('Signature "%s" does not match' % sig)
|
raise BadSignature('Signature "%s" does not match' % sig)
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@ This module provides a middleware that implements protection
|
|||||||
against request forgeries from other sites.
|
against request forgeries from other sites.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
|
import hmac
|
||||||
import logging
|
import logging
|
||||||
import string
|
import string
|
||||||
from collections import defaultdict
|
from collections import defaultdict
|
||||||
@ -15,7 +16,7 @@ from django.core.exceptions import DisallowedHost, ImproperlyConfigured
|
|||||||
from django.http import HttpHeaders, UnreadablePostError
|
from django.http import HttpHeaders, UnreadablePostError
|
||||||
from django.urls import get_callable
|
from django.urls import get_callable
|
||||||
from django.utils.cache import patch_vary_headers
|
from django.utils.cache import patch_vary_headers
|
||||||
from django.utils.crypto import constant_time_compare, get_random_string
|
from django.utils.crypto import get_random_string
|
||||||
from django.utils.deprecation import MiddlewareMixin
|
from django.utils.deprecation import MiddlewareMixin
|
||||||
from django.utils.functional import cached_property
|
from django.utils.functional import cached_property
|
||||||
from django.utils.http import is_same_domain
|
from django.utils.http import is_same_domain
|
||||||
@ -154,7 +155,7 @@ def _does_token_match(request_csrf_token, csrf_secret):
|
|||||||
if len(request_csrf_token) == CSRF_TOKEN_LENGTH:
|
if len(request_csrf_token) == CSRF_TOKEN_LENGTH:
|
||||||
request_csrf_token = _unmask_cipher_token(request_csrf_token)
|
request_csrf_token = _unmask_cipher_token(request_csrf_token)
|
||||||
assert len(request_csrf_token) == CSRF_SECRET_LENGTH
|
assert len(request_csrf_token) == CSRF_SECRET_LENGTH
|
||||||
return constant_time_compare(request_csrf_token, csrf_secret)
|
return hmac.compare_digest(request_csrf_token, csrf_secret)
|
||||||
|
|
||||||
|
|
||||||
class RejectRequest(Exception):
|
class RejectRequest(Exception):
|
||||||
|
@ -5,8 +5,10 @@ Django's standard crypto functions and utilities.
|
|||||||
import hashlib
|
import hashlib
|
||||||
import hmac
|
import hmac
|
||||||
import secrets
|
import secrets
|
||||||
|
import warnings
|
||||||
|
|
||||||
from django.conf import settings
|
from django.conf import settings
|
||||||
|
from django.utils.deprecation import RemovedInDjango70Warning
|
||||||
from django.utils.encoding import force_bytes
|
from django.utils.encoding import force_bytes
|
||||||
|
|
||||||
|
|
||||||
@ -64,7 +66,12 @@ def get_random_string(length, allowed_chars=RANDOM_STRING_CHARS):
|
|||||||
|
|
||||||
def constant_time_compare(val1, val2):
|
def constant_time_compare(val1, val2):
|
||||||
"""Return True if the two strings are equal, False otherwise."""
|
"""Return True if the two strings are equal, False otherwise."""
|
||||||
return secrets.compare_digest(force_bytes(val1), force_bytes(val2))
|
warnings.warn(
|
||||||
|
"constant_time_compare() is deprecated. Use hmac.compare_digest() instead.",
|
||||||
|
RemovedInDjango70Warning,
|
||||||
|
stacklevel=2,
|
||||||
|
)
|
||||||
|
return hmac.compare_digest(val1, val2)
|
||||||
|
|
||||||
|
|
||||||
def pbkdf2(password, salt, iterations, dklen=0, digest=None):
|
def pbkdf2(password, salt, iterations, dklen=0, digest=None):
|
||||||
|
@ -53,6 +53,8 @@ details on these changes.
|
|||||||
* The ``django.core.mail.forbid_multi_line_headers()`` and
|
* The ``django.core.mail.forbid_multi_line_headers()`` and
|
||||||
``django.core.mail.message.sanitize_address()`` functions will be removed.
|
``django.core.mail.message.sanitize_address()`` functions will be removed.
|
||||||
|
|
||||||
|
* The ``django.utils.crypto.constant_time_compare()`` function will be removed.
|
||||||
|
|
||||||
.. _deprecation-removed-in-6.1:
|
.. _deprecation-removed-in-6.1:
|
||||||
|
|
||||||
6.1
|
6.1
|
||||||
|
@ -570,6 +570,9 @@ Miscellaneous
|
|||||||
* The undocumented ``django.core.mail.forbid_multi_line_headers()`` and
|
* The undocumented ``django.core.mail.forbid_multi_line_headers()`` and
|
||||||
``django.core.mail.message.sanitize_address()`` functions are deprecated.
|
``django.core.mail.message.sanitize_address()`` functions are deprecated.
|
||||||
|
|
||||||
|
* The ``django.utils.crypto.constant_time_compare()`` function is deprecated
|
||||||
|
because it is merely an alias of :py:func:`hmac.compare_digest`.
|
||||||
|
|
||||||
Features removed in 6.0
|
Features removed in 6.0
|
||||||
=======================
|
=======================
|
||||||
|
|
||||||
|
@ -2,15 +2,19 @@ import hashlib
|
|||||||
import unittest
|
import unittest
|
||||||
|
|
||||||
from django.test import SimpleTestCase
|
from django.test import SimpleTestCase
|
||||||
|
from django.test.utils import ignore_warnings
|
||||||
from django.utils.crypto import (
|
from django.utils.crypto import (
|
||||||
InvalidAlgorithm,
|
InvalidAlgorithm,
|
||||||
constant_time_compare,
|
constant_time_compare,
|
||||||
pbkdf2,
|
pbkdf2,
|
||||||
salted_hmac,
|
salted_hmac,
|
||||||
)
|
)
|
||||||
|
from django.utils.deprecation import RemovedInDjango70Warning
|
||||||
|
|
||||||
|
|
||||||
class TestUtilsCryptoMisc(SimpleTestCase):
|
class TestUtilsCryptoMisc(SimpleTestCase):
|
||||||
|
# RemovedInDjango70Warning.
|
||||||
|
@ignore_warnings(category=RemovedInDjango70Warning)
|
||||||
def test_constant_time_compare(self):
|
def test_constant_time_compare(self):
|
||||||
# It's hard to test for constant time, just test the result.
|
# It's hard to test for constant time, just test the result.
|
||||||
self.assertTrue(constant_time_compare(b"spam", b"spam"))
|
self.assertTrue(constant_time_compare(b"spam", b"spam"))
|
||||||
@ -18,6 +22,15 @@ class TestUtilsCryptoMisc(SimpleTestCase):
|
|||||||
self.assertTrue(constant_time_compare("spam", "spam"))
|
self.assertTrue(constant_time_compare("spam", "spam"))
|
||||||
self.assertFalse(constant_time_compare("spam", "eggs"))
|
self.assertFalse(constant_time_compare("spam", "eggs"))
|
||||||
|
|
||||||
|
def test_constant_time_compare_deprecated(self):
|
||||||
|
msg = (
|
||||||
|
"constant_time_compare() is deprecated. "
|
||||||
|
"Use hmac.compare_digest() instead."
|
||||||
|
)
|
||||||
|
with self.assertWarnsMessage(RemovedInDjango70Warning, msg) as ctx:
|
||||||
|
constant_time_compare(b"spam", b"spam")
|
||||||
|
self.assertEqual(ctx.filename, __file__)
|
||||||
|
|
||||||
def test_salted_hmac(self):
|
def test_salted_hmac(self):
|
||||||
tests = [
|
tests = [
|
||||||
((b"salt", b"value"), {}, "b51a2e619c43b1ca4f91d15c57455521d71d61eb"),
|
((b"salt", b"value"), {}, "b51a2e619c43b1ca4f91d15c57455521d71d61eb"),
|
||||||
|
Loading…
x
Reference in New Issue
Block a user