Kaynağa Gözat

Refs #33691 -- Removed insecure password hashers per deprecation timeline.

Mariusz Felisiak 1 yıl önce
ebeveyn
işleme
6e4e5523a8

+ 0 - 160
django/contrib/auth/hashers.py

@@ -16,7 +16,6 @@ from django.utils.crypto import (
     get_random_string,
     pbkdf2,
 )
-from django.utils.deprecation import RemovedInDjango51Warning
 from django.utils.module_loading import import_string
 from django.utils.translation import gettext_noop as _
 
@@ -641,57 +640,6 @@ class ScryptPasswordHasher(BasePasswordHasher):
         pass
 
 
-# RemovedInDjango51Warning.
-class SHA1PasswordHasher(BasePasswordHasher):
-    """
-    The SHA1 password hashing algorithm (not recommended)
-    """
-
-    algorithm = "sha1"
-
-    def __init__(self, *args, **kwargs):
-        warnings.warn(
-            "django.contrib.auth.hashers.SHA1PasswordHasher is deprecated.",
-            RemovedInDjango51Warning,
-            stacklevel=2,
-        )
-        super().__init__(*args, **kwargs)
-
-    def encode(self, password, salt):
-        self._check_encode_args(password, salt)
-        hash = hashlib.sha1((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
-
-
 class MD5PasswordHasher(BasePasswordHasher):
     """
     The Salted MD5 password hashing algorithm (not recommended)
@@ -732,111 +680,3 @@ class MD5PasswordHasher(BasePasswordHasher):
 
     def harden_runtime(self, password, encoded):
         pass
-
-
-# RemovedInDjango51Warning.
-class UnsaltedSHA1PasswordHasher(BasePasswordHasher):
-    """
-    Very insecure algorithm that you should *never* use; store SHA1 hashes
-    with an empty salt.
-
-    This class is implemented because Django used to accept such password
-    hashes. Some older Django installs still have these values lingering
-    around so we need to handle and upgrade them properly.
-    """
-
-    algorithm = "unsalted_sha1"
-
-    def __init__(self, *args, **kwargs):
-        warnings.warn(
-            "django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher is deprecated.",
-            RemovedInDjango51Warning,
-            stacklevel=2,
-        )
-        super().__init__(*args, **kwargs)
-
-    def salt(self):
-        return ""
-
-    def encode(self, password, salt):
-        if salt != "":
-            raise ValueError("salt must be empty.")
-        hash = hashlib.sha1(password.encode()).hexdigest()
-        return "sha1$$%s" % hash
-
-    def decode(self, encoded):
-        assert encoded.startswith("sha1$$")
-        return {
-            "algorithm": self.algorithm,
-            "hash": encoded[6:],
-            "salt": None,
-        }
-
-    def verify(self, password, encoded):
-        encoded_2 = self.encode(password, "")
-        return constant_time_compare(encoded, encoded_2)
-
-    def safe_summary(self, encoded):
-        decoded = self.decode(encoded)
-        return {
-            _("algorithm"): decoded["algorithm"],
-            _("hash"): mask_hash(decoded["hash"]),
-        }
-
-    def harden_runtime(self, password, encoded):
-        pass
-
-
-# RemovedInDjango51Warning.
-class UnsaltedMD5PasswordHasher(BasePasswordHasher):
-    """
-    Incredibly insecure algorithm that you should *never* use; stores unsalted
-    MD5 hashes without the algorithm prefix, also accepts MD5 hashes with an
-    empty salt.
-
-    This class is implemented because Django used to store passwords this way
-    and to accept such password hashes. Some older Django installs still have
-    these values lingering around so we need to handle and upgrade them
-    properly.
-    """
-
-    algorithm = "unsalted_md5"
-
-    def __init__(self, *args, **kwargs):
-        warnings.warn(
-            "django.contrib.auth.hashers.UnsaltedMD5PasswordHasher is deprecated.",
-            RemovedInDjango51Warning,
-            stacklevel=2,
-        )
-        super().__init__(*args, **kwargs)
-
-    def salt(self):
-        return ""
-
-    def encode(self, password, salt):
-        if salt != "":
-            raise ValueError("salt must be empty.")
-        return hashlib.md5(password.encode()).hexdigest()
-
-    def decode(self, encoded):
-        return {
-            "algorithm": self.algorithm,
-            "hash": encoded,
-            "salt": None,
-        }
-
-    def verify(self, password, encoded):
-        if len(encoded) == 37:
-            encoded = encoded.removeprefix("md5$$")
-        encoded_2 = self.encode(password, "")
-        return constant_time_compare(encoded, encoded_2)
-
-    def safe_summary(self, encoded):
-        decoded = self.decode(encoded)
-        return {
-            _("algorithm"): decoded["algorithm"],
-            _("hash"): mask_hash(decoded["hash"], show=3),
-        }
-
-    def harden_runtime(self, password, encoded):
-        pass

+ 4 - 0
docs/releases/5.1.txt

@@ -256,3 +256,7 @@ to remove usage of these features.
 * The model's ``Meta.index_together`` option is removed.
 
 * The ``length_is`` template filter is removed.
+
+* The ``django.contrib.auth.hashers.SHA1PasswordHasher``,
+  ``django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher``, and
+  ``django.contrib.auth.hashers.UnsaltedMD5PasswordHasher`` are removed.

+ 1 - 115
tests/auth_tests/test_hashers.py

@@ -18,9 +18,8 @@ from django.contrib.auth.hashers import (
     is_password_usable,
     make_password,
 )
-from django.test import SimpleTestCase, ignore_warnings
+from django.test import SimpleTestCase
 from django.test.utils import override_settings
-from django.utils.deprecation import RemovedInDjango51Warning
 
 try:
     import bcrypt
@@ -103,40 +102,6 @@ class TestUtilsHashPass(SimpleTestCase):
         self.assertIs(hasher.must_update(encoded_weak_salt), True)
         self.assertIs(hasher.must_update(encoded_strong_salt), False)
 
-    @ignore_warnings(category=RemovedInDjango51Warning)
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.SHA1PasswordHasher"]
-    )
-    def test_sha1(self):
-        encoded = make_password("lètmein", "seasalt", "sha1")
-        self.assertEqual(
-            encoded, "sha1$seasalt$cff36ea83f5706ce9aa7454e63e431fc726b2dc8"
-        )
-        self.assertTrue(is_password_usable(encoded))
-        self.assertTrue(check_password("lètmein", encoded))
-        self.assertFalse(check_password("lètmeinz", encoded))
-        self.assertEqual(identify_hasher(encoded).algorithm, "sha1")
-        # Blank passwords
-        blank_encoded = make_password("", "seasalt", "sha1")
-        self.assertTrue(blank_encoded.startswith("sha1$"))
-        self.assertTrue(is_password_usable(blank_encoded))
-        self.assertTrue(check_password("", blank_encoded))
-        self.assertFalse(check_password(" ", blank_encoded))
-        # Salt entropy check.
-        hasher = get_hasher("sha1")
-        encoded_weak_salt = make_password("lètmein", "iodizedsalt", "sha1")
-        encoded_strong_salt = make_password("lètmein", hasher.salt(), "sha1")
-        self.assertIs(hasher.must_update(encoded_weak_salt), True)
-        self.assertIs(hasher.must_update(encoded_strong_salt), False)
-
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.SHA1PasswordHasher"]
-    )
-    def test_sha1_deprecation_warning(self):
-        msg = "django.contrib.auth.hashers.SHA1PasswordHasher is deprecated."
-        with self.assertRaisesMessage(RemovedInDjango51Warning, msg):
-            get_hasher("sha1")
-
     @override_settings(
         PASSWORD_HASHERS=["django.contrib.auth.hashers.MD5PasswordHasher"]
     )
@@ -160,85 +125,6 @@ class TestUtilsHashPass(SimpleTestCase):
         self.assertIs(hasher.must_update(encoded_weak_salt), True)
         self.assertIs(hasher.must_update(encoded_strong_salt), False)
 
-    @ignore_warnings(category=RemovedInDjango51Warning)
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.UnsaltedMD5PasswordHasher"]
-    )
-    def test_unsalted_md5(self):
-        encoded = make_password("lètmein", "", "unsalted_md5")
-        self.assertEqual(encoded, "88a434c88cca4e900f7874cd98123f43")
-        self.assertTrue(is_password_usable(encoded))
-        self.assertTrue(check_password("lètmein", encoded))
-        self.assertFalse(check_password("lètmeinz", encoded))
-        self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_md5")
-        # Alternate unsalted syntax
-        alt_encoded = "md5$$%s" % encoded
-        self.assertTrue(is_password_usable(alt_encoded))
-        self.assertTrue(check_password("lètmein", alt_encoded))
-        self.assertFalse(check_password("lètmeinz", alt_encoded))
-        # Blank passwords
-        blank_encoded = make_password("", "", "unsalted_md5")
-        self.assertTrue(is_password_usable(blank_encoded))
-        self.assertTrue(check_password("", blank_encoded))
-        self.assertFalse(check_password(" ", blank_encoded))
-
-    @ignore_warnings(category=RemovedInDjango51Warning)
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.UnsaltedMD5PasswordHasher"]
-    )
-    def test_unsalted_md5_encode_invalid_salt(self):
-        hasher = get_hasher("unsalted_md5")
-        msg = "salt must be empty."
-        with self.assertRaisesMessage(ValueError, msg):
-            hasher.encode("password", salt="salt")
-
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.UnsaltedMD5PasswordHasher"]
-    )
-    def test_unsalted_md5_deprecation_warning(self):
-        msg = "django.contrib.auth.hashers.UnsaltedMD5PasswordHasher is deprecated."
-        with self.assertRaisesMessage(RemovedInDjango51Warning, msg):
-            get_hasher("unsalted_md5")
-
-    @ignore_warnings(category=RemovedInDjango51Warning)
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher"]
-    )
-    def test_unsalted_sha1(self):
-        encoded = make_password("lètmein", "", "unsalted_sha1")
-        self.assertEqual(encoded, "sha1$$6d138ca3ae545631b3abd71a4f076ce759c5700b")
-        self.assertTrue(is_password_usable(encoded))
-        self.assertTrue(check_password("lètmein", encoded))
-        self.assertFalse(check_password("lètmeinz", encoded))
-        self.assertEqual(identify_hasher(encoded).algorithm, "unsalted_sha1")
-        # Raw SHA1 isn't acceptable
-        alt_encoded = encoded[6:]
-        self.assertFalse(check_password("lètmein", alt_encoded))
-        # Blank passwords
-        blank_encoded = make_password("", "", "unsalted_sha1")
-        self.assertTrue(blank_encoded.startswith("sha1$"))
-        self.assertTrue(is_password_usable(blank_encoded))
-        self.assertTrue(check_password("", blank_encoded))
-        self.assertFalse(check_password(" ", blank_encoded))
-
-    @ignore_warnings(category=RemovedInDjango51Warning)
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher"]
-    )
-    def test_unsalted_sha1_encode_invalid_salt(self):
-        hasher = get_hasher("unsalted_sha1")
-        msg = "salt must be empty."
-        with self.assertRaisesMessage(ValueError, msg):
-            hasher.encode("password", salt="salt")
-
-    @override_settings(
-        PASSWORD_HASHERS=["django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher"]
-    )
-    def test_unsalted_sha1_deprecation_warning(self):
-        msg = "django.contrib.auth.hashers.UnsaltedSHA1PasswordHasher is deprecated."
-        with self.assertRaisesMessage(RemovedInDjango51Warning, msg):
-            get_hasher("unsalted_sha1")
-
     @skipUnless(bcrypt, "bcrypt not installed")
     def test_bcrypt_sha256(self):
         encoded = make_password("lètmein", hasher="bcrypt_sha256")