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

基于C++与OpenSSL的TOTP实现验证失败问题排查

问题排查与修复

你的代码存在多个关键问题导致TOTP验证失败,逐一拆解如下:

1. 参数顺序完全颠倒

测试代码中调用verifyTOTP(newKey, code, 6, 20);,但函数定义的参数顺序是(char* code, char* key, int codeLen, int keyLen)——你把密钥传给了code参数,把待验证的令牌传给了key参数,直接导致验证逻辑完全错位。

2. 未处理Base32编码的密钥

你使用的TOTP生成器密钥是Base32格式的MFQWCYLBMFQWCYLBMFQWCYLBMFQWCYLB,但代码里直接用了ASCII字符串"aaaaaaaaaaaaaaaaaaaa"作为原始密钥。TOTP要求使用原始二进制密钥,必须先将Base32编码的密钥解码为字节数组,不能直接用编码后的字符串。

3. 计数器字节序错误

TOTP标准要求计数器(时间戳除以30)以**大端字节序(网络字节序)**传入HMAC,但你的代码直接传递了主机字节序的intCounter地址,这会导致不同平台(比如x86是小端)生成的HMAC结果错误。

4. 错误的内存释放

函数中delete[] key;和delete[] code;是严重错误:这两个指针是外部传入的,函数无权释放它们,会导致双重释放或野指针问题。

5. 变量名大小写错误

函数里的keylen应该是keyLen(与参数名一致),虽然编译器可能通过,但属于不规范写法,容易引发逻辑错误。


修正后的代码

#include <openssl/hmac.h>
#include <openssl/bio.h>
#include <openssl/evp.h>
#include <cstdio>
#include <ctime>
#include <chrono>
#include <thread>
#include <cstring>
#include <cctype>

// Base32解码实现(TOTP密钥需要从Base32转原始字节)
int base32_decode(const char* input, unsigned char* output, int output_len) {
    static const char* base32_chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    int bits = 0;
    int value = 0;
    int index = 0;

    while (*input != '\0') {
        // 跳过空白字符
        if (*input == ' ' || *input == '\t' || *input == '\n' || *input == '\r') {
            input++;
            continue;
        }

        // 查找字符在Base32表中的位置
        const char* pos = strchr(base32_chars, toupper(*input));
        if (!pos) return -1; // 无效字符

        value = (value << 5) | (pos - base32_chars);
        bits += 5;

        if (bits >= 8) {
            bits -= 8;
            output[index++] = (value >> bits) & 0xFF;
            if (index >= output_len) break;
        }

        input++;
    }

    return index;
}

// 防时序攻击的字符串比较
int compHash(const char* a, const char* b, int len) {
    int result = 0;
    for (int i = 0; i < len; i++) {
        result |= a[i] ^ b[i];
    }
    return result;
}

bool verifyTOTP(const char* code, const unsigned char* key, int codeLen, int keyLen) {
    if (codeLen != 6 || keyLen != 20) {
        return false;
    }

    // 获取当前时间窗口的计数器并转为大端字节序
    unsigned long long intCounter = time(NULL) / 30;
    unsigned char counter_bytes[8];
    for (int i = 7; i >= 0; i--) {
        counter_bytes[i] = intCounter & 0xFF;
        intCounter >>= 8;
    }

    // 计算HMAC-SHA1
    unsigned char md[20];
    unsigned int mdLen;
    HMAC(EVP_sha1(), key, keyLen, counter_bytes, sizeof(counter_bytes), md, &mdLen);

    // 动态截断获取二进制码
    int offset = md[19] & 0x0F;
    unsigned int bin_code = (md[offset] & 0x7F) << 24
                          | (md[offset+1] & 0xFF) << 16
                          | (md[offset+2] & 0xFF) << 8
                          | (md[offset+3] & 0xFF);
    bin_code %= 1000000;

    // 格式化6位令牌
    char correctCode[7];
    snprintf(correctCode, sizeof(correctCode), "%06u", bin_code);

    // 防时序比较
    int compR = compHash(correctCode, code, 6);
    if (compR == 0) {
        return true;
    }

    // 失败延迟(增加暴力破解难度)
    std::this_thread::sleep_for(std::chrono::seconds(1));
    return false;
}

// 测试示例
int main() {
    // Base32编码的密钥:MFQWCYLBMFQWCYLBMFQWCYLBMFQWCYLB
    const char* base32_key = "MFQWCYLBMFQWCYLBMFQWCYLBMFQWCYLB";
    unsigned char raw_key[20];
    int decoded_len = base32_decode(base32_key, raw_key, sizeof(raw_key));
    if (decoded_len != 20) {
        printf("Base32解码失败\n");
        return 1;
    }

    // 替换为TOTP生成器当前的6位令牌
    const char* code = "123456";
    bool result = verifyTOTP(code, raw_key, 6, decoded_len);
    printf("验证结果:%s\n", result ? "成功" : "失败");

    return 0;
}

关键修正说明

  1. 添加Base32解码:实现标准Base32解码,将生成器的Base32密钥转为原始二进制字节,符合TOTP密钥要求。
  2. 修正参数顺序:确保调用时code和key参数传递正确,匹配函数定义。
  3. 计数器转大端字节序:将时间计数器转换为网络字节序后再传入HMAC,严格遵循RFC 6238标准。
  4. 移除错误的内存释放:不再释放外部传入的指针,避免内存泄漏或野指针问题。
  5. 统一变量命名:修正keyLen大小写不一致问题,减少逻辑混淆。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.08.22 00:06:18