将crypto的createHmac转换为crypto-js实现(含Buffer处理)
解决crypto-js与Node.js crypto模块HMAC-SHA1结果不一致的问题
你的核心问题在于密钥和输入数据的编码格式不匹配,导致crypto-js生成的HMAC和Node.js crypto模块的结果不一致。下面是具体的修复方案:
问题分析
- 密钥处理错误:
decodedSecret是字节数组,你用cryptojs.enc.Utf8.stringify()将其转为UTF-8字符串,但Node.js crypto是直接使用原始字节数组作为密钥的。UTF-8编码会破坏原始字节的结构(比如非ASCII字节会被错误解析),导致密钥不一致。 - 输入数据格式错误:你直接把Node.js的
Buffer对象传给hmacjs.update(),但crypto-js无法直接处理Buffer,需要转换成它支持的WordArray格式。
修复后的代码
const crypto = require('crypto'); const cryptojs = require('crypto-js'); const base32 = require('hi-base32'); function generateHOTP(secret, counter) { const decodedSecret = base32.decode.asBytes(secret); const buffer = Buffer.alloc(8); for (let i = 0; i < 8; i++) { buffer[7 - i] = counter & 0xff; counter = counter >> 8; } // Step 1: 修复后的crypto-js HMAC-SHA1生成逻辑 // 将字节数组密钥转为crypto-js的WordArray const secretWordArray = cryptojs.lib.WordArray.create(decodedSecret); // 将Buffer转为WordArray const bufferWordArray = cryptojs.lib.WordArray.create(buffer); // 生成HMAC并转为十六进制字符串 const hmacJsResult = cryptojs.HmacSHA1(bufferWordArray, secretWordArray).toString(); console.log('crypto-js结果:', hmacJsResult); // Node.js crypto原逻辑 const hmac = crypto.createHmac('sha1', Buffer.from(decodedSecret)); hmac.update(buffer); const hmacResult = hmac.digest(); console.log('Node.js crypto结果:', hmacResult.toString('hex')); // Step 2: 动态截断(适配两种结果格式) // 将crypto-js的十六进制字符串转为Uint8Array,用于后续截断 const hmacJsBytes = hexToUint8Array(hmacJsResult); // 可选:用crypto-js的结果计算HOTP(替换为hmacJsBytes即可) const code = dynamicTruncationFn(hmacResult); // Step 3: 计算HOTP值 return code % 10 ** 6; } // 辅助函数:十六进制字符串转Uint8Array function hexToUint8Array(hex) { const bytes = new Uint8Array(hex.length / 2); for (let i = 0; i < hex.length; i += 2) { bytes[i / 2] = parseInt(hex.substring(i, i + 2), 16); } return bytes; } function dynamicTruncationFn(hmacValue) { const offset = hmacValue[hmacValue.length - 1] & 0xf; return ( ((hmacValue[offset] & 0x7f) << 24) | ((hmacValue[offset + 1] & 0xff) << 16) | ((hmacValue[offset + 2] & 0xff) << 8) | (hmacValue[offset + 3] & 0xff) ); } function generateTOTP(secret, window = 0) { const counter = Math.floor(Date.now() / 30000); return generateHOTP(secret, counter + window); } console.log(generateTOTP('GEZDGNBVGY3TQOJQGEZDG', 0));
关键修改点
- 密钥转换:用
cryptojs.lib.WordArray.create(decodedSecret)直接将字节数组转为crypto-js的WordArray,保留原始字节信息,和Node.js crypto的密钥处理逻辑完全一致。 - 输入数据转换:同样用
WordArray.create(buffer)将Node.js Buffer转为crypto-js支持的格式,确保输入数据的字节结构无差异。 - 结果适配:如果要使用crypto-js的结果进行后续的动态截断,需要把十六进制字符串转回Uint8Array,因为
dynamicTruncationFn需要操作字节数组。
运行修改后的代码,你会看到crypto-js和Node.js crypto输出的十六进制哈希值完全一致,后续的TOTP计算结果也会相同。
内容的提问来源于stack exchange,提问作者Stefan AusBerlin
相关产品推荐
相关产品推荐

