diff options
| author | ReAnzu <anzu@reanzu.com> | 2018-02-24 23:45:00 -0600 |
|---|---|---|
| committer | rr- <rr-@sakuya.pl> | 2018-03-08 23:40:47 +0100 |
| commit | 3f52aceca44bc52e0c8654f46c66073320109ae9 (patch) | |
| tree | 8eb921ff35b1241f3eafef97db45c6e495f37338 /server/szurubooru/func/auth.py | |
| parent | 7519e071e79a2cf51e41e35fce030749c71fc0cf (diff) | |
server/users: harden password hashes
- Changed password setup to use libsodium and argon2id (regular SHA256
hashing for passwords is inadequate as modern GPU's can hash generate
billions of hashes per second).
- Added code to auto migrate old passwords to the new password_hash if
the existing password_hash matches either of the legacy password
generation schemes (SHA1 or SHA256).
- Added migration to support new password_hash format length
- Added column password_revision. This field will default to 0, which
all passwords will have till they're updated. After that each password
hash method has a revision.
Diffstat (limited to 'server/szurubooru/func/auth.py')
| -rw-r--r-- | server/szurubooru/func/auth.py | 48 |
1 files changed, 36 insertions, 12 deletions
diff --git a/server/szurubooru/func/auth.py b/server/szurubooru/func/auth.py index 25c991c..c9740fe 100644 --- a/server/szurubooru/func/auth.py +++ b/server/szurubooru/func/auth.py @@ -1,7 +1,10 @@ +from typing import Tuple import hashlib import random from collections import OrderedDict -from szurubooru import config, model, errors +from nacl import pwhash +from nacl.exceptions import InvalidkeyError +from szurubooru import config, model, errors, db from szurubooru.func import util @@ -16,22 +19,29 @@ RANK_MAP = OrderedDict([ ]) -def get_password_hash(salt: str, password: str) -> str: - ''' Retrieve new-style password hash. ''' +def get_password_hash(salt: str, password: str) -> Tuple[str, int]: + ''' Retrieve argon2id password hash. ''' + return pwhash.argon2id.str( + (config.config['secret'] + salt + password).encode('utf8') + ).decode('utf8'), 3 + + +def get_sha256_legacy_password_hash(salt: str, password: str) -> Tuple[str, int]: + ''' Retrieve old-style sha256 password hash. ''' digest = hashlib.sha256() digest.update(config.config['secret'].encode('utf8')) digest.update(salt.encode('utf8')) digest.update(password.encode('utf8')) - return digest.hexdigest() + return digest.hexdigest(), 2 -def get_legacy_password_hash(salt: str, password: str) -> str: - ''' Retrieve old-style password hash. ''' +def get_sha1_legacy_password_hash(salt: str, password: str) -> Tuple[str, int]: + ''' Retrieve old-style sha1 password hash. ''' digest = hashlib.sha1() digest.update(b'1A2/$_4xVa') digest.update(salt.encode('utf8')) digest.update(password.encode('utf8')) - return digest.hexdigest() + return digest.hexdigest(), 1 def create_password() -> str: @@ -47,11 +57,25 @@ def create_password() -> str: def is_valid_password(user: model.User, password: str) -> bool: assert user salt, valid_hash = user.password_salt, user.password_hash - possible_hashes = [ - get_password_hash(salt, password), - get_legacy_password_hash(salt, password) - ] - return valid_hash in possible_hashes + + try: + return pwhash.verify( + user.password_hash.encode('utf8'), + (config.config['secret'] + salt + password).encode('utf8')) + except InvalidkeyError: + possible_hashes = [ + get_sha256_legacy_password_hash(salt, password)[0], + get_sha1_legacy_password_hash(salt, password)[0] + ] + if valid_hash in possible_hashes: + # Convert the user password hash to the new hash + new_hash, revision = get_password_hash(salt, password) + user.password_hash = new_hash + user.password_revision = revision + db.session.commit() + return True + + return False def has_privilege(user: model.User, privilege_name: str) -> bool: |