@kirkelliott/kdfts
v2.0.1
Published
Quantum-seeded KDF — Argon2id with salt from ANU vacuum fluctuations
Readme
kdfts
Argon2id KDF with optional quantum-backed salt provenance. Salt is sourced from the ANU Quantum Random Number Generator (photon shot noise at a beam splitter) when available, with strict mode for deployments where that provenance is required. Falls back to crypto.getRandomValues() otherwise.
Standard CSPRNGs already satisfy OWASP's salt requirements. The ANU source adds auditable entropy provenance — useful for compliance workflows or systems that need to document their randomness chain, not a substitute for strong KDF parameters.
Install
npm install @kirkelliott/kdftsGet a free ANU API key at quantumnumbers.anu.edu.au:
export ANU_API_KEY=your-key-hereUsage
Derive and persist:
import { derive, formatCert } from '@kirkelliott/kdfts'
const { hash, certificate } = await derive('my-password', {
context: 'myapp:user42:session', // domain separation — optional but recommended
})
// Persist this single string — it contains everything needed to verify later
await db.users.update({ passwordHash: hash })
console.log(formatCert(certificate))
// ════════════════════════════════════════════════════
// kdfts Key Certificate
// ════════════════════════════════════════════════════
// Source: ANU Quantum Vacuum (photon shot noise)
// Timestamp: 2026-03-07T04:00:00.000Z
// KDF: argon2id
// t / m / p: 3 / 65536 / 4
// Key length: 32 bytes (256 bits)
// Salt bytes: 32 bytes (256 bits)
// Context: myapp:user42:session
//
// Salt hash: 7bcaf7d45ef1191f9d72556b5f7f6e17...
// Key hash: f0d6b878bb864d5261d5ccd05353bfc1...
// ════════════════════════════════════════════════════Verify later:
import { verify } from '@kirkelliott/kdfts'
const valid = await verify('my-password', storedHash)
// Parameters, salt, and context are all read from the hash string.Reuse a source across multiple derivations (saves one ANU round-trip):
import { QuantumSource, derive } from '@kirkelliott/kdfts'
const source = await QuantumSource.create()
console.log(source.source) // 'anu' | 'crypto'
const [keyA, keyB] = await Promise.all([
derive(passwordA, { source }),
derive(passwordB, { source }),
])Options
| Option | Type | Default | Description |
|---|---|---|---|
| context | string | — | Domain separation string, passed as Argon2id associatedData. Embedded in the hash — no need to pass to verify(). |
| saltBytes | number | 32 | Bytes of quantum entropy to fetch. |
| keyLength | number | 32 | Output key length in bytes. |
| cost | { timeCost, memoryCost, parallelism } | { timeCost: 3, memoryCost: 65536, parallelism: 4 } | Argon2id parameters. memoryCost is in KiB. Embedded in the hash — no need to track separately. |
| strict | boolean | false | Throw if the ANU source is unavailable instead of falling back to crypto.getRandomValues(). |
| source | QuantumSource | — | Pre-created source for reuse or testing. |
Security notes
- Salt is fetched fresh for every
derivecall — never reused - Context is passed as Argon2id
associatedDataand embedded in the hash —verify()reads it automatically - Verification uses
crypto.timingSafeEqual— no timing oracle on key comparison - Argon2id defaults (
timeCost=3, memoryCost=65536, parallelism=4) exceed OWASP minimum recommendations - The certificate records only SHA-256 hashes of the salt and key, never the raw values
- Every claim in this README is validated by the test suite
License
MIT © Kirk Elliott
