INITIAL COMMIT
This commit is contained in:
73
src/garmin_coach_clone/crypto.py
Normal file
73
src/garmin_coach_clone/crypto.py
Normal file
@ -0,0 +1,73 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import base64
|
||||
import hashlib
|
||||
import hmac
|
||||
import json
|
||||
import os
|
||||
import time
|
||||
from typing import Any
|
||||
|
||||
from cryptography.fernet import Fernet
|
||||
|
||||
|
||||
def _fernet_key(secret: str) -> bytes:
|
||||
digest = hashlib.sha256(secret.encode("utf-8")).digest()
|
||||
return base64.urlsafe_b64encode(digest)
|
||||
|
||||
|
||||
class Crypto:
|
||||
def __init__(self, secret: str) -> None:
|
||||
self._secret = secret.encode("utf-8")
|
||||
self._fernet = Fernet(_fernet_key(secret))
|
||||
|
||||
def encrypt(self, value: str) -> str:
|
||||
return self._fernet.encrypt(value.encode("utf-8")).decode("ascii")
|
||||
|
||||
def decrypt(self, value: str) -> str:
|
||||
return self._fernet.decrypt(value.encode("ascii")).decode("utf-8")
|
||||
|
||||
def hash_password(self, password: str) -> str:
|
||||
salt = os.urandom(16)
|
||||
digest = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 210_000)
|
||||
return "pbkdf2_sha256${}${}".format(
|
||||
base64.urlsafe_b64encode(salt).decode("ascii"),
|
||||
base64.urlsafe_b64encode(digest).decode("ascii"),
|
||||
)
|
||||
|
||||
def verify_password(self, password: str, encoded: str) -> bool:
|
||||
try:
|
||||
scheme, salt_s, digest_s = encoded.split("$", 2)
|
||||
except ValueError:
|
||||
return False
|
||||
if scheme != "pbkdf2_sha256":
|
||||
return False
|
||||
salt = base64.urlsafe_b64decode(salt_s.encode("ascii"))
|
||||
expected = base64.urlsafe_b64decode(digest_s.encode("ascii"))
|
||||
actual = hashlib.pbkdf2_hmac("sha256", password.encode("utf-8"), salt, 210_000)
|
||||
return hmac.compare_digest(expected, actual)
|
||||
|
||||
def sign_session(self, payload: dict[str, Any], ttl_seconds: int) -> str:
|
||||
body = dict(payload)
|
||||
body["exp"] = int(time.time()) + ttl_seconds
|
||||
encoded = base64.urlsafe_b64encode(
|
||||
json.dumps(body, separators=(",", ":"), sort_keys=True).encode("utf-8")
|
||||
).decode("ascii")
|
||||
sig = hmac.new(self._secret, encoded.encode("ascii"), hashlib.sha256).hexdigest()
|
||||
return f"{encoded}.{sig}"
|
||||
|
||||
def verify_session(self, token: str) -> dict[str, Any] | None:
|
||||
try:
|
||||
encoded, sig = token.split(".", 1)
|
||||
except ValueError:
|
||||
return None
|
||||
expected = hmac.new(self._secret, encoded.encode("ascii"), hashlib.sha256).hexdigest()
|
||||
if not hmac.compare_digest(expected, sig):
|
||||
return None
|
||||
try:
|
||||
payload = json.loads(base64.urlsafe_b64decode(encoded.encode("ascii")))
|
||||
except (ValueError, json.JSONDecodeError):
|
||||
return None
|
||||
if int(payload.get("exp", 0)) < int(time.time()):
|
||||
return None
|
||||
return payload if isinstance(payload, dict) else None
|
||||
Reference in New Issue
Block a user