/** * HMAC(Hash-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} 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} HMAC 结果 */ function sha1(key, data) { return hmac('sha1', key, data); } /** * 生成 HMAC-SHA256 值 * * @param {string|Uint8Array} key - 密钥 * @param {string|Uint8Array} data - 数据 * @returns {Promise} HMAC 结果 */ function sha256(key, data) { return hmac('sha256', key, data); } /** * 生成 HMAC-SHA512 值 * * @param {string|Uint8Array} key - 密钥 * @param {string|Uint8Array} data - 数据 * @returns {Promise} 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} 派生出的密钥 */ 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 };