Files
atlas-seller-kit/apps/api/src/stores/store-secret.crypto.ts
T
2026-09-15 18:56:13 +08:00

60 lines
1.8 KiB
TypeScript
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
import {
createDecipheriv,
createCipheriv,
createHash,
createHmac,
randomBytes,
} from 'node:crypto';
export type EncryptedStoreSecret = {
ciphertext: Buffer;
nonce: Buffer;
fingerprint: string;
};
/**
* 密钥只在此边界短暂以明文存在。数据库保存 AES-GCM 密文和随机 nonce,
* fingerprint 是不可逆 HMAC,仅用于安全地识别同一份密钥。
*/
export function encryptStoreSecret(
plaintext: string,
keyMaterial: string,
): EncryptedStoreSecret {
const key = createHash('sha256').update(keyMaterial).digest();
const nonce = randomBytes(12);
const cipher = createCipheriv('aes-256-gcm', key, nonce);
const ciphertext = Buffer.concat([
cipher.update(plaintext, 'utf8'),
cipher.final(),
cipher.getAuthTag(),
]);
return {
ciphertext,
nonce,
fingerprint: createHmac('sha256', key)
.update(plaintext)
.digest('base64url'),
};
}
/** 仅供受信任的后台 Worker 使用;调用方不得记录返回值。 */
export function decryptStoreSecret(
ciphertextWithTag: Buffer,
nonce: Buffer,
keyMaterial: string,
): string {
const authenticationTagLength = 16;
if (ciphertextWithTag.length <= authenticationTagLength) {
throw new Error('Invalid encrypted store secret');
}
const key = createHash('sha256').update(keyMaterial).digest();
const ciphertext = ciphertextWithTag.subarray(0, -authenticationTagLength);
const tag = ciphertextWithTag.subarray(-authenticationTagLength);
const decipher = createDecipheriv('aes-256-gcm', key, nonce);
decipher.setAuthTag(tag);
return Buffer.concat([
decipher.update(ciphertext),
decipher.final(),
]).toString('utf8');
}