1
0
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:
Sarah Boyce 2025-08-26 13:37:34 +02:00
parent c594574175
commit d0e4dd5cdd
9 changed files with 28 additions and 48 deletions

View File

@ -1,4 +1,3 @@
import hmac
import inspect
import re
import warnings
@ -7,6 +6,7 @@ from django.apps import apps as django_apps
from django.conf import settings
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
from django.middleware.csrf import rotate_token
from django.utils.crypto import constant_time_compare
from django.utils.deprecation import RemovedInDjango61Warning
from django.utils.module_loading import import_string
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 _get_user_session_key(request) != user.pk or (
session_auth_hash
and not hmac.compare_digest(
and not constant_time_compare(
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 _aget_user_session_key(request) != user.pk or (
session_auth_hash
and not hmac.compare_digest(
and not constant_time_compare(
await request.session.aget(HASH_SESSION_KEY, ""),
session_auth_hash,
)
@ -323,7 +323,7 @@ def get_user(request):
session_hash_verified = False
else:
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
)
if not session_hash_verified:
@ -331,7 +331,7 @@ def get_user(request):
# with the fallback secrets and stop when a matching one is
# found.
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()
):
request.session.cycle_key()
@ -364,7 +364,7 @@ async def aget_user(request):
session_hash_verified = False
else:
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
)
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
# found.
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()
):
await request.session.acycle_key()

View File

@ -2,7 +2,6 @@ import base64
import binascii
import functools
import hashlib
import hmac
import importlib
import math
import warnings
@ -13,7 +12,12 @@ from django.conf import settings
from django.core.exceptions import ImproperlyConfigured
from django.core.signals import setting_changed
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.module_loading import import_string
from django.utils.translation import gettext_noop as _
@ -345,7 +349,7 @@ class PBKDF2PasswordHasher(BasePasswordHasher):
def verify(self, password, encoded):
decoded = self.decode(encoded)
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):
decoded = self.decode(encoded)
@ -529,7 +533,7 @@ class BCryptSHA256PasswordHasher(BasePasswordHasher):
algorithm, data = encoded.split("$", 1)
assert algorithm == self.algorithm
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):
decoded = self.decode(encoded)
@ -624,7 +628,7 @@ class ScryptPasswordHasher(BasePasswordHasher):
decoded["block_size"],
decoded["parallelism"],
)
return hmac.compare_digest(encoded, encoded_2)
return constant_time_compare(encoded, encoded_2)
def safe_summary(self, encoded):
decoded = self.decode(encoded)
@ -677,7 +681,7 @@ class MD5PasswordHasher(BasePasswordHasher):
def verify(self, password, encoded):
decoded = self.decode(encoded)
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):
decoded = self.decode(encoded)

View File

@ -1,8 +1,7 @@
import hmac
from datetime import datetime
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
@ -68,7 +67,7 @@ class PasswordResetTokenGenerator:
# Check that the timestamp/uid has not been tampered with
for secret in [self.secret, *self.secret_fallbacks]:
if hmac.compare_digest(
if constant_time_compare(
self._make_token_with_timestamp(user, ts, secret),
token,
):

View File

@ -36,13 +36,12 @@ These functions make use of all of them.
import base64
import datetime
import hmac
import json
import time
import zlib
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.module_loading import import_string
from django.utils.regex_helper import _lazy_re_compile
@ -210,7 +209,7 @@ class Signer:
raise BadSignature('No "%s" found in value' % self.sep)
value, sig = signed_value.rsplit(self.sep, 1)
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
raise BadSignature('Signature "%s" does not match' % sig)

View File

@ -5,7 +5,6 @@ This module provides a middleware that implements protection
against request forgeries from other sites.
"""
import hmac
import logging
import string
from collections import defaultdict
@ -16,7 +15,7 @@ from django.core.exceptions import DisallowedHost, ImproperlyConfigured
from django.http import HttpHeaders, UnreadablePostError
from django.urls import get_callable
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.functional import cached_property
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:
request_csrf_token = _unmask_cipher_token(request_csrf_token)
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):

View File

@ -5,10 +5,8 @@ Django's standard crypto functions and utilities.
import hashlib
import hmac
import secrets
import warnings
from django.conf import settings
from django.utils.deprecation import RemovedInDjango70Warning
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):
"""Return True if the two strings are equal, False otherwise."""
warnings.warn(
"constant_time_compare() is deprecated. Use hmac.compare_digest() instead.",
RemovedInDjango70Warning,
stacklevel=2,
)
return hmac.compare_digest(val1, val2)
return secrets.compare_digest(force_bytes(val1), force_bytes(val2))
def pbkdf2(password, salt, iterations, dklen=0, digest=None):

View File

@ -53,8 +53,6 @@ details on these changes.
* The ``django.core.mail.forbid_multi_line_headers()`` and
``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:
6.1

View File

@ -570,9 +570,6 @@ Miscellaneous
* The undocumented ``django.core.mail.forbid_multi_line_headers()`` and
``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
=======================

View File

@ -2,34 +2,25 @@ import hashlib
import unittest
from django.test import SimpleTestCase
from django.test.utils import ignore_warnings
from django.utils.crypto import (
InvalidAlgorithm,
constant_time_compare,
pbkdf2,
salted_hmac,
)
from django.utils.deprecation import RemovedInDjango70Warning
class TestUtilsCryptoMisc(SimpleTestCase):
# RemovedInDjango70Warning.
@ignore_warnings(category=RemovedInDjango70Warning)
def test_constant_time_compare(self):
# It's hard to test for constant time, just test the result.
self.assertTrue(constant_time_compare(b"spam", b"spam"))
self.assertFalse(constant_time_compare(b"spam", b"eggs"))
self.assertTrue(constant_time_compare("spam", "spam"))
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__)
self.assertTrue(constant_time_compare(b"spam", "spam"))
self.assertFalse(constant_time_compare("spam", b"eggs"))
self.assertTrue(constant_time_compare("ありがとう", "ありがとう"))
self.assertFalse(constant_time_compare("ありがとう", "おはよう"))
def test_salted_hmac(self):
tests = [