/** * Relay 加密与安全工具 */ import crypto from 'crypto'; const RSA_KEY_SIZE = 2048; export interface KeyPair { publicKey: string; privateKey: string; } export function generateKeyPair(): KeyPair { const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', { modulusLength: RSA_KEY_SIZE, publicKeyEncoding: { type: 'spki', format: 'pem' }, privateKeyEncoding: { type: 'pkcs8', format: 'pem' }, }); return { publicKey, privateKey }; } export function encryptWithPublicKey(publicKeyPem: string, plaintext: string): string { const publicKey = crypto.createPublicKey(publicKeyPem); const aesKey = crypto.randomBytes(32); const iv = crypto.randomBytes(12); const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv); const ciphertext = Buffer.concat([ cipher.update(Buffer.from(plaintext, 'utf8')), cipher.final(), ]); const wrappedKey = crypto.publicEncrypt( { key: publicKey, oaepHash: 'sha256' }, aesKey, ); const envelope = { v: 2, alg: 'RSA-OAEP-256+A256GCM', key: wrappedKey.toString('base64'), iv: iv.toString('base64'), tag: cipher.getAuthTag().toString('base64'), ciphertext: ciphertext.toString('base64'), }; return `v2:${Buffer.from(JSON.stringify(envelope), 'utf8').toString('base64')}`; } export function hashApiSecret(secret: string): string { return crypto.createHmac('sha256', 'qiwei-relay-secret-salt').update(secret).digest('hex'); } export function generateApiCredentials(): { apiKey: string; apiSecret: string } { return { apiKey: 'qk_' + crypto.randomBytes(16).toString('hex'), apiSecret: crypto.randomBytes(32).toString('hex'), }; } export function generateRelaySecret(): string { return crypto.randomBytes(32).toString('hex'); } export function verifyHmacSignature(secret: string, rawBody: string, signature: string): boolean { try { const expected = crypto.createHmac('sha256', secret).update(rawBody, 'utf8').digest('hex'); return timingSafeEqual(expected, signature); } catch { return false; } } export function verifyBearerToken(secret: string, authHeader: string | undefined): boolean { if (!authHeader) return false; const match = authHeader.match(/^Bearer\s+(.+)$/i); const provided = (match ? match[1] : authHeader).trim(); if (!provided) return false; return timingSafeEqual(provided, secret); } function timingSafeEqual(a: string, b: string): boolean { if (a.length !== b.length) return false; try { return crypto.timingSafeEqual(Buffer.from(a), Buffer.from(b)); } catch { return false; } } export function sha256Hash(input: string): string { return crypto.createHash('sha256').update(input).digest('hex'); }