YahBible / code /yahbible_auth.py
OhBeOneKeyNoBe's picture
BlackBox split-key: yahbible_auth.py
6933cf5 verified
Raw History Blame Contribute Delete
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},
}