changeset 8239:6bd11a73f2ed

issue2551253. default hash is PBKDF2-SHA512. The default password hashing algorithm has been upgraded to PBKDF2-SHA512 from PBKDF2-SHA1. The default pbkdf2 rounds in the config file has been changed to 250000. Doc updated.
author John Rouillard <rouilj@ieee.org>
date Mon, 30 Dec 2024 02:57:46 -0500
parents 05405220dc38
children 1189c742e4b3
files CHANGES.txt doc/upgrading.txt roundup/configuration.py roundup/password.py
diffstat 4 files changed, 65 insertions(+), 7 deletions(-) [+]
line wrap: on
line diff
--- a/CHANGES.txt	Sun Dec 29 19:48:42 2024 -0500
+++ b/CHANGES.txt	Mon Dec 30 02:57:46 2024 -0500
@@ -49,6 +49,13 @@
 - issue2551383 - Setting same address via REST PUT command results in
   an error. Now the userauditor does not trigger an error if a user
   sets the primary address to the existing value. (John Rouillard)
+- issue2551253 - Modify password PBKDF2 method to use SHA512. The
+  default password hashing algorithm has been upgraded to
+  PBKDF2-SHA512 from PBKDF2-SHA1. The default pbkdf2 rounds in the
+  config file has been changed to 250000. The admin should change it
+  manually if it is at 2 million. PBKDF2-SHA512 (PBKDF2S5) has been
+  available since release 2.3, but it required a manual step to make
+  it the default. (John Rouillard)
 
 Features:
 
--- a/doc/upgrading.txt	Sun Dec 29 19:48:42 2024 -0500
+++ b/doc/upgrading.txt	Mon Dec 30 02:57:46 2024 -0500
@@ -159,6 +159,41 @@
 add the lines marked with ``+`` in the file in the location after
 check_main is assigned.
 
+Modify config.ini password_pbkdf2_default_rounds setting (recommended)
+----------------------------------------------------------------------
+
+The method for hashing and storing passwords has been updated to use
+PBKDF2 with SHA512 hash. This change was first introduced in Roundup
+2.3 and is now the standard. If you previously added code in
+interfaces.py for a `PBKDF2 upgrade`_ to enable PBKDF2S5, you can
+remove that code now.
+
+SHA512 is a more secure hash, it requires fewer rounds to ensure
+safety. The older PBKDF2-SHA1 needed around 2 million rounds.
+
+You should update the ``password_pbkdf2_default_rounds`` setting in
+``config.ini`` to 250000. This value is higher than the OWASP
+recommendation of 210000 from three years ago. If you don’t make this
+change, logins will be slow, especially for REST or XMLRPC calls.
+
+See `PBKDF2 upgrade`_ for details on how to test the algorithm's
+speed. We do not recommend reverting to the older SHA1 PBKDF2. If you
+have to do so due to a slow CPU, you can add the following to your
+tracker's ``interfaces.py``::
+
+  from roundup.password import Password
+  ## Use PBDKF2 (PBKDF2-SHA1) as default hash for passwords.
+  # That scheme is at the start of the deprecated_schemes list and ha
+  # to be removed.
+  Password.default_scheme = Password.deprecated_schemes.pop(0)
+  # Add PBKDF2S5 (PBKDF2-SHA512) as a valid scheme. Passwords
+  # using it will be rehashed to use PBDKF2.
+  Password.experimental_schemes[0] = "PBKDF2S5"
+
+If you proceed with this, you should set
+``password_pbkdf2_default_rounds`` to 2 million or more rounds to keep
+your hashed password database secure in case it gets stolen.
+
 Defusedxml support improves XMLRPC security (optional)
 ------------------------------------------------------
 
@@ -1292,6 +1327,8 @@
 
 .. _recommended setting of 1,300,000: https://cheatsheetseries.owasp.org/cheatsheets/Password_Storage_Cheat_Sheet.html#pbkdf2
 
+.. _PBKDF2 upgrade:
+
 Upgrade to PBKDF2-SHA512 from current PBKDF2-SHA1 (recommended)
 ---------------------------------------------------------------
 
--- a/roundup/configuration.py	Sun Dec 29 19:48:42 2024 -0500
+++ b/roundup/configuration.py	Mon Dec 30 02:57:46 2024 -0500
@@ -1139,10 +1139,11 @@
             "get the error 'Error: field larger than field limit' during\n"
             "import."),
         (IntegerNumberGeqZeroOption, 'password_pbkdf2_default_rounds',
-         '2000000',
+         '250000',
             "Sets the default number of rounds used when encoding passwords\n"
-            "using the PBKDF2 scheme. Set this to a higher value on faster\n"
-            "systems which want more security.\n"
+            "using any PBKDF2 scheme. Set this to a higher value on faster\n"
+            "systems which want more security. Use a minimum of 250000\n"
+            "for PBKDF2-SHA512 which is the default hash in Roundup 2.5.\n"
             "PBKDF2 (Password-Based Key Derivation Function) is a\n"
             "password hashing mechanism that derives hash from the\n"
             "password and a random salt. For authentication this process\n"
--- a/roundup/password.py	Sun Dec 29 19:48:42 2024 -0500
+++ b/roundup/password.py	Mon Dec 30 02:57:46 2024 -0500
@@ -331,7 +331,7 @@
         version only reads the encryption scheme from the given
         encrypted password.
     """
-    default_scheme = 'PBKDF2'        # new encryptions use this scheme
+    default_scheme = 'PBKDF2S5'        # new encryptions use this scheme
     pwre = re.compile(r'{(\w+)}(.+)')
 
     def __init__(self, encrypted=''):
@@ -394,12 +394,12 @@
     1
     """
 
-    deprecated_schemes = ["SSHA", "SHA", "MD5", "plaintext"]
+    deprecated_schemes = ["PBKDF2", "SSHA", "SHA", "MD5", "plaintext"]
     if crypt:
         # place just before plaintext if crypt is available
         deprecated_schemes.insert(-1, "crypt")
-    experimental_schemes = ["PBKDF2S5"]
-    known_schemes = ["PBKDF2"] + experimental_schemes + \
+    experimental_schemes = []
+    known_schemes = ["PBKDF2S5"] + experimental_schemes + \
         deprecated_schemes
 
     def __init__(self, plaintext=None, scheme=None, encrypted=None,
@@ -442,6 +442,19 @@
                     new_rounds = 1000
             if rounds < int(new_rounds):
                 return True
+
+        if (self.scheme == "PBKDF2S5"):
+            new_rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS
+            if ("pytest" in sys.modules and
+                "PYTEST_CURRENT_TEST" in os.environ):
+                if ("PYTEST_USE_CONFIG" in os.environ):
+                    new_rounds = config.PASSWORD_PBKDF2_DEFAULT_ROUNDS
+                else:
+                    # for testing
+                    new_rounds = 1000
+            if rounds < int(new_rounds):
+                return True
+
         return False
 
     def unpack(self, encrypted, scheme=None, strict=False, config=None):

Roundup Issue Tracker: http://roundup-tracker.org/