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

Postman中通过JavaScript生成OTP失败问题排查

HOTP生成OTP错误的常见排查点

1. 密钥解码逻辑错误

HOTP的密钥通常是Base32编码格式,而Crypto-JS默认处理UTF-8字符串。如果直接用CryptoJS.enc.Utf8.parse(key)解析Base32密钥,会导致哈希计算的原始数据完全错误,这是最常见的问题根源。必须先将Base32编码的密钥转换为字节数组,再传入HMAC计算。

2. 计数器字节序处理错误

HOTP规范要求计数器必须转换为**8字节的大端(Big-Endian)**字节数组。如果用小端(Little-Endian)或其他格式处理计数器,生成的哈希结果会和正确值完全不符。

错误示例:

// 错误:直接将计数器转为字符串后解析
let counterBytes = CryptoJS.enc.Utf8.parse(counter.toString());

正确处理方式:

function counterToBigEndian(counter) {
    let bytes = new Uint8Array(8);
    for (let i = 7; i >= 0; i--) {
        bytes[i] = counter & 0xff;
        counter = counter >> 8;
    }
    return CryptoJS.lib.WordArray.create(bytes);
}

3. 哈希算法不匹配

默认HOTP使用SHA1算法,但部分场景会用到SHA256或SHA512。需确认C#工具使用的哈希算法与Crypto-JS代码一致,比如C#用SHA256但JS用CryptoJS.HmacSHA1,结果必然不匹配。

4. 截断步骤实现错误

HOTP的截断环节需严格遵循规范:

  • 取HMAC结果的最后4位作为偏移量
  • 从偏移量位置取4字节转换为无符号整数
  • 对10^6取模得到6位OTP

错误示例:

// 错误:未按偏移量取字节,直接取最后一组数据
let truncated = hmacSig.words[hmacSig.words.length - 1] & 0x7fffffff;

正确截断逻辑:

function truncateHmac(hmacSig) {
    let offset = hmacSig.words[hmacSig.words.length - 1] & 0x0f;
    let otp = ((hmacSig.words[offset] & 0x7fff0000) >> 16) |
              ((hmacSig.words[offset + 1] & 0xffff00) >> 8) |
              (hmacSig.words[offset + 2] & 0xff);
    return otp % 1000000;
}

5. 补零处理缺失

生成的OTP如果不足6位,必须在前面补零(比如结果为1234时要转为001234)。若省略此步骤,会导致与标准6位OTP格式不匹配。


完整正确HOTP实现示例(基于Crypto-JS)

// Base32转字节数组工具函数
function base32ToBytes(base32) {
    const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ234567';
    base32 = base32.replace(/=/g, '').toUpperCase();
    let bytes = [];
    let bits = 0;
    let value = 0;
    for (let char of base32) {
        let index = chars.indexOf(char);
        value = (value << 5) | index;
        bits += 5;
        if (bits >= 8) {
            bits -= 8;
            bytes.push((value >> bits) & 0xff);
        }
    }
    return CryptoJS.lib.WordArray.create(new Uint8Array(bytes));
}

// 计数器转大端字节数组
function counterToBigEndian(counter) {
    let bytes = new Uint8Array(8);
    for (let i = 7; i >= 0; i--) {
        bytes[i] = counter & 0xff;
        counter = counter >> 8;
    }
    return CryptoJS.lib.WordArray.create(bytes);
}

// 生成6位HOTP
function generateHOTP(keyBase32, counter) {
    // 解码Base32密钥
    let key = base32ToBytes(keyBase32);
    // 处理计数器字节序
    let counterBytes = counterToBigEndian(counter);
    // 计算HMAC-SHA1(若用其他算法替换为HmacSHA256/HmacSHA512)
    let hmac = CryptoJS.HmacSHA1(counterBytes, key);
    // 截断处理
    let offset = hmac.words[hmac.words.length - 1] & 0x0f;
    let otp = ((hmac.words[offset] & 0x7fff0000) >> 16) |
              ((hmac.words[offset + 1] & 0xffff00) >> 8) |
              (hmac.words[offset + 2] & 0xff);
    otp = otp % 1000000;
    // 补零到6位
    return otp.toString().padStart(6, '0');
}

你可以对照自己的代码,重点检查密钥解码、计数器字节序这两个核心环节,通常就能定位到错误原因。

内容的提问来源于stack exchange,提问作者Dennis Robare

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.06 11:42:51