import { createCipheriv, createDecipheriv, createHash, randomBytes } from "crypto"; const TOKEN_PREFIX = "chj_"; const TOKEN_ALGO = "aes-256-gcm"; const TOKEN_IV_BYTES = 12; function getTokenEncryptionKey(): Buffer { const secret = process.env.JWT_SECRET?.trim(); if (!secret) { throw new Error("JWT_SECRET 未配置,无法加密客户 API Key"); } return createHash("sha256").update(secret, "utf8").digest(); } export function hashServiceToken(rawToken: string): string { return createHash("sha256").update(rawToken, "utf8").digest("hex"); } export function generateServiceToken(): string { return `${TOKEN_PREFIX}${randomBytes(24).toString("base64url")}`; } export function tokenKeyPrefix(rawToken: string): string { return rawToken.slice(0, Math.min(12, rawToken.length)); } export function encryptServiceToken(rawToken: string): string { return encryptSecret(rawToken); } export function decryptServiceToken(ciphertext: string | null | undefined): string | null { return decryptSecret(ciphertext); } /** 通用 AES-GCM 密文(API Key、承运商密码等) */ export function encryptSecret(plaintext: string): string { const iv = randomBytes(TOKEN_IV_BYTES); const cipher = createCipheriv(TOKEN_ALGO, getTokenEncryptionKey(), iv); const encrypted = Buffer.concat([ cipher.update(plaintext, "utf8"), cipher.final(), ]); const tag = cipher.getAuthTag(); return Buffer.concat([iv, tag, encrypted]).toString("base64"); } export function decryptSecret(ciphertext: string | null | undefined): string | null { if (!ciphertext) { return null; } const payload = Buffer.from(ciphertext, "base64"); if (payload.length < TOKEN_IV_BYTES + 16 + 1) { return null; } const iv = payload.subarray(0, TOKEN_IV_BYTES); const tag = payload.subarray(TOKEN_IV_BYTES, TOKEN_IV_BYTES + 16); const encrypted = payload.subarray(TOKEN_IV_BYTES + 16); const decipher = createDecipheriv(TOKEN_ALGO, getTokenEncryptionKey(), iv); decipher.setAuthTag(tag); return Buffer.concat([decipher.update(encrypted), decipher.final()]).toString( "utf8", ); }