@absoluteui/services/crypto
Web Crypto wrappers: digests, HMAC, AES-GCM, PBKDF2, and timing-safe comparison.
import { ... } from '@absoluteui/services/crypto';How to use
Section titled “How to use”Thin, typed wrappers over the browser’s Web Crypto API: digests, HMAC, AES-GCM encryption, password-based keys (PBKDF2), ECDSA signatures, and timing-safe comparison. Every function is async and accepts text (UTF-8) or bytes. Web Crypto needs a secure context (HTTPS or localhost); elsewhere every call rejects with SVC_CRYPTO_UNAVAILABLE, so check isWebCryptoAvailable() before offering features that depend on it.
How do I hash a value?
Section titled “How do I hash a value?”sha256Hex and digestHex return lowercase hex, ready to compare with a checksum published by a server. For files, fileChecksum in @absoluteui/services/file streams large inputs.
import { digestHex, sha256Hex } from '@absoluteui/services/crypto';
await sha256Hex('payroll-2026-09.csv'); // e.g. a 64-character hex string(await sha256Hex('abc')).length; // => 64await digestHex('SHA-1', 'abc'); // => 'a9993e364706816aba3e25717850c26c9cd0d89d'How do I sign or verify a webhook payload with HMAC?
Section titled “How do I sign or verify a webhook payload with HMAC?”Sign with hmac or hmacHex and verify with hmacVerify, which compares in constant time. Import the secret once with importHmacKey when you check many messages.
import { hexDecode } from '@absoluteui/helpers/encoding';import { hmacHex, hmacVerify, importHmacKey } from '@absoluteui/services/crypto';
const body = JSON.stringify({ event: 'employee.hired', id: 42 });const key = await importHmacKey('webhook-secret', 'SHA-256');const header = await hmacHex(key, body); // what the sender puts in its signature headerawait hmacVerify(key, body, hexDecode(header)); // => trueawait hmacVerify(key, body.replace('42', '43'), hexDecode(header)); // => falseNever compare signatures or tokens with ===; use hmacVerify or timingSafeEqual.
How do I encrypt data with a password?
Section titled “How do I encrypt data with a password?”Derive an AES-GCM key from the password and a random salt with deriveKeyPbkdf2 (600,000 iterations by default), then encrypt to a compact token. Store the salt with the token; it is not secret.
import { aesGcmDecryptString, aesGcmEncryptString, deriveKeyPbkdf2, randomSalt } from '@absoluteui/services/crypto';import { base64UrlDecode, base64UrlEncode } from '@absoluteui/helpers/encoding';
const salt = randomSalt();const key = await deriveKeyPbkdf2('correct horse battery staple', salt);const token = await aesGcmEncryptString(key, 'Salary review: 8% raise', { aad: 'employee:42' });const saved = { salt: base64UrlEncode(salt), token };
const again = await deriveKeyPbkdf2('correct horse battery staple', base64UrlDecode(saved.salt));await aesGcmDecryptString(again, saved.token, { aad: 'employee:42' }); // => 'Salary review: 8% raise'aad binds the ciphertext to a context (here the employee), so a token copied to another record fails to decrypt. For encrypted local storage, use createEncryptedStore in @absoluteui/services/storage.
How do I sign a document so others can verify it?
Section titled “How do I sign a document so others can verify it?”ECDSA keeps the private key in the browser (non-extractable by default) and hands out the public key.
import { ecdsaSign, ecdsaVerify, exportKey, generateEcdsaKeyPair } from '@absoluteui/services/crypto';
const { privateKey, publicKey } = await generateEcdsaKeyPair('P-256');const signature = await ecdsaSign(privateKey, 'payslip-2026-09-42.pdf');await ecdsaVerify(publicKey, signature, 'payslip-2026-09-42.pdf'); // => trueconst jwk = await exportKey(publicKey, 'jwk'); // send this to the verifierjwk.kty; // => 'EC'How do I read the expiry from a JWT?
Section titled “How do I read the expiry from a JWT?”jwtDecodeUnsafe decodes the header and payload without verifying anything. Use it only for display hints, such as warning before a session expires; the server must make every authorization decision.
import { jwtDecodeUnsafe } from '@absoluteui/services/crypto';
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImV4cCI6MTc5MDAwMDAwMH0.sig';jwtDecodeUnsafe(token)?.payload['exp']; // => 1790000000jwtDecodeUnsafe('not-a-jwt'); // => undefinedWhat goes wrong?
Section titled “What goes wrong?”- A wrong key, wrong
aad, or a tampered token rejects withSVC_CRYPTO_DECRYPT_FAILED; the error never says which, by design. - Keys are non-extractable unless you pass
{ extractable: true };exportKeyon such a key throwsSVC_CRYPTO_NOT_EXTRACTABLE. - SHA-1 is available for interoperability with old systems only; do not use it for new signatures.
import { aesGcmDecryptString, aesGcmEncryptString, exportKey, generateAesKey } from '@absoluteui/services/crypto';
const key = await generateAesKey();const token = await aesGcmEncryptString(key, 'Annual leave', { aad: 'employee:42' });await aesGcmDecryptString(key, token, { aad: 'employee:43' }); // => throws SVC_CRYPTO_DECRYPT_FAILEDawait exportKey(key); // => throws SVC_CRYPTO_NOT_EXTRACTABLEAPI reference
Section titled “API reference”Functions
Section titled “Functions”aesGcmDecrypt
Decrypts an AES-GCM payload (or packed iv || ciphertext bytes). Any failure, including tampering
or a wrong key or AAD, rejects with SVC_CRYPTO_DECRYPT_FAILED and no further detail.
function aesGcmDecrypt(key: CryptoKey, payload: AesGcmPayload | BufferSource, options?: AesGcmOptions): Promise<Uint8Array<ArrayBuffer>>;Example:
const key = await generateAesKey();const payload = await aesGcmEncrypt(key, 'secret', { aad: 'user:42' });const plain = await aesGcmDecrypt(key, payload, { aad: 'user:42' });new TextDecoder().decode(plain); // => 'secret'aesGcmDecryptString
Decrypts a token from {@link aesGcmEncryptString} back to text.
function aesGcmDecryptString(key: CryptoKey, token: string, options?: AesGcmOptions): Promise<string>;Example:
const key = await generateAesKey();const token = await aesGcmEncryptString(key, 'Annual leave');await aesGcmDecryptString(key, token); // => 'Annual leave'aesGcmEncrypt
Encrypts with AES-GCM using a fresh random 96-bit IV per call (never reuse an IV with the same key).
function aesGcmEncrypt(key: CryptoKey, plaintext: CryptoInput, options?: AesGcmOptions): Promise<AesGcmPayload>;Example:
const key = await generateAesKey();const { iv, ciphertext } = await aesGcmEncrypt(key, 'secret', { aad: 'user:42' });iv.length; // => 12aesGcmEncryptString
Encrypts text to a compact base64url token (iv || ciphertext).
function aesGcmEncryptString(key: CryptoKey, plaintext: string, options?: AesGcmOptions): Promise<string>;Example:
const key = await generateAesKey();const token = await aesGcmEncryptString(key, JSON.stringify({ reason: 'Annual leave' }));typeof token; // => 'string'deriveBitsPbkdf2
Derives raw bits from a password (for example to compare against a stored hash).
function deriveBitsPbkdf2(password: CryptoInput, salt: CryptoInput, options?: Pbkdf2Options & { readonly length?: number;}): Promise<Uint8Array<ArrayBuffer>>;Example:
import { hexEncode } from '@absoluteui/helpers/encoding';const bits = await deriveBitsPbkdf2('password', 'salt', { iterations: 1, hash: 'SHA-1', length: 160 });hexEncode(bits); // => '0c60c80f961f0e71f3a9b524af6012062fe037a6' // RFC 6070deriveKeyPbkdf2
Derives a non-extractable AES-GCM key from a password.
function deriveKeyPbkdf2(password: CryptoInput, salt: CryptoInput, options?: Pbkdf2Options & KeyOptions & { readonly length?: AesKeyLength;}): Promise<CryptoKey>;Example:
const salt = randomSalt(); // store it with the encrypted dataconst key = await deriveKeyPbkdf2('correct horse battery staple', salt);key.algorithm.name; // => 'AES-GCM'digest
Hashes data with a SHA algorithm.
function digest(algorithm: DigestAlgorithm, data: CryptoInput): Promise<Uint8Array<ArrayBuffer>>;Example:
const d = await digest('SHA-256', 'abc');d.length; // => 32digestHex
Hashes data and returns lowercase hex.
function digestHex(algorithm: DigestAlgorithm, data: CryptoInput): Promise<string>;Example:
await digestHex('SHA-1', 'abc'); // => 'a9993e364706816aba3e25717850c26c9cd0d89d'ecdsaSign
Signs with ECDSA (IEEE P1363 r || s signature format).
function ecdsaSign(privateKey: CryptoKey, data: CryptoInput, hash?: HashAlgorithm): Promise<Uint8Array<ArrayBuffer>>;Example:
const { privateKey } = await generateEcdsaKeyPair();const sig = await ecdsaSign(privateKey, 'payroll-2026-09.csv');sig.length; // => 64 // P-256 r||secdsaVerify
Verifies an ECDSA signature.
function ecdsaVerify(publicKey: CryptoKey, signature: BufferSource, data: CryptoInput, hash?: HashAlgorithm): Promise<boolean>;Example:
const { privateKey, publicKey } = await generateEcdsaKeyPair();const sig = await ecdsaSign(privateKey, 'payroll-2026-09.csv');await ecdsaVerify(publicKey, sig, 'payroll-2026-09.csv'); // => trueexportKey
Exports an extractable key as raw bytes, or as a JWK with format: 'jwk'.
function exportKey(key: CryptoKey, format?: 'raw'): Promise<Uint8Array<ArrayBuffer>>;function exportKey(key: CryptoKey, format: 'jwk'): Promise<JsonWebKey>;Example:
const key = await generateAesKey({ extractable: true }); // exportKey needs an extractable keyconst raw = await exportKey(key);raw.length; // => 32 // 256-bit keygenerateAesKey
Generates a random AES-GCM key (default 256-bit, non-extractable).
function generateAesKey(options?: KeyOptions & { readonly length?: AesKeyLength;}): Promise<CryptoKey>;Example:
const key = await generateAesKey();key.algorithm.name; // => 'AES-GCM'generateEcdsaKeyPair
Generates an ECDSA key pair (private key non-extractable by default; the public key is always extractable).
function generateEcdsaKeyPair(curve?: EcdsaCurve, options?: KeyOptions): Promise<CryptoKeyPair>;Example:
const { privateKey, publicKey } = await generateEcdsaKeyPair();publicKey.type; // => 'public'hmac
HMAC signature of data. key is a CryptoKey from {@link importHmacKey} or raw secret material.
function hmac(key: CryptoKey | CryptoInput, data: CryptoInput, hash?: HashAlgorithm): Promise<Uint8Array<ArrayBuffer>>;Example:
const mac = await hmac('Jefe', 'what do ya want for nothing?', 'SHA-256');mac.length; // => 32hmacHex
HMAC as lowercase hex.
function hmacHex(key: CryptoKey | CryptoInput, data: CryptoInput, hash?: HashAlgorithm): Promise<string>;Example:
await hmacHex('Jefe', 'what do ya want for nothing?'); // => '5bdcc146bf60754e6a042426089575c75a003f089d2739839dec58b964ec3843' // RFC 4231hmacVerify
Verifies an HMAC with crypto.subtle.verify (constant-time in the platform).
function hmacVerify(key: CryptoKey | CryptoInput, data: CryptoInput, signature: BufferSource, hash?: HashAlgorithm): Promise<boolean>;Example:
const body = '{"event":"leave.approved"}';const signature = await hmac('webhook-secret', body);await hmacVerify('webhook-secret', body, signature); // => trueawait hmacVerify('webhook-secret', body + ' ', signature); // => falseimportAesKey
Imports raw AES key bytes (16, 24 or 32) as a non-extractable AES-GCM key.
function importAesKey(raw: BufferSource, options?: KeyOptions): Promise<CryptoKey>;Example:
import { base64UrlDecode } from '@absoluteui/helpers/encoding';declare const stored: string; // base64url key saved earlierconst key = await importAesKey(base64UrlDecode(stored));importHmacKey
Imports a raw HMAC secret as a non-extractable CryptoKey (sign and verify).
function importHmacKey(secret: CryptoInput, hash?: HashAlgorithm, options?: KeyOptions): Promise<CryptoKey>;Example:
const key = await importHmacKey('webhook-secret', 'SHA-256');await hmacHex(key, 'payload') === await hmacHex('webhook-secret', 'payload'); // => trueisWebCryptoAvailable
True when crypto.subtle is usable (secure context in browsers).
function isWebCryptoAvailable(): boolean;Example:
const canEncrypt = isWebCryptoAvailable(); // false on plain HTTP pagesjwtDecodeUnsafe
Decodes a JWT’s header and payload WITHOUT verifying the signature, expiry or issuer.
Use only to read display hints (for example exp for a session-timeout warning) from a token
the server already trusts; never make authorization decisions on the result.
Returns undefined for malformed or oversized (> 16 KiB) tokens.
function jwtDecodeUnsafe(token: string): UnverifiedJwt | undefined;Example:
const token = 'eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI0MiIsImV4cCI6MTc5MDAwMDAwMH0.sig';jwtDecodeUnsafe(token)?.payload['exp']; // => 1790000000jwtDecodeUnsafe('not-a-jwt'); // => undefinedpackAesGcm
Packs a payload as iv || ciphertext.
function packAesGcm(payload: AesGcmPayload): Uint8Array<ArrayBuffer>;Example:
const key = await generateAesKey();const packed = packAesGcm(await aesGcmEncrypt(key, 'secret'));packed.length; // => 34 // 12-byte IV + 6 bytes + 16-byte tagrandomSalt
Cryptographically random salt (default 16 bytes).
function randomSalt(length?: number): Uint8Array<ArrayBuffer>;Example:
const salt = randomSalt();salt.length; // => 16sha256Hex
SHA-256 as lowercase hex.
function sha256Hex(data: CryptoInput): Promise<string>;Example:
await sha256Hex('abc') // => 'ba7816bf8f01cfea414140de5dae2223b00361a396177a9cb410ff61f20015ad'timingSafeEqual
Compares two values in time that depends only on the longer length, not on where they differ. Strings are compared as UTF-8 bytes. Prefer {@link hmacVerify} for MACs.
function timingSafeEqual(a: CryptoInput, b: CryptoInput): boolean;Example:
timingSafeEqual('csrf-token-1', 'csrf-token-1'); // => truetimingSafeEqual('csrf-token-1', 'csrf-token-2'); // => falseunpackAesGcm
Splits iv || ciphertext (throws SVC_CRYPTO_DECRYPT_FAILED when too short to hold an IV and tag).
function unpackAesGcm(packed: BufferSource): AesGcmPayload;Example:
const key = await generateAesKey();const packed = packAesGcm(await aesGcmEncrypt(key, 'secret'));const { iv, ciphertext } = unpackAesGcm(packed);ciphertext.byteLength; // => 22Constants
Section titled “Constants”AES_GCM_IV_BYTES
AES-GCM IV length in bytes (96 bits, the recommended size).
const AES_GCM_IV_BYTES = 12;PBKDF2_DEFAULT_ITERATIONS
OWASP (2023) minimum PBKDF2 iterations for SHA-256.
const PBKDF2_DEFAULT_ITERATIONS = 600000;Interfaces
Section titled “Interfaces”AesGcmOptions
Options for AES-GCM operations.
interface AesGcmOptions { readonly aad?: CryptoInput;}AesGcmPayload
AES-GCM output: the random IV and the ciphertext with its 128-bit tag appended.
interface AesGcmPayload { readonly iv: Uint8Array<ArrayBuffer>; readonly ciphertext: Uint8Array<ArrayBuffer>;}KeyOptions
Options for importing or generating keys.
interface KeyOptions { readonly extractable?: boolean;}Pbkdf2Options
PBKDF2 options.
interface Pbkdf2Options { readonly iterations?: number; readonly hash?: HashAlgorithm;}UnverifiedJwt
Decoded, UNVERIFIED JWT parts.
interface UnverifiedJwt { readonly header: Readonly<Record<string, unknown>>; readonly payload: Readonly<Record<string, unknown>>;}AesKeyLength
AES key sizes in bits.
type AesKeyLength = 128 | 192 | 256;CryptoInput
Text (encoded as UTF-8) or bytes.
type CryptoInput = string | BufferSource;DigestAlgorithm
SHA family digests supported by Web Crypto.
type DigestAlgorithm = 'SHA-1' | 'SHA-256' | 'SHA-384' | 'SHA-512';EcdsaCurve
NIST curves supported by Web Crypto ECDSA.
type EcdsaCurve = 'P-256' | 'P-384' | 'P-521';HashAlgorithm
Hashes allowed for HMAC, PBKDF2 and ECDSA. SHA-1 is kept only for interoperability.
type HashAlgorithm = DigestAlgorithm;