Skip to content
Snippets Groups Projects
Commit ac003909 authored by Julian's avatar Julian
Browse files

Argon2 for user password hashing

Argon2 is a modern password hashing algorithm. It is significantly more secure
than the previous algorithm (salted SHA512). User logins with Argon2 are
relativly slow and cause significant spikes in CPU and memory (100MB) usage.

Existing passwords are gradually migrated to Argon2 on login.
parent 117e257c
No related branches found
No related tags found
No related merge requests found
......@@ -16,6 +16,7 @@ Please note that we refer to Debian packages here and **not** pip packages.
- python3-fido2 (version 0.5.0 or 0.9.1, optional)
- python3-oauthlib
- python3-flask-babel
- python3-argon2
- python3-mysqldb or python3-pymysql for MySQL/MariaDB support
Some of the dependencies (especially fido2) changed their API in recent versions, so make sure to install the versions from Debian Buster or Bullseye.
......
......@@ -25,6 +25,7 @@ Depends:
python3-fido2,
python3-oauthlib,
python3-flask-babel,
python3-argon2,
uwsgi,
uwsgi-plugin-python3,
Recommends:
......
......@@ -40,6 +40,7 @@ setup(
'Flask-Migrate==2.1.1',
'Flask-Babel==0.11.2',
'alembic==1.0.0',
'argon2-cffi==18.3.0',
# The main dependencies on their own lead to version collisions and pip is
# not very good at resolving them, so we pin the versions from Debian Buster
......
......@@ -130,6 +130,31 @@ class TestCryptPasswordHash(unittest.TestCase):
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
class TestArgon2PasswordHash(unittest.TestCase):
def test_verify(self):
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw')
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
obj = Argon2PasswordHash('{argon2}$invalid$')
self.assertFalse(obj.verify('password'))
def test_from_password(self):
obj = Argon2PasswordHash.from_password('password')
self.assertIsNotNone(obj.value)
self.assertTrue(obj.value.startswith('{argon2}'))
self.assertTrue(obj.verify('password'))
self.assertFalse(obj.verify('notpassword'))
def test_needs_rehash(self):
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw')
self.assertFalse(obj.needs_rehash)
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=2,p=8$Jc8LpCgPLjwlN/7efHLvwQ$ZqSg3CFb2/hBb3X8hOq4aw', target_cls=PlaintextPasswordHash)
self.assertTrue(obj.needs_rehash)
obj = Argon2PasswordHash('{argon2}$argon2d$v=19$m=102400,t=2,p=8$kshPgLU1+h72l/Z8QWh8Ig$tYerKCe/5I2BCPKu8hCl2w')
self.assertTrue(obj.needs_rehash)
obj = Argon2PasswordHash('{argon2}$argon2id$v=19$m=102400,t=1,p=8$aa6i4vg/szKX5xHVGFaAeQ$v6j0ltuVqQaZlmuepaVJ1A')
self.assertTrue(obj.needs_rehash)
class TestInvalidPasswordHash(unittest.TestCase):
def test(self):
obj = InvalidPasswordHash('test')
......
......@@ -2,6 +2,7 @@ import secrets
import hashlib
import base64
from crypt import crypt
import argon2
def build_value(method_name, data):
return '{' + method_name + '}' + data
......@@ -179,6 +180,28 @@ class CryptPasswordHash(PasswordHash):
def verify(self, password):
return secrets.compare_digest(crypt(password, self.data), self.data)
@registry.register
class Argon2PasswordHash(PasswordHash):
METHOD_NAME = 'argon2'
hasher = argon2.PasswordHasher()
@classmethod
def from_password(cls, password):
return cls(build_value(cls.METHOD_NAME, cls.hasher.hash(password)))
def verify(self, password):
try:
return self.hasher.verify(self.data, password)
except argon2.exceptions.Argon2Error:
return False
except argon2.exceptions.InvalidHash:
return False
@property
def needs_rehash(self):
return super().needs_rehash or self.hasher.check_needs_rehash(self.data)
class InvalidPasswordHash:
def __init__(self, value=None):
self.value = value
......@@ -262,9 +285,8 @@ class PasswordHashAttribute:
setattr(obj, self.attribute_name, value.value)
# Hashing method for (potentially) low entropy secrets like user passwords. Is
# usually slow and uses salting to make dictionary attacks difficult. Note
# that SSHA512 is not slow and should be replaced with a modern alternative.
LowEntropyPasswordHash = SaltedSHA512PasswordHash
# usually slow and uses salting to make dictionary attacks difficult.
LowEntropyPasswordHash = Argon2PasswordHash
# Hashing method for high entropy secrets like API keys. The secrets are
# generated instead of user-selected to ensure a high level of entropy. Is
......
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment