Initial Awawawa
This commit is contained in:
61
web/patchbay_web/security.py
Normal file
61
web/patchbay_web/security.py
Normal file
@@ -0,0 +1,61 @@
|
||||
"""Password hashing (scrypt from the standard library) and token helpers."""
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import secrets
|
||||
|
||||
PREFIX = "$scrypt$"
|
||||
N, R, P = 2 ** 15, 8, 1
|
||||
|
||||
|
||||
def _b64(b):
|
||||
return base64.b64encode(b).decode().rstrip("=")
|
||||
|
||||
|
||||
def _unb64(s):
|
||||
return base64.b64decode(s + "=" * (-len(s) % 4))
|
||||
|
||||
|
||||
def hash_password(password):
|
||||
salt = secrets.token_bytes(16)
|
||||
dk = hashlib.scrypt(password.encode(), salt=salt, n=N, r=R, p=P, maxmem=64 * 1024 * 1024, dklen=32)
|
||||
return f"{PREFIX}n={N},r={R},p={P}${_b64(salt)}${_b64(dk)}"
|
||||
|
||||
|
||||
def is_hashed(value):
|
||||
return value.startswith(PREFIX)
|
||||
|
||||
|
||||
# Used for unknown users so a login attempt costs the same either way.
|
||||
_DUMMY = None
|
||||
|
||||
|
||||
def verify_password(password, stored):
|
||||
global _DUMMY
|
||||
if not stored or not is_hashed(stored):
|
||||
if _DUMMY is None:
|
||||
_DUMMY = hash_password("dummy")
|
||||
stored, ok = _DUMMY, False
|
||||
else:
|
||||
ok = True
|
||||
try:
|
||||
params, salt, dk = stored[len(PREFIX):].split("$")
|
||||
p = dict(kv.split("=") for kv in params.split(","))
|
||||
calc = hashlib.scrypt(password.encode(), salt=_unb64(salt), n=int(p["n"]), r=int(p["r"]),
|
||||
p=int(p["p"]), maxmem=64 * 1024 * 1024, dklen=len(_unb64(dk)))
|
||||
except (ValueError, KeyError):
|
||||
return False
|
||||
return hmac.compare_digest(calc, _unb64(dk)) and ok
|
||||
|
||||
|
||||
def token():
|
||||
return secrets.token_urlsafe(32)
|
||||
|
||||
|
||||
def code_hash(tok, code):
|
||||
return hashlib.sha256(f"{tok}:{code}".encode()).hexdigest()
|
||||
|
||||
|
||||
def new_code():
|
||||
return f"{secrets.randbelow(10 ** 6):06d}"
|
||||
Reference in New Issue
Block a user