Download code/yahbible_auth.py from OhBeOneKeyNoBe/YahBible: direct link, hf CLI and curl.
- Browser
- Download file 5.21 kB
-
https://huggingface.co/OhBeOneKeyNoBe/YahBible/resolve/main/code/yahbible_auth.py
- Command line
-
hf download hf://OhBeOneKeyNoBe/YahBible/code/yahbible_auth.py
-
curl -L -o yahbible_auth.py https://huggingface.co/OhBeOneKeyNoBe/YahBible/resolve/main/code/yahbible_auth.py
5.21 kB
| """YahBible split-key auth -- sign in without Supabase. | |
| Model (Elan'iel's design): the PC is the authoritative SOURCE; Hugging Face and a | |
| GitHub repo each hold ONE half of an encrypted credential, so login still works when | |
| the laptop is offline. The password is the third factor and is never stored or sent in | |
| the clear. | |
| signup(user, password) -> | |
| account Ed25519 key (the user's Otaviel identity) + profile are AES-GCM encrypted | |
| under K = PBKDF2-HMAC-SHA256(password, salt, 600000). The ciphertext is split | |
| 2-of-2 by a one-time pad: | |
| half_HF = random pad (published to Hugging Face) | |
| half_GH = ciphertext XOR pad (published to the GitHub repo) | |
| Neither half alone reveals anything (OTP); BOTH are required to reconstruct the | |
| ciphertext, and the PASSWORD is still required to decrypt it. | |
| A verifier = HMAC(K, "yahbible-verify") lets a wrong password fail fast without | |
| touching the account key. | |
| login(password, hf_record, gh_record) -> | |
| reconstruct ciphertext = half_HF XOR half_GH, derive K, check the verifier, | |
| decrypt -> the account key + profile. No PC and no Supabase required. | |
| Cross-compatible with the browser: PBKDF2-HMAC-SHA256 + AES-GCM are both WebCrypto | |
| primitives, so shim/auth.js can run the identical scheme client-side. | |
| """ | |
| from __future__ import annotations | |
| import hashlib | |
| import hmac | |
| import json | |
| import os | |
| from cryptography.hazmat.primitives.ciphers.aead import AESGCM | |
| from cryptography.hazmat.primitives.asymmetric.ed25519 import Ed25519PrivateKey | |
| PBKDF2_ITERS = 600_000 | |
| KDF_HASH = "sha256" | |
| def _kdf(password: str, salt: bytes) -> bytes: | |
| return hashlib.pbkdf2_hmac(KDF_HASH, password.encode("utf-8"), salt, PBKDF2_ITERS, 32) | |
| def _verifier(K: bytes) -> str: | |
| return hmac.new(K, b"yahbible-verify", hashlib.sha256).hexdigest() | |
| def _xor(a: bytes, b: bytes) -> bytes: | |
| return bytes(x ^ y for x, y in zip(a, b)) | |
| def _uhash(user: str) -> str: | |
| """Public, stable record id -- the username is not stored in the clear at rest.""" | |
| return hashlib.sha256(("yahbible:user:" + user.strip().lower()).encode()).hexdigest()[:32] | |
| def signup(user: str, password: str, profile: dict | None = None) -> dict: | |
| """Create the split credential. Returns {hf, gh} records to publish to each host.""" | |
| salt = os.urandom(16) | |
| K = _kdf(password, salt) | |
| sk = Ed25519PrivateKey.generate() | |
| account = {"priv": sk.private_bytes_raw().hex(), | |
| "pub": sk.public_key().public_bytes_raw().hex(), | |
| "user": user, "profile": profile or {}} | |
| plaintext = json.dumps(account, separators=(",", ":")).encode() | |
| nonce = os.urandom(12) | |
| ct = nonce + AESGCM(K).encrypt(nonce, plaintext, _uhash(user).encode()) | |
| pad = os.urandom(len(ct)) # one-time pad -> 2-of-2 split | |
| half_hf, half_gh = pad, _xor(ct, pad) | |
| uh = _uhash(user) | |
| return { | |
| "uhash": uh, | |
| "pub": account["pub"], | |
| # Hugging Face: login info + salt + verifier + HF half (readable when PC is offline) | |
| "hf": {"uhash": uh, "salt": salt.hex(), "verifier": _verifier(K), | |
| "pub": account["pub"], "half": half_hf.hex(), "v": 1}, | |
| # GitHub: the encrypted other half (kept in the passwords repo) | |
| "gh": {"uhash": uh, "half": half_gh.hex(), "v": 1}, | |
| } | |
| def login(password: str, hf_record: dict, gh_record: dict) -> dict | None: | |
| """Reconstruct + decrypt the credential from the two halves. None on any failure.""" | |
| try: | |
| if not hf_record or not gh_record: | |
| return None # need BOTH halves (2-of-2) | |
| salt = bytes.fromhex(hf_record["salt"]) | |
| K = _kdf(password, salt) | |
| if not hmac.compare_digest(_verifier(K), hf_record.get("verifier", "")): | |
| return None # wrong password -> fail fast | |
| ct = _xor(bytes.fromhex(hf_record["half"]), bytes.fromhex(gh_record["half"])) | |
| nonce, body = ct[:12], ct[12:] | |
| pt = AESGCM(K).decrypt(nonce, body, hf_record["uhash"].encode()) | |
| acc = json.loads(pt) | |
| return {"user": acc["user"], "pub": acc["pub"], "priv": acc["priv"], | |
| "profile": acc.get("profile", {})} | |
| except Exception: | |
| return None | |
| def change_password(old_pw: str, new_pw: str, hf_record: dict, gh_record: dict) -> dict | None: | |
| """Re-wrap the same account key under a new password (keeps the identity).""" | |
| acc = login(old_pw, hf_record, gh_record) | |
| if not acc: | |
| return None | |
| salt = os.urandom(16) | |
| K = _kdf(new_pw, salt) | |
| plaintext = json.dumps( | |
| {"priv": acc["priv"], "pub": acc["pub"], "user": acc["user"], "profile": acc["profile"]}, | |
| separators=(",", ":")).encode() | |
| nonce = os.urandom(12) | |
| ct = nonce + AESGCM(K).encrypt(nonce, plaintext, hf_record["uhash"].encode()) | |
| pad = os.urandom(len(ct)) | |
| return { | |
| "uhash": hf_record["uhash"], "pub": acc["pub"], | |
| "hf": {"uhash": hf_record["uhash"], "salt": salt.hex(), "verifier": _verifier(K), | |
| "pub": acc["pub"], "half": pad.hex(), "v": 1}, | |
| "gh": {"uhash": hf_record["uhash"], "half": _xor(ct, pad).hex(), "v": 1}, | |
| } | |