This commit is contained in:
“xHuPo” 2025-06-09 13:35:15 +08:00
commit 2b8870a40e
51 changed files with 5845 additions and 0 deletions

165
utils/crypto.js Normal file
View file

@ -0,0 +1,165 @@
/**
* 加密工具函数
* 为微信小程序环境优化的加密工具集
*/
const { decode: base32Decode } = require('./base32.js');
/**
* 常量时间比较两个字符串
* 防止时间侧信道攻击
*
* @param {string} a - 第一个字符串
* @param {string} b - 第二个字符串
* @returns {boolean} 是否相等
*/
function constantTimeEqual(a, b) {
if (typeof a !== 'string' || typeof b !== 'string') {
return false;
}
if (a.length !== b.length) {
return false;
}
let result = 0;
for (let i = 0; i < a.length; i++) {
result |= a.charCodeAt(i) ^ b.charCodeAt(i);
}
return result === 0;
}
/**
* 安全的整数解析
* 带有范围检查
*
* @param {number|string} value - 要解析的值
* @param {number} min - 最小值包含
* @param {number} max - 最大值包含
* @returns {number|null} 解析后的整数如果无效则返回null
*/
function safeIntegerParse(value, min, max) {
let num;
if (typeof value === 'number') {
num = value;
} else if (typeof value === 'string') {
num = parseInt(value, 10);
} else {
return null;
}
if (!Number.isInteger(num)) {
return null;
}
if (num < min || num > max) {
return null;
}
return num;
}
/**
* 生成密码学安全的随机字节
*
* @param {number} length - 需要的字节数
* @returns {Uint8Array} 随机字节数组
*/
function getRandomBytes(length) {
if (!Number.isInteger(length) || length <= 0) {
throw new Error('Length must be a positive integer');
}
// 优先使用微信小程序的随机数API
if (typeof wx !== 'undefined' && wx.getRandomValues) {
const array = new Uint8Array(length);
wx.getRandomValues({
length: length,
success: (res) => {
array.set(new Uint8Array(res.randomValues));
},
fail: () => {
// 如果原生API失败回退到Math.random
for (let i = 0; i < length; i++) {
array[i] = Math.floor(Math.random() * 256);
}
}
});
return array;
}
// 回退到Math.random不够安全但作为降级方案
const array = new Uint8Array(length);
for (let i = 0; i < length; i++) {
array[i] = Math.floor(Math.random() * 256);
}
return array;
}
/**
* 生成指定长度的随机Base32密钥
*
* @param {number} length - 密钥长度字节
* @returns {string} Base32编码的密钥
*/
function generateSecretKey(length = 20) {
if (!Number.isInteger(length) || length < 16 || length > 64) {
throw new Error('Key length must be between 16 and 64 bytes');
}
const bytes = getRandomBytes(length);
return require('./base32.js').encode(bytes);
}
/**
* 验证Base32密钥的有效性和强度
*
* @param {string} key - Base32编码的密钥
* @returns {boolean} 密钥是否有效且足够强
*/
function validateBase32Secret(key) {
try {
// 检查是否是有效的Base32
if (!require('./base32.js').isValid(key)) {
return false;
}
// 解码密钥
const decoded = base32Decode(key);
// 检查最小长度128位/16字节
if (decoded.length < 16) {
return false;
}
// 检查是否全为0或1弱密钥
let allZeros = true;
let allOnes = true;
for (const byte of decoded) {
if (byte !== 0) allZeros = false;
if (byte !== 255) allOnes = false;
if (!allZeros && !allOnes) break;
}
if (allZeros || allOnes) {
return false;
}
return true;
} catch {
return false;
}
}
const { deriveKeyPBKDF2 } = require('./hmac.js');
module.exports = {
constantTimeEqual,
safeIntegerParse,
getRandomBytes,
generateSecretKey,
validateBase32Secret,
deriveKeyPBKDF2
};