Add kdf module: Key Derivation Function API (JEP 510, final in JDK 25), HKDF, RFC 5869 vectors, interop with Python cryptography and openssl, per-tenant keys

Co-Authored-By: Claude Sonnet 5.5 <[email protected]>
Claude-Session: https://claude.ai/code/session_017ic4b7rnC7jppGGdHGESvy
This commit is contained in:
2026-10-28 18:07:57 +00:00
co-authored by Claude Sonnet 5.5
parent c93a1fffbb
commit eca73ad544
21 changed files with 995 additions and 0 deletions
File diff suppressed because one or more lines are too long
+27
View File
@@ -0,0 +1,27 @@
#!/usr/bin/env python3
"""Generate interop/cases.tsv: random HKDF inputs plus the expected OKM from Python's `cryptography` package
(a thin wrapper over OpenSSL's HKDF). Deterministic: fixed seed, so the file is byte-stable.
Columns: hash <TAB> ikm <TAB> salt <TAB> info <TAB> length <TAB> okm (hex; an empty field is an empty value)"""
import random, sys
from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.kdf.hkdf import HKDF
HASHES = {"SHA256": hashes.SHA256, "SHA384": hashes.SHA384, "SHA512": hashes.SHA512}
SIZE = {"SHA256": 32, "SHA384": 48, "SHA512": 64}
rnd = random.Random(5869)
def rb(n): return bytes(rnd.getrandbits(8) for _ in range(n))
rows = []
for h in HASHES:
for i in range(100):
ikm = rb(rnd.choice([1, 16, 22, 32, 64, 100]))
salt = b"" if i % 5 == 0 else rb(rnd.choice([1, 13, 32, 80]))
info = b"" if i % 7 == 0 else rb(rnd.choice([1, 10, 40, 80]))
length = rnd.choice([1, 16, 32, 42, SIZE[h], SIZE[h] + 1, 82, 255 * SIZE[h]]) if i % 10 else 255 * SIZE[h]
okm = HKDF(algorithm=HASHES[h](), length=length, salt=salt or None, info=info).derive(ikm)
rows.append((h, ikm.hex(), salt.hex(), info.hex(), str(length), okm.hex()))
with open(sys.argv[1], "w") as f:
for r in rows: f.write("\t".join(r) + "\n")
print(f"wrote {len(rows)} cases")
+28
View File
@@ -0,0 +1,28 @@
#!/usr/bin/env python3
"""Third opinion: ask the `openssl kdf` command line tool (no Python crypto, no Java) for the RFC 5869 cases
and for the first rows of cases.tsv, and compare with the expected OKM."""
import subprocess, sys, re
def openssl_hkdf(digest, ikm, salt, info, length):
cmd = ["openssl", "kdf", "-keylen", str(length), "-kdfopt", f"digest:{digest}", "-kdfopt", f"hexkey:{ikm}",
"-kdfopt", f"hexinfo:{info}", "-kdfopt", "mode:EXTRACT_AND_EXPAND"]
if salt: cmd += ["-kdfopt", f"hexsalt:{salt}"]
cmd += ["HKDF"]
out = subprocess.run(cmd, capture_output=True, text=True, check=True).stdout.strip()
return out.replace(":", "").lower()
print(subprocess.run(["openssl", "version"], capture_output=True, text=True).stdout.strip())
ok = bad = 0
rfc = [("SHA256", "0b"*22, "000102030405060708090a0b0c", "f0f1f2f3f4f5f6f7f8f9", 42,
"3cb25f25faacd57a90434f64d0362f2a2d2d0a90cf1a5a4c5db02d56ecc4c5bf34007208d5b887185865"),
("SHA256", "0b"*22, "", "", 42,
"8da4e775a563c18f715f802a063c5a31b8a11f5c5ee1879ec3454e5f3c738d2d9d201395faa4b61a96c8")]
rows = [tuple(l.rstrip("\n").split("\t")) for l in open(sys.argv[1])]
rows = [(h, i, s, n, int(L), o) for h, i, s, n, L, o in rows]
for h, i, s, n, L, o in rfc + rows[::10]:
got = openssl_hkdf(h, i, s, n, L)
if got == o: ok += 1
else:
bad += 1; print("MISMATCH", h, i[:16], L)
print(f"openssl kdf HKDF: {ok} of {ok+bad} cases match ({len(rfc)} RFC 5869 + every 10th row of cases.tsv)")
sys.exit(1 if bad else 0)