mirror of
https://github.com/django/django.git
synced 2025-09-10 11:09:12 +00:00
Fixed #36572 -- Revert "Fixed #36546 -- Deprecated django.utils.crypto.constant_time_compare() in favor of hmac.compare_digest()."
This reverts commit 0246f478882c26bc1fe293224653074cd46a90d0.
This commit is contained in:
parent
c594574175
commit
d0e4dd5cdd
@ -1,4 +1,3 @@
|
|||||||
import hmac
|
|
||||||
import inspect
|
import inspect
|
||||||
import re
|
import re
|
||||||
import warnings
|
import warnings
|
||||||
@ -7,6 +6,7 @@ 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 hmac.compare_digest(
|
and not constant_time_compare(
|
||||||
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 hmac.compare_digest(
|
and not constant_time_compare(
|
||||||
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 = hmac.compare_digest(
|
session_hash_verified = constant_time_compare(
|
||||||
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(
|
||||||
hmac.compare_digest(session_hash, fallback_auth_hash)
|
constant_time_compare(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 = hmac.compare_digest(
|
session_hash_verified = constant_time_compare(
|
||||||
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(
|
||||||
hmac.compare_digest(session_hash, fallback_auth_hash)
|
constant_time_compare(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,7 +2,6 @@ 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
|
||||||
@ -13,7 +12,12 @@ 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 RANDOM_STRING_CHARS, get_random_string, pbkdf2
|
from django.utils.crypto import (
|
||||||
|
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 _
|
||||||
@ -345,7 +349,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 hmac.compare_digest(encoded, encoded_2)
|
return constant_time_compare(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
@ -529,7 +533,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 hmac.compare_digest(encoded, encoded_2)
|
return constant_time_compare(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
@ -624,7 +628,7 @@ class ScryptPasswordHasher(BasePasswordHasher):
|
|||||||
decoded["block_size"],
|
decoded["block_size"],
|
||||||
decoded["parallelism"],
|
decoded["parallelism"],
|
||||||
)
|
)
|
||||||
return hmac.compare_digest(encoded, encoded_2)
|
return constant_time_compare(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
@ -677,7 +681,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 hmac.compare_digest(encoded, encoded_2)
|
return constant_time_compare(encoded, encoded_2)
|
||||||
|
|
||||||
def safe_summary(self, encoded):
|
def safe_summary(self, encoded):
|
||||||
decoded = self.decode(encoded)
|
decoded = self.decode(encoded)
|
||||||
|
@ -1,8 +1,7 @@
|
|||||||
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 salted_hmac
|
from django.utils.crypto import constant_time_compare, salted_hmac
|
||||||
from django.utils.http import base36_to_int, int_to_base36
|
from django.utils.http import base36_to_int, int_to_base36
|
||||||
|
|
||||||
|
|
||||||
@ -68,7 +67,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 hmac.compare_digest(
|
if constant_time_compare(
|
||||||
self._make_token_with_timestamp(user, ts, secret),
|
self._make_token_with_timestamp(user, ts, secret),
|
||||||
token,
|
token,
|
||||||
):
|
):
|
||||||
|
@ -36,13 +36,12 @@ 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 salted_hmac
|
from django.utils.crypto import constant_time_compare, 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
|
||||||
@ -210,7 +209,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 hmac.compare_digest(sig, self.signature(value, key)):
|
if constant_time_compare(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,7 +5,6 @@ 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
|
||||||
@ -16,7 +15,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 get_random_string
|
from django.utils.crypto import constant_time_compare, 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
|
||||||
@ -155,7 +154,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 hmac.compare_digest(request_csrf_token, csrf_secret)
|
return constant_time_compare(request_csrf_token, csrf_secret)
|
||||||
|
|
||||||
|
|
||||||
class RejectRequest(Exception):
|
class RejectRequest(Exception):
|
||||||
|
@ -5,10 +5,8 @@ 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
|
||||||
|
|
||||||
|
|
||||||
@ -66,12 +64,7 @@ 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."""
|
||||||
warnings.warn(
|
return secrets.compare_digest(force_bytes(val1), force_bytes(val2))
|
||||||
"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,8 +53,6 @@ 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,9 +570,6 @@ 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,34 +2,25 @@ 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"))
|
||||||
self.assertFalse(constant_time_compare(b"spam", b"eggs"))
|
self.assertFalse(constant_time_compare(b"spam", b"eggs"))
|
||||||
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"))
|
||||||
|
self.assertTrue(constant_time_compare(b"spam", "spam"))
|
||||||
def test_constant_time_compare_deprecated(self):
|
self.assertFalse(constant_time_compare("spam", b"eggs"))
|
||||||
msg = (
|
self.assertTrue(constant_time_compare("ありがとう", "ありがとう"))
|
||||||
"constant_time_compare() is deprecated. "
|
self.assertFalse(constant_time_compare("ありがとう", "おはよう"))
|
||||||
"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 = [
|
||||||
|
Loading…
x
Reference in New Issue
Block a user