-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathauth.py
More file actions
61 lines (48 loc) · 1.52 KB
/
Copy pathauth.py
File metadata and controls
61 lines (48 loc) · 1.52 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
import base64
import binascii
import hashlib
import hmac
import os
from typing import Optional
PASSWORD_HASH_SCHEME = "pbkdf2_sha256"
PASSWORD_HASH_ITERATIONS = 260_000
def normalize_username(username: str) -> str:
return username.strip().lower()
def hash_password(password: str) -> str:
salt = os.urandom(16)
digest = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
PASSWORD_HASH_ITERATIONS,
)
salt_b64 = base64.b64encode(salt).decode("ascii")
digest_b64 = base64.b64encode(digest).decode("ascii")
return f"{PASSWORD_HASH_SCHEME}${PASSWORD_HASH_ITERATIONS}${salt_b64}${digest_b64}"
def parse_password_hash(value: str) -> Optional[tuple[int, bytes, bytes]]:
try:
scheme, iterations, salt_b64, digest_b64 = value.split("$", 3)
except ValueError:
return None
if scheme != PASSWORD_HASH_SCHEME:
return None
try:
return (
int(iterations),
base64.b64decode(salt_b64.encode("ascii")),
base64.b64decode(digest_b64.encode("ascii")),
)
except (ValueError, binascii.Error):
return None
def verify_password_hash(password: str, stored_hash: str) -> bool:
parsed = parse_password_hash(stored_hash)
if parsed is None:
return False
iterations, salt, expected_digest = parsed
digest = hashlib.pbkdf2_hmac(
"sha256",
password.encode("utf-8"),
salt,
iterations,
)
return hmac.compare_digest(digest, expected_digest)