Credential Hash
Derive a deterministic, verifiable hash for credential lookup (API-key secret, shared bearer token, webhook signing key) without storing the credential itself. The default is an Argon2id-style fingerprint over a SHAKE256 MAC — same chassis the password primitive uses, but tuned for high-entropy machine-generated secrets where memory-hard work is unnecessary.
Rows persist a base64 envelope:
byte 0: 0xC1 (CREDENTIAL_MAGIC) byte 1: algorithm ID (0x01 SHAKE256 | 0x02 Argon2id) bytes 2-N: algorithm-specific payload
verify dispatches on the algorithm byte so old rows remain verifiable regardless of what ACTIVE.CRED_HASH is today. When a new algorithm becomes the framework default, existing rows surface via needsRehash() and the next successful verify rotates them transparently — same pattern as b.auth.password.needsRehash.
Active algorithm: SHAKE256 (0x01). Suitable for high-entropy random secrets (>= 128 bits) — verify is microseconds, brute force is infeasible at the entropy level the framework generates. SHAKE256 is an XOF: the envelope payload length drives the digest size, so a future operator can request a 96-byte (or 32-byte) digest with no algorithm rotation. Operators with low-entropy or operator-supplied secrets pin Argon2id per-registry via { algo: "argon2id" }.
Validation tiers: - hash() opts and secret shape — throw at call site (config-time) - verify() malformed envelope or unknown algo ID — return false - inspect() malformed envelope — return null
b.credentialHash.hash(secret, opts?) #
{
algo: "shake256" | "argon2id",
params: {
length: number, // SHAKE256 output bytes (default 128)
... // Argon2id m / t / p forwarded to b.auth.password
},
}
Hash a credential secret into a base64 envelope ready for storage in a credentialHash column. Default algorithm is SHAKE256 with a 128-byte output; pass { algo: "argon2id" } for low-entropy or operator-supplied secrets. Throws on a non-string-or-Buffer secret, an unknown algorithm, a non-object params, or a SHAKE256 length below the 16-byte (128-bit) collision-space floor.
var token = b.crypto.generateToken(); // 32 random bytes, base64url
var env = await b.credentialHash.hash(token);
// → "wQE..." (base64 envelope)
// Operator-supplied (low-entropy) secret pins Argon2id:
var humanEnv = await b.credentialHash.hash("partner-shared-key", { algo: "argon2id" });
// → "wQI..." (base64 envelope, algo byte 0x02)
b.credentialHash.verify(secret, envelope) #
Constant-time check that secret matches the stored envelope. Tolerant read: malformed envelope / unknown algorithm / payload shorter than 16 bytes returns false without throwing, so callers write a single if (!await verify(...)) branch without try/catch ceremony. Emits credentialHash.verify observability events with outcome + reason for SIEM dashboards.
var ok = await b.credentialHash.verify(presented, row.credentialHash);
if (!ok) {
res.statusCode = 401;
return res.end();
}
// → true / false
b.credentialHash.inspect(envelope) #
Decode the envelope's algorithm byte and payload length without verifying the secret. Returns null for any malformed envelope (missing magic byte, unknown algorithm, truncated). Used by operator dashboards to count rows-by-algorithm during a rotation window.
var info = b.credentialHash.inspect(row.credentialHash);
if (info && info.algoName === "shake256" && info.payloadBytes < 64) {
console.warn("legacy SHAKE256 row, will be rotated on next verify");
}
// → { algoId: 0x01, algoName: "shake256", payloadBytes: 128 }
b.credentialHash.needsRehash(envelope, opts?) #
{
algo: "shake256" | "argon2id", // pin the comparison target
params: object, // Argon2id m / t / p targets
}
Returns true when the stored envelope was produced under an algorithm or parameter set that no longer matches the framework default. Operators wrap a successful verify with this and re-issue the credential transparently — same shape as b.auth.password. Argon2id rows defer the parameter-lag check to the password primitive's own needsRehash so the threshold lives in one place.
if (await b.credentialHash.verify(secret, row.credentialHash)) {
if (b.credentialHash.needsRehash(row.credentialHash)) {
var fresh = await b.credentialHash.hash(secret);
db.from("apiKeys").where({ _id: row._id }).update({ credentialHash: fresh });
}
}
// → true / false
Last updated 2026-08-08T16:39:49.652Z by seeder.