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:
File diff suppressed because one or more lines are too long
@@ -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")
|
||||
@@ -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)
|
||||
Reference in New Issue
Block a user