rebuild(stage-3): establish accounts permissions and secure settings
This commit is contained in:
@@ -0,0 +1,68 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import secrets
|
||||
from dataclasses import dataclass
|
||||
|
||||
|
||||
class PasswordPolicyError(ValueError):
|
||||
pass
|
||||
|
||||
|
||||
@dataclass(frozen=True, slots=True)
|
||||
class PasswordHasher:
|
||||
work_factor: int = 2**15
|
||||
block_size: int = 8
|
||||
parallelism: int = 1
|
||||
salt_bytes: int = 16
|
||||
key_bytes: int = 32
|
||||
|
||||
def validate(self, password: str) -> None:
|
||||
if not 8 <= len(password) <= 128:
|
||||
raise PasswordPolicyError("密码长度应为8至128位。")
|
||||
categories = (
|
||||
any(character.isalpha() for character in password),
|
||||
any(character.isdigit() for character in password),
|
||||
any(not character.isalnum() for character in password),
|
||||
)
|
||||
if sum(categories) < 2:
|
||||
raise PasswordPolicyError("密码至少包含字母、数字、符号中的两类。")
|
||||
|
||||
def hash(self, password: str) -> str:
|
||||
self.validate(password)
|
||||
salt = secrets.token_bytes(self.salt_bytes)
|
||||
digest = hashlib.scrypt(
|
||||
password.encode("utf-8"),
|
||||
salt=salt,
|
||||
n=self.work_factor,
|
||||
r=self.block_size,
|
||||
p=self.parallelism,
|
||||
dklen=self.key_bytes,
|
||||
maxmem=64 * 1024 * 1024,
|
||||
)
|
||||
encoded_salt = base64.urlsafe_b64encode(salt).decode("ascii")
|
||||
encoded_digest = base64.urlsafe_b64encode(digest).decode("ascii")
|
||||
return (
|
||||
f"scrypt${self.work_factor}${self.block_size}${self.parallelism}"
|
||||
f"${encoded_salt}${encoded_digest}"
|
||||
)
|
||||
|
||||
def verify(self, password: str, encoded: str) -> bool:
|
||||
try:
|
||||
scheme, work_factor, block_size, parallelism, salt, expected = encoded.split("$")
|
||||
if scheme != "scrypt":
|
||||
return False
|
||||
digest = hashlib.scrypt(
|
||||
password.encode("utf-8"),
|
||||
salt=base64.urlsafe_b64decode(salt.encode("ascii")),
|
||||
n=int(work_factor),
|
||||
r=int(block_size),
|
||||
p=int(parallelism),
|
||||
dklen=self.key_bytes,
|
||||
maxmem=64 * 1024 * 1024,
|
||||
)
|
||||
return hmac.compare_digest(base64.urlsafe_b64encode(digest).decode("ascii"), expected)
|
||||
except (TypeError, ValueError):
|
||||
return False
|
||||
Reference in New Issue
Block a user