|
|
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",
|
|
|
);
|
|
|
}
|