fix: harden cloud storage security

This commit is contained in:
237899745
2026-06-13 18:45:12 +08:00
parent 7943b04ee2
commit bb6ad01018
28 changed files with 2229 additions and 996 deletions

View File

@@ -16,6 +16,7 @@
*/
const crypto = require('crypto');
const ENCRYPTION_PREFIX = 'enc:v1:';
/**
* 从环境变量获取加密密钥
@@ -111,8 +112,8 @@ function encryptSecret(plaintext) {
authTag
]);
// 返回 Base64 编码结果
return combined.toString('base64');
// 返回带版本前缀的 Base64 编码结果,避免旧明文被误判为密文。
return ENCRYPTION_PREFIX + combined.toString('base64');
} catch (error) {
console.error('[加密] 加密失败:', error);
throw new Error('数据加密失败: ' + error.message);
@@ -134,24 +135,26 @@ function encryptSecret(plaintext) {
* // 输出: 'my-secret-key'
*/
function decryptSecret(ciphertext) {
// 如果是 null 或 undefined直接返回
if (!ciphertext) {
return ciphertext;
}
const rawValue = String(ciphertext);
const hasPrefix = rawValue.startsWith(ENCRYPTION_PREFIX);
const encodedValue = hasPrefix ? rawValue.slice(ENCRYPTION_PREFIX.length) : rawValue;
if (!hasPrefix && !isEncrypted(encodedValue)) {
if (/[+/=]/.test(rawValue)) {
throw new Error('数据解密失败: 疑似密文格式无效');
}
console.warn('[加密] 检测到未加密的旧密钥,建议重新保存以完成加密');
return rawValue;
}
try {
// 如果是 null 或 undefined直接返回
if (!ciphertext) {
return ciphertext;
}
// 检查是否为加密格式Base64
// 如果不是 Base64可能是旧数据明文直接返回
if (!/^[A-Za-z0-9+/=]+$/.test(ciphertext)) {
console.warn('[加密] 检测到未加密的密钥,建议重新加密');
return ciphertext;
}
// 获取加密密钥
const key = getEncryptionKey();
// 解析 Base64
const combined = Buffer.from(ciphertext, 'base64');
const combined = Buffer.from(encodedValue, 'base64');
// 提取各部分
// IV: 前 12 字节
@@ -175,13 +178,9 @@ function decryptSecret(ciphertext) {
return decrypted;
} catch (error) {
// 如果解密失败,可能是旧数据(明文),直接返回
console.error('[加密] 解密失败,可能是未加密的旧数据:', error.message);
// 在开发环境抛出错误,生产环境尝试返回原值
if (process.env.NODE_ENV === 'production') {
console.error('[加密] 生产环境中解密失败,返回原值(可能导致 OSS 连接失败)');
return ciphertext;
if (!hasPrefix && !/[+/=]/.test(rawValue)) {
console.warn('[加密] 旧格式数据解密失败,按未加密旧密钥处理:', error.message);
return rawValue;
}
throw new Error('数据解密失败: ' + error.message);
@@ -240,6 +239,10 @@ function isEncrypted(data) {
return false;
}
const encodedValue = data.startsWith(ENCRYPTION_PREFIX)
? data.slice(ENCRYPTION_PREFIX.length)
: data;
// 加密后的数据特征:
// 1. 是有效的 Base64
// 2. 长度至少为 (12 + 16) * 4/3 = 38 字符IV + authTag 的 Base64
@@ -247,7 +250,11 @@ function isEncrypted(data) {
try {
// 尝试解码 Base64
const buffer = Buffer.from(data, 'base64');
if (!/^[A-Za-z0-9+/=]+$/.test(encodedValue) || encodedValue.length % 4 !== 0) {
return false;
}
const buffer = Buffer.from(encodedValue, 'base64');
// 检查长度(至少包含 IV + authTag
// AES-GCM: 12字节IV + 至少1字节密文 + 16字节authTag = 29字节