Template
1
0
Files
boilerplate/platform/vault/hmac.ts

52 lines
1.8 KiB
TypeScript

/**
* Hash a value with given secret.
*
* @param value - Value to hash.
* @param secret - Secret to hash the value against.
*/
export async function hash(value: string, secret: string): Promise<string> {
const key = await getImportKey(secret, ["sign"]);
const encoder = new TextEncoder();
const valueData = encoder.encode(value);
const signature = await crypto.subtle.sign("HMAC", key, valueData);
return bufferToHex(signature);
}
/**
* Verify that the given value results in the expected hash using the provided secret.
*
* @param value - Value to verify.
* @param expectedHash - Expected hash value.
* @param secret - Secret used to hash the value.
*/
export async function verify(value: string, expectedHash: string, secret: string): Promise<boolean> {
const key = await getImportKey(secret, ["verify"]);
const encoder = new TextEncoder();
const valueData = encoder.encode(value);
const signature = hexToBuffer(expectedHash);
return crypto.subtle.verify("HMAC", key, signature, valueData);
}
/*
|--------------------------------------------------------------------------------
| Utilities
|--------------------------------------------------------------------------------
*/
async function getImportKey(secret: string, usages: KeyUsage[]): Promise<CryptoKey> {
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
return crypto.subtle.importKey("raw", keyData, { name: "HMAC", hash: { name: "SHA-256" } }, false, usages);
}
function bufferToHex(buffer: ArrayBuffer): string {
return Array.from(new Uint8Array(buffer))
.map((byte) => byte.toString(16).padStart(2, "0"))
.join("");
}
function hexToBuffer(hex: string): ArrayBuffer {
const bytes = new Uint8Array(hex.match(/.{1,2}/g)!.map((byte) => parseInt(byte, 16)));
return bytes.buffer;
}