otp/utils/crypto.js
“xHuPo” 2b8870a40e init
2025-06-09 13:35:15 +08:00

165 lines
No EOL
3.7 KiB
JavaScript
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.

/**
* 加密工具函数
* 为微信小程序环境优化的加密工具集
*/
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
};