diff --git a/photo21/hashers.py b/photo21/hashers.py new file mode 100644 index 0000000..e94397e --- /dev/null +++ b/photo21/hashers.py @@ -0,0 +1,45 @@ +import hashlib +import base64 +from collections import OrderedDict + +from django.utils.crypto import constant_time_compare +from django.utils.encoding import force_bytes +from django.utils.translation import gettext_noop as _ +from django.contrib.auth.hashers import mask_hash, BasePasswordHasher + + +class SHA512PasswordHasher(BasePasswordHasher): + """ + The SHA512 password hashing algorithm + + It is used to migrate passwords from old Symfony2 photos server. + https://github.com/symfony/symfony/blob/2.8/src/Symfony/Component/Security/Core/Encoder/MessageDigestPasswordEncoder.php + """ + algorithm = "sha512" + + def encode(self, password, salt): + assert password is not None + assert salt and '$' not in salt + hash = hashlib.sha512(force_bytes(password + "{" + salt + "}")) + hash = base64.b64encode(hash) + encoded = "%s$%s$%s" % (self.algorithm, salt, hash) + encoded = encoded[:128] + return encoded + + def verify(self, password, encoded): + algorithm, salt, hash = encoded.split('$', 2) + assert algorithm == self.algorithm + encoded_2 = self.encode(password, salt) + return constant_time_compare(encoded, encoded_2) + + def safe_summary(self, encoded): + algorithm, salt, hash = encoded.split('$', 2) + assert algorithm == self.algorithm + return OrderedDict([ + (_('algorithm'), algorithm), + (_('salt'), mask_hash(salt, show=2)), + (_('hash'), mask_hash(hash)), + ]) + + def harden_runtime(self, password, encoded): + pass diff --git a/photo21/settings.py b/photo21/settings.py index 88216ed..f5aaaa0 100644 --- a/photo21/settings.py +++ b/photo21/settings.py @@ -129,6 +129,11 @@ AUTH_PASSWORD_VALIDATORS = [ }, ] +PASSWORD_HASHERS = [ + 'django.contrib.auth.hashers.PBKDF2PasswordHasher', + 'photo21.hashers.SHA512PasswordHasher', +] + # Internationalization # https://docs.djangoproject.com/en/2.2/topics/i18n/