diff --git a/README.md b/README.md index d55f97e..a8375f2 100644 --- a/README.md +++ b/README.md @@ -24,6 +24,11 @@ value the client sends. Max uses this address for audit logging. For nginx: proxy_set_header X-Real-IP $remote_addr; ``` +## Dovecot + +Dovecot reads the password hashes written by Max from the `users` table. Max stores bcrypt +hashes as `{BLF-CRYPT}$2b$12$…`. Dovecot must therefore support `BLF-CRYPT` (check with `doveadm pw -l`). + ## Dependencies Direct dependencies are listed in `requirements.in` (production) and diff --git a/max/auth/auth.py b/max/auth/auth.py index 8ddbc19..1c49098 100644 --- a/max/auth/auth.py +++ b/max/auth/auth.py @@ -1,17 +1,13 @@ -import base64 -import hashlib - import bcrypt from flask import current_app, session -from max.crypto import constant_time_compare, force_bytes +from max.crypto import force_bytes from max.db import get_user_by_email, update_user from max.exceptions import InvalidPassword from max.sessions import session_storage from max.translations import t BLF_CRYPT_PREFIX = "{BLF-CRYPT}" -SSHA512_PREFIX = "{SSHA512}" BCRYPT_ROUNDS = 12 # bcrypt only uses the first 72 bytes MAX_PASSWORD_BYTES = 72 @@ -100,8 +96,6 @@ def hash_password(password: str): def needs_rehash(hash: str): - if not hash.startswith(BLF_CRYPT_PREFIX): - return True # "$2b$12$…": the cost factor follows the version rounds = int(hash[len(BLF_CRYPT_PREFIX) :].split("$")[2]) # noqa return rounds < BCRYPT_ROUNDS @@ -115,31 +109,4 @@ def check_password(password: str, hash: str): bcrypt_hash = hash[len(BLF_CRYPT_PREFIX) :] # noqa return bcrypt.checkpw(force_bytes(password), force_bytes(bcrypt_hash)) - if hash.startswith(SSHA512_PREFIX): - return check_ssha512_password(password, hash) - raise RuntimeError("The hash has an unknown scheme") - - -# {SSHA512} is outdated. Existing hashes are still checked and replaced at the -# next login. Remove this once no hash uses it anymore (see F-08). - - -def hash_ssha512_password(password: str, salt: bytes): - sha = hashlib.sha512() - sha.update(force_bytes(password)) - sha.update(salt) - sha_and_salt = base64.b64encode(sha.digest() + salt) - return SSHA512_PREFIX + sha_and_salt.decode() - - -def get_ssha512_salt(hash: str): - sha_and_salt = base64.b64decode(hash[len(SSHA512_PREFIX) :]) # noqa - return sha_and_salt[64:] - - -def check_ssha512_password(password: str, hash: str): - salt = get_ssha512_salt(hash) - hash_from_password = hash_ssha512_password(password, salt) - - return constant_time_compare(hash, hash_from_password)