You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

64 lines
2.1 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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