Skip to content

Commit d0e4dd5

Browse files
committed
Fixed #36572 -- Revert "Fixed #36546 -- Deprecated django.utils.crypto.constant_time_compare() in favor of hmac.compare_digest()."
This reverts commit 0246f47.
1 parent c594574 commit d0e4dd5

9 files changed

Lines changed: 28 additions & 48 deletions

File tree

django/contrib/auth/__init__.py

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
import hmac
21
import inspect
32
import re
43
import warnings
@@ -7,6 +6,7 @@
76
from django.conf import settings
87
from django.core.exceptions import ImproperlyConfigured, PermissionDenied
98
from django.middleware.csrf import rotate_token
9+
from django.utils.crypto import constant_time_compare
1010
from django.utils.deprecation import RemovedInDjango61Warning
1111
from django.utils.module_loading import import_string
1212
from django.views.decorators.debug import sensitive_variables
@@ -175,7 +175,7 @@ def login(request, user, backend=None):
175175
if SESSION_KEY in request.session:
176176
if _get_user_session_key(request) != user.pk or (
177177
session_auth_hash
178-
and not hmac.compare_digest(
178+
and not constant_time_compare(
179179
request.session.get(HASH_SESSION_KEY, ""), session_auth_hash
180180
)
181181
):
@@ -217,7 +217,7 @@ async def alogin(request, user, backend=None):
217217
if await request.session.ahas_key(SESSION_KEY):
218218
if await _aget_user_session_key(request) != user.pk or (
219219
session_auth_hash
220-
and not hmac.compare_digest(
220+
and not constant_time_compare(
221221
await request.session.aget(HASH_SESSION_KEY, ""),
222222
session_auth_hash,
223223
)
@@ -323,15 +323,15 @@ def get_user(request):
323323
session_hash_verified = False
324324
else:
325325
session_auth_hash = user.get_session_auth_hash()
326-
session_hash_verified = hmac.compare_digest(
326+
session_hash_verified = constant_time_compare(
327327
session_hash, session_auth_hash
328328
)
329329
if not session_hash_verified:
330330
# If the current secret does not verify the session, try
331331
# with the fallback secrets and stop when a matching one is
332332
# found.
333333
if session_hash and any(
334-
hmac.compare_digest(session_hash, fallback_auth_hash)
334+
constant_time_compare(session_hash, fallback_auth_hash)
335335
for fallback_auth_hash in user.get_session_auth_fallback_hash()
336336
):
337337
request.session.cycle_key()
@@ -364,15 +364,15 @@ async def aget_user(request):
364364
session_hash_verified = False
365365
else:
366366
session_auth_hash = user.get_session_auth_hash()
367-
session_hash_verified = hmac.compare_digest(
367+
session_hash_verified = constant_time_compare(
368368
session_hash, session_auth_hash
369369
)
370370
if not session_hash_verified:
371371
# If the current secret does not verify the session, try
372372
# with the fallback secrets and stop when a matching one is
373373
# found.
374374
if session_hash and any(
375-
hmac.compare_digest(session_hash, fallback_auth_hash)
375+
constant_time_compare(session_hash, fallback_auth_hash)
376376
for fallback_auth_hash in user.get_session_auth_fallback_hash()
377377
):
378378
await request.session.acycle_key()

django/contrib/auth/hashers.py

Lines changed: 10 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,6 @@
22
import binascii
33
import functools
44
import hashlib
5-
import hmac
65
import importlib
76
import math
87
import warnings
@@ -13,7 +12,12 @@
1312
from django.core.exceptions import ImproperlyConfigured
1413
from django.core.signals import setting_changed
1514
from django.dispatch import receiver
16-
from django.utils.crypto import RANDOM_STRING_CHARS, get_random_string, pbkdf2
15+
from django.utils.crypto import (
16+
RANDOM_STRING_CHARS,
17+
constant_time_compare,
18+
get_random_string,
19+
pbkdf2,
20+
)
1721
from django.utils.encoding import force_bytes, force_str
1822
from django.utils.module_loading import import_string
1923
from django.utils.translation import gettext_noop as _
@@ -345,7 +349,7 @@ def decode(self, encoded):
345349
def verify(self, password, encoded):
346350
decoded = self.decode(encoded)
347351
encoded_2 = self.encode(password, decoded["salt"], decoded["iterations"])
348-
return hmac.compare_digest(encoded, encoded_2)
352+
return constant_time_compare(encoded, encoded_2)
349353

350354
def safe_summary(self, encoded):
351355
decoded = self.decode(encoded)
@@ -529,7 +533,7 @@ def verify(self, password, encoded):
529533
algorithm, data = encoded.split("$", 1)
530534
assert algorithm == self.algorithm
531535
encoded_2 = self.encode(password, data.encode("ascii"))
532-
return hmac.compare_digest(encoded, encoded_2)
536+
return constant_time_compare(encoded, encoded_2)
533537

534538
def safe_summary(self, encoded):
535539
decoded = self.decode(encoded)
@@ -624,7 +628,7 @@ def verify(self, password, encoded):
624628
decoded["block_size"],
625629
decoded["parallelism"],
626630
)
627-
return hmac.compare_digest(encoded, encoded_2)
631+
return constant_time_compare(encoded, encoded_2)
628632

629633
def safe_summary(self, encoded):
630634
decoded = self.decode(encoded)
@@ -677,7 +681,7 @@ def decode(self, encoded):
677681
def verify(self, password, encoded):
678682
decoded = self.decode(encoded)
679683
encoded_2 = self.encode(password, decoded["salt"])
680-
return hmac.compare_digest(encoded, encoded_2)
684+
return constant_time_compare(encoded, encoded_2)
681685

682686
def safe_summary(self, encoded):
683687
decoded = self.decode(encoded)

django/contrib/auth/tokens.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
1-
import hmac
21
from datetime import datetime
32

43
from django.conf import settings
5-
from django.utils.crypto import salted_hmac
4+
from django.utils.crypto import constant_time_compare, salted_hmac
65
from django.utils.http import base36_to_int, int_to_base36
76

87

@@ -68,7 +67,7 @@ def check_token(self, user, token):
6867

6968
# Check that the timestamp/uid has not been tampered with
7069
for secret in [self.secret, *self.secret_fallbacks]:
71-
if hmac.compare_digest(
70+
if constant_time_compare(
7271
self._make_token_with_timestamp(user, ts, secret),
7372
token,
7473
):

django/core/signing.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,12 @@
3636

3737
import base64
3838
import datetime
39-
import hmac
4039
import json
4140
import time
4241
import zlib
4342

4443
from django.conf import settings
45-
from django.utils.crypto import salted_hmac
44+
from django.utils.crypto import constant_time_compare, salted_hmac
4645
from django.utils.encoding import force_bytes
4746
from django.utils.module_loading import import_string
4847
from django.utils.regex_helper import _lazy_re_compile
@@ -210,7 +209,7 @@ def unsign(self, signed_value):
210209
raise BadSignature('No "%s" found in value' % self.sep)
211210
value, sig = signed_value.rsplit(self.sep, 1)
212211
for key in [self.key, *self.fallback_keys]:
213-
if hmac.compare_digest(sig, self.signature(value, key)):
212+
if constant_time_compare(sig, self.signature(value, key)):
214213
return value
215214
raise BadSignature('Signature "%s" does not match' % sig)
216215

django/middleware/csrf.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,6 @@
55
against request forgeries from other sites.
66
"""
77

8-
import hmac
98
import logging
109
import string
1110
from collections import defaultdict
@@ -16,7 +15,7 @@
1615
from django.http import HttpHeaders, UnreadablePostError
1716
from django.urls import get_callable
1817
from django.utils.cache import patch_vary_headers
19-
from django.utils.crypto import get_random_string
18+
from django.utils.crypto import constant_time_compare, get_random_string
2019
from django.utils.deprecation import MiddlewareMixin
2120
from django.utils.functional import cached_property
2221
from django.utils.http import is_same_domain
@@ -155,7 +154,7 @@ def _does_token_match(request_csrf_token, csrf_secret):
155154
if len(request_csrf_token) == CSRF_TOKEN_LENGTH:
156155
request_csrf_token = _unmask_cipher_token(request_csrf_token)
157156
assert len(request_csrf_token) == CSRF_SECRET_LENGTH
158-
return hmac.compare_digest(request_csrf_token, csrf_secret)
157+
return constant_time_compare(request_csrf_token, csrf_secret)
159158

160159

161160
class RejectRequest(Exception):

django/utils/crypto.py

Lines changed: 1 addition & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,10 +5,8 @@
55
import hashlib
66
import hmac
77
import secrets
8-
import warnings
98

109
from django.conf import settings
11-
from django.utils.deprecation import RemovedInDjango70Warning
1210
from django.utils.encoding import force_bytes
1311

1412

@@ -66,12 +64,7 @@ def get_random_string(length, allowed_chars=RANDOM_STRING_CHARS):
6664

6765
def constant_time_compare(val1, val2):
6866
"""Return True if the two strings are equal, False otherwise."""
69-
warnings.warn(
70-
"constant_time_compare() is deprecated. Use hmac.compare_digest() instead.",
71-
RemovedInDjango70Warning,
72-
stacklevel=2,
73-
)
74-
return hmac.compare_digest(val1, val2)
67+
return secrets.compare_digest(force_bytes(val1), force_bytes(val2))
7568

7669

7770
def pbkdf2(password, salt, iterations, dklen=0, digest=None):

docs/internals/deprecation.txt

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -53,8 +53,6 @@ details on these changes.
5353
* The ``django.core.mail.forbid_multi_line_headers()`` and
5454
``django.core.mail.message.sanitize_address()`` functions will be removed.
5555

56-
* The ``django.utils.crypto.constant_time_compare()`` function will be removed.
57-
5856
.. _deprecation-removed-in-6.1:
5957

6058
6.1

docs/releases/6.0.txt

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -570,9 +570,6 @@ Miscellaneous
570570
* The undocumented ``django.core.mail.forbid_multi_line_headers()`` and
571571
``django.core.mail.message.sanitize_address()`` functions are deprecated.
572572

573-
* The ``django.utils.crypto.constant_time_compare()`` function is deprecated
574-
because it is merely an alias of :py:func:`hmac.compare_digest`.
575-
576573
Features removed in 6.0
577574
=======================
578575

tests/utils_tests/test_crypto.py

Lines changed: 4 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -2,34 +2,25 @@
22
import unittest
33

44
from django.test import SimpleTestCase
5-
from django.test.utils import ignore_warnings
65
from django.utils.crypto import (
76
InvalidAlgorithm,
87
constant_time_compare,
98
pbkdf2,
109
salted_hmac,
1110
)
12-
from django.utils.deprecation import RemovedInDjango70Warning
1311

1412

1513
class TestUtilsCryptoMisc(SimpleTestCase):
16-
# RemovedInDjango70Warning.
17-
@ignore_warnings(category=RemovedInDjango70Warning)
1814
def test_constant_time_compare(self):
1915
# It's hard to test for constant time, just test the result.
2016
self.assertTrue(constant_time_compare(b"spam", b"spam"))
2117
self.assertFalse(constant_time_compare(b"spam", b"eggs"))
2218
self.assertTrue(constant_time_compare("spam", "spam"))
2319
self.assertFalse(constant_time_compare("spam", "eggs"))
24-
25-
def test_constant_time_compare_deprecated(self):
26-
msg = (
27-
"constant_time_compare() is deprecated. "
28-
"Use hmac.compare_digest() instead."
29-
)
30-
with self.assertWarnsMessage(RemovedInDjango70Warning, msg) as ctx:
31-
constant_time_compare(b"spam", b"spam")
32-
self.assertEqual(ctx.filename, __file__)
20+
self.assertTrue(constant_time_compare(b"spam", "spam"))
21+
self.assertFalse(constant_time_compare("spam", b"eggs"))
22+
self.assertTrue(constant_time_compare("ありがとう", "ありがとう"))
23+
self.assertFalse(constant_time_compare("ありがとう", "おはよう"))
3324

3425
def test_salted_hmac(self):
3526
tests = [

0 commit comments

Comments
 (0)