/**
* Crypto utilities for Cloudflare Workers
* Uses Web Crypto API instead of Node.js crypto
*/
/**
* Encrypt data using AES-256-GCM
*/
export async function encrypt(plaintext: string, keyBase64: string): Promise<Uint8Array> {
const key = await importKey(keyBase64);
const iv = crypto.getRandomValues(new Uint8Array(12));
const encoder = new TextEncoder();
const data = encoder.encode(plaintext);
const ciphertext = await crypto.subtle.encrypt(
{ name: 'AES-GCM', iv },
key,
data
);
// Combine IV + ciphertext
const result = new Uint8Array(iv.length + ciphertext.byteLength);
result.set(iv, 0);
result.set(new Uint8Array(ciphertext), iv.length);
return result;
}
/**
* Decrypt data using AES-256-GCM
*/
export async function decrypt(encrypted: Uint8Array, keyBase64: string): Promise<string> {
const key = await importKey(keyBase64);
// Extract IV (first 12 bytes) and ciphertext
const iv = encrypted.slice(0, 12);
const ciphertext = encrypted.slice(12);
const plaintext = await crypto.subtle.decrypt(
{ name: 'AES-GCM', iv },
key,
ciphertext
);
const decoder = new TextDecoder();
return decoder.decode(plaintext);
}
/**
* Import a base64-encoded key for AES-256-GCM
*/
async function importKey(keyBase64: string): Promise<CryptoKey> {
const keyBytes = Uint8Array.from(atob(keyBase64), c => c.charCodeAt(0));
return crypto.subtle.importKey(
'raw',
keyBytes,
{ name: 'AES-GCM' },
false,
['encrypt', 'decrypt']
);
}
/**
* Generate a SHA-256 hash
*/
export async function sha256(data: string): Promise<string> {
const encoder = new TextEncoder();
const bytes = encoder.encode(data);
const hash = await crypto.subtle.digest('SHA-256', bytes);
return Array.from(new Uint8Array(hash))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
/**
* Generate HMAC-SHA256
*/
export async function hmacSha256(data: string, secret: string): Promise<string> {
const encoder = new TextEncoder();
const keyData = encoder.encode(secret);
const dataBytes = encoder.encode(data);
const key = await crypto.subtle.importKey(
'raw',
keyData,
{ name: 'HMAC', hash: 'SHA-256' },
false,
['sign']
);
const signature = await crypto.subtle.sign('HMAC', key, dataBytes);
return Array.from(new Uint8Array(signature))
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
/**
* Generate a random token
*/
export function generateToken(length: number = 32): string {
const bytes = crypto.getRandomValues(new Uint8Array(length));
return Array.from(bytes)
.map(b => b.toString(16).padStart(2, '0'))
.join('');
}
/**
* Mask an API key for display (show first 4 and last 4 chars)
*/
export function maskApiKey(key: string): string {
if (key.length <= 12) {
return '*'.repeat(key.length);
}
return `${key.substring(0, 4)}${'*'.repeat(key.length - 8)}${key.substring(key.length - 4)}`;
}