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

290
utils/hmac.js Normal file
View file

@ -0,0 +1,290 @@
/**
* HMACHash-based Message Authentication Code实现
* 优先使用微信小程序原生API如果不可用则使用sjcl库作为备用实现
*
* 依赖
* - sjcl: Stanford JavaScript Crypto Library
*/
import sjcl from './sjcl.min.js';
// 兼容 sjcl.codec.bytes
if (!sjcl.codec.bytes) {
sjcl.codec.bytes = {
fromBits: function (arr) {
var out = [], bl = sjcl.bitArray.bitLength(arr), i, tmp;
for (i = 0; i < bl / 8; i++) {
if ((i & 3) === 0) {
tmp = arr[i / 4] || 0;
}
out.push(tmp >>> 24);
tmp <<= 8;
}
return out;
},
toBits: function (bytes) {
var out = [], i, tmp = 0;
for (i = 0; i < bytes.length; i++) {
tmp = tmp << 8 | bytes[i];
if ((i & 3) === 3) {
out.push(tmp);
tmp = 0;
}
}
if (i & 3) {
out.push(sjcl.bitArray.partial(8 * (i & 3), tmp));
}
return out;
}
};
}
// 微信小程序原生 createHMAC 接口映射表
const HASH_ALGORITHMS = {
SHA1: 'sha1',
SHA256: 'sha256',
SHA512: 'sha512'
};
/**
* 生成 HMAC-SHA1/SHA256/SHA512
*
* @param {string} algorithm - 哈希算法 ('sha1', 'sha256', 'sha512')
* @param {string|Uint8Array} key - 密钥
* @param {string|Uint8Array} data - 数据
* @returns {Promise<Uint8Array>} HMAC 结果
* @throws {Error} 如果算法不支持或参数错误
*/
function hmac(algorithm, key, data) {
return new Promise((resolve, reject) => {
try {
// 增强参数校验
if (!algorithm || typeof algorithm !== 'string') {
throw new Error('Algorithm must be a non-empty string');
}
const normalizedAlgorithm = algorithm.toLowerCase();
if (!['sha1', 'sha256', 'sha512'].includes(normalizedAlgorithm)) {
throw new Error(`Unsupported algorithm: ${algorithm}. Supported: sha1, sha256, sha512`);
}
if (!key || (typeof key !== 'string' && !(key instanceof Uint8Array))) {
throw new Error('Key must be a non-empty string or Uint8Array');
}
if (!data || (typeof data !== 'string' && !(data instanceof Uint8Array))) {
throw new Error('Data must be a non-empty string or Uint8Array');
}
// 优先使用微信小程序原生接口
if (typeof wx !== 'undefined' && wx.createHMAC) {
try {
const crypto = wx.createHMAC(normalizedAlgorithm);
// 改进数据格式转换
const keyStr = typeof key === 'string' ? key :
new TextDecoder().decode(key);
const dataStr = typeof data === 'string' ? data :
new TextDecoder().decode(data);
crypto.update(keyStr);
crypto.update(dataStr);
const result = crypto.digest();
if (!result || result.byteLength === 0) {
throw new Error('HMAC digest returned empty result');
}
resolve(result);
} catch (wxError) {
console.error('WeChat HMAC API failed:', {
algorithm: normalizedAlgorithm,
error: wxError.message
});
throw new Error(`WeChat HMAC failed: ${wxError.message}`);
}
} else {
// 使用sjcl库作为备用实现
const keyBytes = typeof key === 'string' ? new TextEncoder().encode(key) : key;
const dataBytes = typeof data === 'string' ? new TextEncoder().encode(data) : data;
// 根据算法选择对应的哈希函数
let hashFunction;
switch (normalizedAlgorithm) {
case 'sha1':
hashFunction = sjcl.hash.sha1;
break;
case 'sha256':
hashFunction = sjcl.hash.sha256;
break;
case 'sha512':
hashFunction = sjcl.hash.sha512;
break;
default:
throw new Error(`Unsupported algorithm: ${algorithm}`);
}
const backupResult = backupHMAC(keyBytes, dataBytes, hashFunction);
resolve(backupResult);
}
} catch (error) {
console.error('HMAC generation failed:', error);
reject(new Error(`HMAC generation failed: ${error.message}`));
}
});
}
/**
* 备用HMAC实现使用sjcl库
*
* @param {Uint8Array} key - 密钥
* @param {Uint8Array} data - 数据
* @param {Function} hashFunction - 哈希算法构造函数
* @returns {Uint8Array} HMAC 结果
*/
function backupHMAC(key, data, hashFunction) {
try {
// 将key和data转换为sjcl的bitArray格式
const keyBits = sjcl.codec.bytes.toBits(Array.from(key));
const dataBits = sjcl.codec.bytes.toBits(Array.from(data));
// 创建HMAC对象并更新数据
const hmac = new sjcl.misc.hmac(keyBits, hashFunction);
hmac.update(dataBits);
// 获取结果并转换回字节数组
const result = hmac.digest();
const byteArray = sjcl.codec.bytes.fromBits(result);
return new Uint8Array(byteArray);
} catch (error) {
console.error('Backup HMAC implementation failed:', error);
throw new Error(`Backup HMAC implementation failed: ${error.message}`);
}
}
/**
* 生成 HMAC-SHA1 向后兼容
*
* @param {string|Uint8Array} key - 密钥
* @param {string|Uint8Array} data - 数据
* @returns {Promise<Uint8Array>} HMAC 结果
*/
function sha1(key, data) {
return hmac('sha1', key, data);
}
/**
* 生成 HMAC-SHA256
*
* @param {string|Uint8Array} key - 密钥
* @param {string|Uint8Array} data - 数据
* @returns {Promise<Uint8Array>} HMAC 结果
*/
function sha256(key, data) {
return hmac('sha256', key, data);
}
/**
* 生成 HMAC-SHA512
*
* @param {string|Uint8Array} key - 密钥
* @param {string|Uint8Array} data - 数据
* @returns {Promise<Uint8Array>} HMAC 结果
*/
function sha512(key, data) {
return hmac('sha512', key, data);
}
/**
* 使用PBKDF2算法派生密钥
*
* @param {Uint8Array} password - 原始密码
* @param {Uint8Array} salt - 盐值
* @param {number} iterations - 迭代次数建议至少10000次
* @param {number} keyLength - 生成的密钥长度字节
* @param {string} hashAlgorithm - 哈希算法'sha256'
* @returns {Promise<Uint8Array>} 派生出的密钥
*/
async function deriveKeyPBKDF2(password, salt, iterations, keyLength, hashAlgorithm = 'sha256') {
// 参数验证
if (!(password instanceof Uint8Array) || !(salt instanceof Uint8Array)) {
throw new Error('Password and salt must be Uint8Array');
}
if (typeof iterations !== 'number' || iterations <= 0) {
throw new Error('Iterations must be a positive number');
}
if (typeof keyLength !== 'number' || keyLength <= 0) {
throw new Error('Key length must be a positive number');
}
// 优先使用微信小程序原生加密API
if (typeof wx !== 'undefined' && wx.derivePBKDF2) {
return new Promise((resolve, reject) => {
wx.derivePBKDF2({
password: password.buffer,
salt: salt.buffer,
iterations: iterations,
keySize: keyLength,
hashAlgorithm: hashAlgorithm,
success: (res) => {
resolve(new Uint8Array(res.key));
},
fail: (err) => {
console.error('PBKDF2 derivation failed:', {
source: 'WeChat API',
error: err.errMsg || 'Unknown error'
});
reject(new Error(`PBKDF2 derivation failed: ${err.errMsg}`));
}
});
});
}
// 使用sjcl库的PBKDF2实现
try {
// 根据算法选择对应的哈希函数
let hashFunction;
switch (hashAlgorithm.toLowerCase()) {
case 'sha1':
hashFunction = sjcl.hash.sha1;
break;
case 'sha256':
hashFunction = sjcl.hash.sha256;
break;
case 'sha512':
hashFunction = sjcl.hash.sha512;
break;
default:
throw new Error(`Unsupported hash algorithm: ${hashAlgorithm}`);
}
// 将密码转换为字符串格式
const passwordStr = typeof password === 'string' ? password : String.fromCharCode.apply(null, password);
// 将盐值转换为字符串格式
const saltStr = typeof salt === 'string' ? salt : String.fromCharCode.apply(null, salt);
// 使用sjcl的PBKDF2实现
const key = sjcl.misc.pbkdf2(passwordStr, saltStr, iterations, keyLength * 8, hashFunction);
// 将结果转换为Uint8Array
const keyBuffer = new ArrayBuffer(keyLength);
const keyData = new Uint8Array(keyBuffer);
for (let i = 0; i < keyLength; i++) {
keyData[i] = key[i];
}
return keyData;
} catch (error) {
throw new Error(`PBKDF2 derivation failed: ${error.message}`);
}
}
// 导出函数
export {
hmac,
sha1,
sha256,
sha512,
deriveKeyPBKDF2
};