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

290 lines
No EOL
8.5 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.

/**
* 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
};