参考 novavis-authority 的加解密方案,实现前后端通信的应用层加密: - 重写 src/lib/crypto.ts 使用 Web Crypto API(浏览器兼容),PBKDF2+AES-256-GCM - 新增 src/lib/crypto-server.ts 服务端加解密工具(Node.js crypto) - 新增 src/lib/api-crypto.ts API 路由中间件 withCrypto(),自动解密请求体/加密响应体 - 更新 src/lib/admin-api.ts 自动加密所有请求/解密响应 - 所有 11 个 admin API 路由文件已应用 withCrypto 包装器 - 更新 .env 文件,添加 NEXT_PUBLIC_ENCRYPTION_SECRET 和 ENCRYPTION_SECRET
88 lines
2.3 KiB
TypeScript
88 lines
2.3 KiB
TypeScript
/**
|
||
* 服务端 AES-256-GCM 加解密工具(Node.js 环境)
|
||
*
|
||
* 使用 PBKDF2-HMAC-SHA256 从 ENCRYPTION_SECRET 派生密钥,
|
||
* 与 crypto.ts 共享相同的算法和参数,实现前后端加解密互通。
|
||
*
|
||
* 供 Next.js API Route 在服务端解密请求体、加密响应体。
|
||
*/
|
||
|
||
import crypto from 'node:crypto';
|
||
|
||
const ALGORITHM = 'aes-256-gcm';
|
||
const IV_LENGTH = 12;
|
||
const AUTH_TAG_LENGTH = 16;
|
||
const PBKDF2_ITERATIONS = 100_000;
|
||
const PBKDF2_SALT = 'novalon-website-crypto-salt-v1';
|
||
const KEY_LENGTH = 32; // 256 bits
|
||
|
||
function getSecret(): string {
|
||
const secret = process.env.ENCRYPTION_SECRET;
|
||
if (!secret) {
|
||
throw new Error('ENCRYPTION_SECRET 未配置,服务端加解密无法初始化');
|
||
}
|
||
return secret;
|
||
}
|
||
|
||
let cachedKey: Buffer | null = null;
|
||
|
||
function deriveKey(): Buffer {
|
||
if (cachedKey) return cachedKey;
|
||
const secret = getSecret();
|
||
cachedKey = crypto.pbkdf2Sync(
|
||
secret,
|
||
PBKDF2_SALT,
|
||
PBKDF2_ITERATIONS,
|
||
KEY_LENGTH,
|
||
'sha256',
|
||
);
|
||
return cachedKey;
|
||
}
|
||
|
||
/**
|
||
* 加密明文,返回 Base64 编码的密文(12-byte-IV || AES-GCM-ciphertext)
|
||
*/
|
||
export function encrypt(plaintext: string): string {
|
||
const key = deriveKey();
|
||
const iv = crypto.randomBytes(IV_LENGTH);
|
||
const cipher = crypto.createCipheriv(ALGORITHM, key, iv);
|
||
|
||
const encrypted = Buffer.concat([
|
||
cipher.update(plaintext, 'utf8'),
|
||
cipher.final(),
|
||
]);
|
||
const authTag = cipher.getAuthTag();
|
||
|
||
// 格式: iv + authTag + encrypted
|
||
const combined = Buffer.concat([iv, authTag, encrypted]);
|
||
return combined.toString('base64');
|
||
}
|
||
|
||
/**
|
||
* 解密 Base64 编码的密文,返回明文字符串
|
||
*/
|
||
export function decrypt(encryptedBase64: string): string {
|
||
const key = deriveKey();
|
||
const combined = Buffer.from(encryptedBase64, 'base64');
|
||
|
||
const iv = combined.subarray(0, IV_LENGTH);
|
||
const authTag = combined.subarray(IV_LENGTH, IV_LENGTH + AUTH_TAG_LENGTH);
|
||
const encrypted = combined.subarray(IV_LENGTH + AUTH_TAG_LENGTH);
|
||
|
||
const decipher = crypto.createDecipheriv(ALGORITHM, key, iv);
|
||
decipher.setAuthTag(authTag);
|
||
|
||
const decrypted = Buffer.concat([
|
||
decipher.update(encrypted),
|
||
decipher.final(),
|
||
]);
|
||
|
||
return decrypted.toString('utf8');
|
||
}
|
||
|
||
/**
|
||
* 验证加密配置是否可用
|
||
*/
|
||
export function isEncryptionAvailable(): boolean {
|
||
return !!process.env.ENCRYPTION_SECRET;
|
||
} |