如何用CryptoJS实现HKDF?实现结果与CyberChef不符求排查
CryptoJS实现HKDF与CyberChef结果不一致的问题修复
问题分析
核心错误在于对CryptoJS中WordArray.concat()方法的误用,以及后续的结果拼接逻辑错误:
WordArray.concat()是纯函数,调用后会返回新的WordArray实例,不会修改原对象。原代码中okm.concat(previousBlock)没有将返回值赋值给okm,导致okm始终为空,循环内的密钥派生逻辑完全错误。- 额外的Uint8Array与WordArray互转属于冗余操作,直接使用CryptoJS的WordArray可以避免转换过程的潜在问题。
修正后的代码
/** * HKDF (HMAC-based Key Derivation Function) SHA256实现 * @param {CryptoJS.lib.WordArray} salt - 盐值 * @param {CryptoJS.lib.WordArray} ikm - 输入密钥材料 * @param {CryptoJS.lib.WordArray} info - 上下文信息 * @param {number} length - 期望输出密钥长度(字节) * @returns {Uint8Array} 派生的密钥材料 */ function hkdfSha256(salt, ikm, info, length) { // HKDF-Extract 阶段:生成伪随机密钥PRK const prk = CryptoJS.HmacSHA256(ikm, salt); let okm = CryptoJS.lib.WordArray.create(); let previousBlock = CryptoJS.lib.WordArray.create(); for (let i = 1; okm.sigBytes < length; i++) { // 拼接前一块结果、info、当前块索引(1字节) const currentInput = previousBlock.concat(info).concat(CryptoJS.lib.WordArray.create([i], 1)); // 计算当前块HMAC previousBlock = CryptoJS.HmacSHA256(currentInput, prk); // 将当前块结果追加到okm(必须接收concat的返回值) okm = okm.concat(previousBlock); } // 截断到指定长度并转成Uint8Array const truncatedOkm = okm.clone(); truncatedOkm.sigBytes = length; truncatedOkm.words = truncatedOkm.words.slice(0, Math.ceil(length / 4)); // WordArray转Uint8Array const words = truncatedOkm.words; const sigBytes = truncatedOkm.sigBytes; const u8 = new Uint8Array(sigBytes); for (let i = 0; i < sigBytes; i++) { const byte = (words[i >>> 2] >>> (24 - (i % 4) * 8)) & 0xff; u8[i] = byte; } return u8; } function main() { const salt = CryptoJS.enc.Hex.parse('f339a9b6f339a9b6'); const ikm = CryptoJS.enc.Utf8.parse('Hello'); const info = CryptoJS.enc.Hex.parse(''); // 空info const length = 32; const outputKeyMaterial = hkdfSha256(salt, ikm, info, length); // 转成Hex字符串输出,将和CyberChef结果一致 console.log(CryptoJS.lib.WordArray.create(outputKeyMaterial).toString()); // 输出: 3d18bc7eccb941ded1260bef702b94d899f4defa0365a49ee83543c59f336fe6 } // 执行测试 main();
关键修改说明
- 修复okm的拼接逻辑:将
okm.concat(previousBlock)改为okm = okm.concat(previousBlock),确保每次循环都将新生成的块追加到结果中。 - 添加结果截断处理:循环结束后将okm截断到指定的length长度,严格符合HKDF规范。
- 移除冗余类型转换:直接使用CryptoJS的WordArray作为参数,避免Uint8Array和WordArray之间的来回转换,简化逻辑。
内容的提问来源于stack exchange,提问作者IvanaGyro
相关产品推荐
相关产品推荐

