Files
WRNexusJS/packages/captcha/src/crypto.ts
T
2026-07-27 12:42:18 +05:30

57 lines
1.8 KiB
TypeScript

const encoder = new TextEncoder();
export function defaultRandomBytes(length: number): Uint8Array {
if (!Number.isInteger(length) || length < 1)
throw new RangeError("random byte length must be positive");
const bytes = new Uint8Array(length);
crypto.getRandomValues(bytes);
return bytes;
}
export function bytesToBase64Url(bytes: Uint8Array): string {
let binary = "";
for (const byte of bytes) binary += String.fromCharCode(byte);
return btoa(binary).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/g, "");
}
export function randomId(randomBytes = defaultRandomBytes, length = 24): string {
return bytesToBase64Url(randomBytes(length));
}
function bytesToHex(bytes: Uint8Array): string {
let out = "";
for (const byte of bytes) out += byte.toString(16).padStart(2, "0");
return out;
}
export async function sha256(value: string): Promise<string> {
const digest = await crypto.subtle.digest("SHA-256", encoder.encode(value));
return bytesToHex(new Uint8Array(digest));
}
export async function hmacSha256(secret: string, value: string): Promise<string> {
const key = await crypto.subtle.importKey(
"raw",
encoder.encode(secret),
{ name: "HMAC", hash: "SHA-256" },
false,
["sign"],
);
const signature = await crypto.subtle.sign("HMAC", key, encoder.encode(value));
return bytesToHex(new Uint8Array(signature));
}
export function constantTimeEqual(left: string, right: string): boolean {
if (left.length !== right.length) return false;
let difference = 0;
for (let index = 0; index < left.length; index++) {
difference |= left.charCodeAt(index) ^ right.charCodeAt(index);
}
return difference === 0;
}
export async function bindingHash(value: string | undefined): Promise<string | undefined> {
if (!value) return undefined;
return sha256(value);
}