mirror of
https://github.com/ReCloudStudio/WebHooker.git
synced 2026-09-22 16:11:29 +00:00
36 lines
1.1 KiB
TypeScript
36 lines
1.1 KiB
TypeScript
const keyCache = new Map<string, CryptoKey>();
|
|
|
|
async function getHmacKey(secret: string): Promise<CryptoKey> {
|
|
const cached = keyCache.get(secret);
|
|
if (cached) return cached;
|
|
const key = await crypto.subtle.importKey(
|
|
"raw",
|
|
new TextEncoder().encode(secret),
|
|
{ name: "HMAC", hash: "SHA-256" },
|
|
false,
|
|
["sign"],
|
|
);
|
|
keyCache.set(secret, key);
|
|
return key;
|
|
}
|
|
|
|
export async function hmacSha256Hex(secret: string, payload: string): Promise<string> {
|
|
const key = await getHmacKey(secret);
|
|
const sig = await crypto.subtle.sign("HMAC", key, new TextEncoder().encode(payload));
|
|
return Array.from(new Uint8Array(sig))
|
|
.map((b) => b.toString(16).padStart(2, "0"))
|
|
.join("");
|
|
}
|
|
|
|
/** Constant-time string comparison. */
|
|
export function timingSafeEqual(a: string, b: string): boolean {
|
|
const encoder = new TextEncoder();
|
|
const x = encoder.encode(a);
|
|
const y = encoder.encode(b);
|
|
if (x.byteLength !== y.byteLength) return false;
|
|
let diff = 0;
|
|
for (let i = 0; i < x.byteLength; i++) {
|
|
diff |= x[i]! ^ y[i]!;
|
|
}
|
|
return diff === 0;
|
|
}
|