1import hashlib
2import hmac
3import json
4import os
5import urllib.error
6import urllib.request
7
8SALT = b"AS-HKDF-v1"
9INFO = b"analogseed-csprng-reseed"
10
11
12def hkdf_sha256(ikm: bytes, salt: bytes, info: bytes, length: int = 32) -> bytes:
13 if not salt:
14 salt = b"\x00" * 32
15 prk = hmac.new(salt, ikm, hashlib.sha256).digest()
16 okm = b""
17 previous = b""
18 counter = 1
19 while len(okm) < length:
20 previous = hmac.new(prk, previous + info + bytes([counter]), hashlib.sha256).digest()
21 okm += previous
22 counter += 1
23 return okm[:length]
24
25
26def reseed(physical: bytes) -> bytes:
27 local = os.urandom(32)
28 return hkdf_sha256(physical + local, SALT, INFO, 32)
29
30
31def random_bytes_or_local(base: str, key: str, n: int = 32) -> tuple[bytes, str]:
32 """Return (material, source). source is 'mixed' or 'os-csprng'."""
33 url = f"{base}/v1/random?bytes={n}&format=hex"
34 req = urllib.request.Request(url, headers={"Authorization": f"Bearer {key}"})
35 try:
36 with urllib.request.urlopen(req, timeout=10) as res:
37 body = json.load(res)
38 physical = bytes.fromhex(body["bytes"])
39 return reseed(physical), "mixed"
40 except urllib.error.HTTPError as exc:
41 if exc.code not in (429, 503):
42 raise
43 return os.urandom(32), "os-csprng"