Commit b4250ea0 authored by Bas Westerbaan's avatar Bas Westerbaan Committed by Tim Graham
Browse files

Fixed #26033 -- Added Argon2 password hasher.

parent 74670498
Loading
Loading
Loading
Loading
+1 −0
Original line number Diff line number Diff line
@@ -500,6 +500,7 @@ PASSWORD_RESET_TIMEOUT_DAYS = 3
PASSWORD_HASHERS = [
    'django.contrib.auth.hashers.PBKDF2PasswordHasher',
    'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
    'django.contrib.auth.hashers.Argon2PasswordHasher',
    'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
    'django.contrib.auth.hashers.BCryptPasswordHasher',
]
+73 −0
Original line number Diff line number Diff line
@@ -297,6 +297,79 @@ class PBKDF2SHA1PasswordHasher(PBKDF2PasswordHasher):
    digest = hashlib.sha1


class Argon2PasswordHasher(BasePasswordHasher):
    """
    Secure password hashing using the argon2 algorithm.

    This is the winner of the Password Hashing Competition 2013-2015
    (https://password-hashing.net). It requires the argon2-cffi library which
    depends on native C code and might cause portability issues.
    """
    algorithm = 'argon2'
    library = 'argon2'

    time_cost = 2
    memory_cost = 512
    parallelism = 2

    def encode(self, password, salt):
        argon2 = self._load_library()
        data = argon2.low_level.hash_secret(
            force_bytes(password),
            force_bytes(salt),
            time_cost=self.time_cost,
            memory_cost=self.memory_cost,
            parallelism=self.parallelism,
            hash_len=argon2.DEFAULT_HASH_LENGTH,
            type=argon2.low_level.Type.I,
        )
        return self.algorithm + data.decode('utf-8')

    def verify(self, password, encoded):
        argon2 = self._load_library()
        algorithm, data = encoded.split('$', 1)
        assert algorithm == self.algorithm
        try:
            return argon2.low_level.verify_secret(
                force_bytes('$' + data),
                force_bytes(password),
                type=argon2.low_level.Type.I,
            )
        except argon2.exceptions.VerificationError:
            return False

    def safe_summary(self, encoded):
        algorithm, variety, raw_pars, salt, data = encoded.split('$', 5)
        pars = dict(bit.split('=', 1) for bit in raw_pars.split(','))
        assert algorithm == self.algorithm
        assert len(pars) == 3 and 't' in pars and 'm' in pars and 'p' in pars
        return OrderedDict([
            (_('algorithm'), algorithm),
            (_('variety'), variety),
            (_('memory cost'), int(pars['m'])),
            (_('time cost'), int(pars['t'])),
            (_('parallelism'), int(pars['p'])),
            (_('salt'), mask_hash(salt)),
            (_('hash'), mask_hash(data)),
        ])

    def must_update(self, encoded):
        algorithm, variety, raw_pars, salt, data = encoded.split('$', 5)
        pars = dict([bit.split('=', 1) for bit in raw_pars.split(',')])
        assert algorithm == self.algorithm
        assert len(pars) == 3 and 't' in pars and 'm' in pars and 'p' in pars
        return (
            self.time_cost != int(pars['t']) or
            self.memory_cost != int(pars['m']) or
            self.parallelism != int(pars['p'])
        )

    def harden_runtime(self, password, encoded):
        # The runtime for Argon2 is too complicated to implement a sensible
        # hardening algorithm.
        pass


class BCryptSHA256PasswordHasher(BasePasswordHasher):
    """
    Secure password hashing using the bcrypt algorithm (recommended)
+2 −0
Original line number Diff line number Diff line
@@ -137,6 +137,7 @@ Running all the tests
If you want to run the full suite of tests, you'll need to install a number of
dependencies:

*  argon2-cffi_ 16.0.0+
*  bcrypt_
*  docutils_
*  enum34_ (Python 2 only)
@@ -171,6 +172,7 @@ and install the Geospatial libraries</ref/contrib/gis/install/index>`.
Each of these dependencies is optional. If you're missing any of them, the
associated tests will be skipped.

.. _argon2-cffi: https://pypi.python.org/pypi/argon2_cffi
.. _bcrypt: https://pypi.python.org/pypi/bcrypt
.. _docutils: https://pypi.python.org/pypi/docutils
.. _enum34: https://pypi.python.org/pypi/enum34
+3 −0
Original line number Diff line number Diff line
@@ -2684,6 +2684,7 @@ Default::
    [
        'django.contrib.auth.hashers.PBKDF2PasswordHasher',
        'django.contrib.auth.hashers.PBKDF2SHA1PasswordHasher',
        'django.contrib.auth.hashers.Argon2PasswordHasher',
        'django.contrib.auth.hashers.BCryptSHA256PasswordHasher',
        'django.contrib.auth.hashers.BCryptPasswordHasher',
    ]
@@ -2702,6 +2703,8 @@ Default::
    to strengthen the hashes in your database. If that's not feasible, add this
    setting to your project and add back any hashers that you need.

    Also, the ``Argon2PasswordHasher`` was added.

.. setting:: AUTH_PASSWORD_VALIDATORS

``AUTH_PASSWORD_VALIDATORS``
+4 −0
Original line number Diff line number Diff line
@@ -70,6 +70,10 @@ Minor features
:mod:`django.contrib.auth`
~~~~~~~~~~~~~~~~~~~~~~~~~~

* Added support for the :ref:`Argon2 password hash <argon2_usage>`. It's
  recommended over PBKDF2, however, it's not the default as it requires a
  third-party library.

* The default iteration count for the PBKDF2 password hasher has been increased
  by 25%. This backwards compatible change will not affect users who have
  subclassed ``django.contrib.auth.hashers.PBKDF2PasswordHasher`` to change the
Loading