You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

将crypto的createHmac转换为crypto-js实现(含Buffer处理)

解决crypto-js与Node.js crypto模块HMAC-SHA1结果不一致的问题

你的核心问题在于密钥和输入数据的编码格式不匹配,导致crypto-js生成的HMAC和Node.js crypto模块的结果不一致。下面是具体的修复方案:

问题分析

  1. 密钥处理错误:decodedSecret是字节数组,你用cryptojs.enc.Utf8.stringify()将其转为UTF-8字符串,但Node.js crypto是直接使用原始字节数组作为密钥的。UTF-8编码会破坏原始字节的结构(比如非ASCII字节会被错误解析),导致密钥不一致。
  2. 输入数据格式错误:你直接把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));

关键修改点

  1. 密钥转换:用cryptojs.lib.WordArray.create(decodedSecret)直接将字节数组转为crypto-js的WordArray,保留原始字节信息,和Node.js crypto的密钥处理逻辑完全一致。
  2. 输入数据转换:同样用WordArray.create(buffer)将Node.js Buffer转为crypto-js支持的格式,确保输入数据的字节结构无差异。
  3. 结果适配:如果要使用crypto-js的结果进行后续的动态截断,需要把十六进制字符串转回Uint8Array,因为dynamicTruncationFn需要操作字节数组。

运行修改后的代码,你会看到crypto-js和Node.js crypto输出的十六进制哈希值完全一致,后续的TOTP计算结果也会相同。

内容的提问来源于stack exchange,提问作者Stefan AusBerlin

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.17 00:28:21