HKDF Key Derivation With Web Crypto
Derive AES-GCM keys or raw bytes from a shared secret with HKDF-SHA-256, an explicit salt and context-specific info strings.
Code
function assertSecret(secret: string): void {
if (typeof secret !== "string" || secret.length === 0) {
throw new TypeError("secret must be a non-empty string");
}
}
export async function deriveAesKey(
secret: string,
salt: Uint8Array,
info: string,
): Promise<CryptoKey> {
assertSecret(secret);
if (!(salt instanceof Uint8Array)) throw new TypeError("salt must be a Uint8Array");
if (typeof info !== "string") throw new TypeError("info must be a string");
const material = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(secret),
"HKDF",
false,
["deriveKey"],
);
return crypto.subtle.deriveKey(
{ name: "HKDF", hash: "SHA-256", salt, info: new TextEncoder().encode(info) },
material,
{ name: "AES-GCM", length: 256 },
false,
["encrypt", "decrypt"],
);
}
export async function deriveBytes(
secret: string,
salt: Uint8Array,
info: string,
length = 32,
): Promise<Uint8Array> {
assertSecret(secret);
if (!(salt instanceof Uint8Array)) throw new TypeError("salt must be a Uint8Array");
if (typeof info !== "string") throw new TypeError("info must be a string");
if (!Number.isInteger(length) || length < 1 || length > 255) {
throw new RangeError("length must be an integer between 1 and 255");
}
const material = await crypto.subtle.importKey(
"raw",
new TextEncoder().encode(secret),
"HKDF",
false,
["deriveBits"],
);
const bits = await crypto.subtle.deriveBits(
{ name: "HKDF", hash: "SHA-256", salt, info: new TextEncoder().encode(info) },
material,
length * 8,
);
return new Uint8Array(bits);
}Report a problem with this exampleOpens a pre-filled issue on GitHub.