Skip to content
Snippets Groups Projects
Forked from uffd / uffd
31 commits behind the upstream repository.
  • Julian's avatar
    ac003909
    Argon2 for user password hashing · ac003909
    Julian authored
    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.
    ac003909
    History
    Argon2 for user password hashing
    Julian authored
    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.
test_password_hash.py 7.48 KiB
import unittest

from uffd.password_hash import *

class TestPasswordHashRegistry(unittest.TestCase):
	def test(self):
		registry = PasswordHashRegistry()

		@registry.register
		class TestPasswordHash:
			METHOD_NAME = 'test'
			def __init__(self, value, **kwargs):
				self.value = value
				self.kwargs = kwargs

		@registry.register
		class Test2PasswordHash:
			METHOD_NAME = 'test2'

		result = registry.parse('{test}data', key='value')
		self.assertIsInstance(result, TestPasswordHash)
		self.assertEqual(result.value, '{test}data')
		self.assertEqual(result.kwargs, {'key': 'value'})
		with self.assertRaises(ValueError):
			registry.parse('{invalid}data')
		with self.assertRaises(ValueError):
			registry.parse('invalid')
		with self.assertRaises(ValueError):
			registry.parse('{invalid')

class TestPasswordHash(unittest.TestCase):
	def setUp(self):
		class TestPasswordHash(PasswordHash):
			@classmethod
			def from_password(cls, password):
				cls(build_value(cls.METHOD_NAME, password))

			def verify(self, password):
				return self.data == password

		class TestPasswordHash1(TestPasswordHash):
			METHOD_NAME = 'test1'

		class TestPasswordHash2(TestPasswordHash):
			METHOD_NAME = 'test2'

		self.TestPasswordHash1 = TestPasswordHash1
		self.TestPasswordHash2 = TestPasswordHash2

	def test(self):
		obj = self.TestPasswordHash1('{test1}data')
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, self.TestPasswordHash1)
		self.assertFalse(obj.needs_rehash)

	def test_invalid(self):
		with self.assertRaises(ValueError):
			self.TestPasswordHash1('invalid')
		with self.assertRaises(ValueError):
			self.TestPasswordHash1('{invalid}data')
		with self.assertRaises(ValueError):
			self.TestPasswordHash1('{test2}data')

	def test_target_cls(self):
		obj = self.TestPasswordHash1('{test1}data', target_cls=self.TestPasswordHash1)
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, self.TestPasswordHash1)
		self.assertFalse(obj.needs_rehash)
		obj = self.TestPasswordHash1('{test1}data', target_cls=self.TestPasswordHash2)
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, self.TestPasswordHash2)
		self.assertTrue(obj.needs_rehash)
		obj = self.TestPasswordHash1('{test1}data', target_cls=PasswordHash)
		self.assertEqual(obj.value, '{test1}data')
		self.assertEqual(obj.data, 'data')
		self.assertIs(obj.target_cls, PasswordHash)
		self.assertFalse(obj.needs_rehash)

class TestPlaintextPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = PlaintextPasswordHash('{plain}password')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = PlaintextPasswordHash.from_password('password')
		self.assertEqual(obj.value, '{plain}password')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestHashlibPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = SHA512PasswordHash('{sha512}sQnzu7wkTrgkQZF+0G1hi5AI3Qmzvv0bXgc5THBqi7mAsdd4Xll27ASbRt9fEyavWi6m0QP9B8lThf+rDKy8hg==')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = SHA512PasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{sha512}'))
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestSaltedHashlibPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = SaltedSHA512PasswordHash('{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = SaltedSHA512PasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{ssha512}'))
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

class TestCryptPasswordHash(unittest.TestCase):
	def test_verify(self):
		obj = CryptPasswordHash('{crypt}$5$UbTTMBH9NRurlQcX$bUiUTyedvmArlVt.62ZLRV80e2v3DjcBp/tSDkP2imD')
		self.assertTrue(obj.verify('password'))
		self.assertFalse(obj.verify('notpassword'))

	def test_from_password(self):
		obj = CryptPasswordHash.from_password('password')
		self.assertIsNotNone(obj.value)
		self.assertTrue(obj.value.startswith('{crypt}'))
		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')
		self.assertEqual(obj.value, 'test')
		self.assertFalse(obj.verify('test'))
		self.assertTrue(obj.needs_rehash)
		self.assertFalse(obj)
		obj = InvalidPasswordHash(None)
		self.assertIsNone(obj.value)
		self.assertFalse(obj.verify('test'))
		self.assertTrue(obj.needs_rehash)
		self.assertFalse(obj)

class TestPasswordWrapper(unittest.TestCase):
	def setUp(self):
		class Test:
			password_hash = None
			password = PasswordHashAttribute('password_hash', PlaintextPasswordHash)

		self.test = Test()

	def test_get_none(self):
		self.test.password_hash = None
		obj = self.test.password
		self.assertIsInstance(obj, InvalidPasswordHash)
		self.assertEqual(obj.value, None)
		self.assertTrue(obj.needs_rehash)

	def test_get_valid(self):
		self.test.password_hash = '{plain}password'
		obj = self.test.password
		self.assertIsInstance(obj, PlaintextPasswordHash)
		self.assertEqual(obj.value, '{plain}password')
		self.assertFalse(obj.needs_rehash)

	def test_get_needs_rehash(self):
		self.test.password_hash = '{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC'
		obj = self.test.password
		self.assertIsInstance(obj, SaltedSHA512PasswordHash)
		self.assertEqual(obj.value, '{ssha512}dOeDLmVpHJThhHeag10Hm2g4T7s3SBE6rGHcXUolXJHVufY4qT782rwZ/0XE6cuLcBZ0KpnwmUzRpAEtZBdv+JYEEtZQs/uC')
		self.assertTrue(obj.needs_rehash)

	def test_set(self):
		self.test.password = 'password'
		self.assertEqual(self.test.password_hash, '{plain}password')

	def test_set_none(self):
		self.test.password = None
		self.assertIsNone(self.test.password_hash)