Remove the SSHA512 password hashes
All passwords are migrated to bcrypt, so {SSHA512} hashes are no longer
checked; an unknown scheme raises like before. The README documents that
Dovecot must support BLF-CRYPT.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
4cbe1435dc
commit
2e40c0ff47
2 files changed
+6
-34
No files matched your search
@@ -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
|
||||
|
||||
+1
-34
@@ -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)
|
||||
Reference in new issue
Block a user