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

如何在C++中通过密钥生成与谷歌验证器一致的TOTP?

TOTP生成与谷歌验证器不一致问题解决

我正在开发一款算法交易机器人,对接券商时需要输入基于密钥生成的TOTP验证码。我持有密钥,但在C++中生成的验证码始终和谷歌验证器显示的不一致,无法正常使用。

我试过两种方案:直接用OpenSSL实现HMAC-SHA1,以及使用oath库,但都没成功。


我使用的OpenSSL代码

#include <iostream>
#include <cstring>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <ctime>
#include <cmath>

std::string generateTOTP(const std::string& secret, unsigned long timeStep, int digits) {
    const unsigned char* key = reinterpret_cast<const unsigned char*>(secret.c_str());
    int keyLen = secret.length();

    // Get the current Unix time
    unsigned long current_time = time(nullptr) / timeStep;

    // Convert the time to big-endian
    current_time = htobe64(current_time);

    // Create the data to be hashed (current time)
    unsigned char data[8];
    memcpy(data, &current_time, sizeof(current_time));

    // Calculate the HMAC-SHA1 hash
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hashLen;
    HMAC(EVP_sha1(), key, keyLen, data, sizeof(data), hash, &hashLen);

    // Calculate the offset
    int offset = hash[hashLen - 1] & 0xf;

    // Calculate the 4 bytes OTP
    int binary = ((hash[offset] & 0x7f) << 24) |
                 ((hash[offset + 1] & 0xff) << 16) |
                 ((hash[offset + 2] & 0xff) << 8) |
                 (hash[offset + 3] & 0xff);

    int otp = binary % static_cast<int>(std::pow(10, digits));

    // Convert the OTP to a string with leading zeros if needed
    return std::to_string(otp);
} 
int main() {
    const std::string secret_key = "###################";
    const int digits = 6; // Number of OTP digits

    std::string totp = generateTOTP(secret_key, 30, digits);
    while (totp.length() < digits) {
        totp = "0" + totp;
    }
    std::cout << "Generated TOTP: " << totp << std::endl;
    return 0;
}

我使用的oath库代码

#include <iostream>
#include <chrono>
#include <oath.h>

int main() {
    const char* secret_key = "####################"; // Replace with your actual secret key
    char otp[6];
    int otp_len = sizeof(otp);

    auto now = std::chrono::system_clock::now();
    auto current_time = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();
    unsigned long time_step = 30; // TOTP time step (usually 30 seconds)

    if (oath_totp_generate2(
        secret_key, 
        strlen(secret_key), 
        current_time, 
        OATH_TOTP_DEFAULT_TIME_STEP_SIZE, 
        OATH_TOTP_DEFAULT_START_TIME, 
        otp_len,
        OATH_TOTP_HMAC_SHA256, 
        otp) == 0) {
        // otp[otp_len] = '\0'; // Null-terminate the OTP
        std::cout << "Generated TOTP: " << otp << std::endl;
    } else {
        std::cerr << "Error generating TOTP." << std::endl;
    }

    return 0;
}

核心问题排查与修复方案

1. 密钥格式错误(最常见原因)

谷歌验证器使用Base32编码的密钥,你代码里直接将密钥字符串作为原始字节使用是错误的。必须先把Base32编码的密钥解码成二进制字节数组,再传入HMAC算法。

2. 哈希算法不匹配

oath库代码里用了OATH_TOTP_HMAC_SHA256,但谷歌验证器默认使用HMAC-SHA1,除非券商明确指定算法,否则要改成OATH_TOTP_HMAC_SHA1。

3. 时间同步问题

TOTP依赖精准的Unix时间,确保本地/服务器时间和NTP服务器同步,误差不能超过30秒(TOTP默认时间步长)。

4. 浮点数精度问题

OpenSSL代码里用std::pow(10, digits)计算模值,可能存在浮点数精度误差,建议改用预定义的整数常量(比如6位OTP用1000000)。


修正后的代码示例

修复版OpenSSL实现(含Base32解码)

#include <iostream>
#include <cstring>
#include <openssl/hmac.h>
#include <openssl/evp.h>
#include <ctime>
#include <vector>
#include <cctype>
#include <stdint.h>

// Base32解码函数
bool base32Decode(const std::string& input, std::vector<uint8_t>& output) {
    static const char* base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    int bits = 0;
    int value = 0;
    output.clear();

    for (char c : input) {
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
        const char* pos = strchr(base32Chars, toupper(c));
        if (!pos) return false;
        value = (value << 5) | (pos - base32Chars);
        bits += 5;
        if (bits >= 8) {
            bits -= 8;
            output.push_back((value >> bits) & 0xFF);
        }
    }
    return true;
}

std::string generateTOTP(const std::vector<uint8_t>& secret, unsigned long timeStep, int digits) {
    const unsigned char* key = secret.data();
    int keyLen = secret.size();

    // 获取当前Unix时间步
    uint64_t current_time = time(nullptr) / timeStep;

    // 转换为大端字节序
    current_time = htobe64(current_time);
    unsigned char data[8];
    memcpy(data, &current_time, sizeof(current_time));

    // 计算HMAC-SHA1
    unsigned char hash[EVP_MAX_MD_SIZE];
    unsigned int hashLen;
    HMAC(EVP_sha1(), key, keyLen, data, sizeof(data), hash, &hashLen);

    // 计算偏移量
    int offset = hash[hashLen - 1] & 0xf;

    // 提取31位二进制值
    uint32_t binary = ((hash[offset] & 0x7f) << 24) |
                      ((hash[offset + 1] & 0xff) << 16) |
                      ((hash[offset + 2] & 0xff) << 8) |
                      (hash[offset + 3] & 0xff);

    // 用整数运算避免浮点数误差
    const uint32_t powerMap[] = {1, 10, 100, 1000, 10000, 100000, 1000000};
    uint32_t otp = binary % powerMap[digits];

    // 格式化带前导零的字符串
    char otpStr[7];
    snprintf(otpStr, sizeof(otpStr), "%0*u", digits, otp);
    return std::string(otpStr);
}

int main() {
    const std::string base32Secret = "JBSWY3DPEHPK3PXP"; // 替换为你的Base32密钥
    const int digits = 6;
    const unsigned long timeStep = 30;

    std::vector<uint8_t> secret;
    if (!base32Decode(base32Secret, secret)) {
        std::cerr << "Base32解码失败" << std::endl;
        return 1;
    }

    std::string totp = generateTOTP(secret, timeStep, digits);
    std::cout << "生成的TOTP: " << totp << std::endl;
    return 0;
}

修复版oath库实现

#include <iostream>
#include <chrono>
#include <oath.h>
#include <vector>
#include <cctype>

// Base32解码函数
bool base32Decode(const std::string& input, std::vector<uint8_t>& output) {
    static const char* base32Chars = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    int bits = 0;
    int value = 0;
    output.clear();

    for (char c : input) {
        if (c == ' ' || c == '\t' || c == '\n' || c == '\r') continue;
        const char* pos = strchr(base32Chars, toupper(c));
        if (!pos) return false;
        value = (value << 5) | (pos - base32Chars);
        bits += 5;
        if (bits >= 8) {
            bits -= 8;
            output.push_back((value >> bits) & 0xFF);
        }
    }
    return true;
}

int main() {
    const std::string base32Secret = "JBSWY3DPEHPK3PXP"; // 替换为你的Base32密钥
    char otp[7]; // 6位+字符串终止符
    int otp_len = 6;

    std::vector<uint8_t> secret;
    if (!base32Decode(base32Secret, secret)) {
        std::cerr << "Base32解码失败" << std::endl;
        return 1;
    }

    auto now = std::chrono::system_clock::now();
    auto current_time = std::chrono::duration_cast<std::chrono::seconds>(now.time_since_epoch()).count();

    if (oath_totp_generate2(
        reinterpret_cast<const char*>(secret.data()), 
        secret.size(), 
        current_time, 
        30, 
        0, 
        otp_len,
        OATH_TOTP_HMAC_SHA1, 
        otp) == 0) {
        otp[otp_len] = '\0'; // 必须添加终止符
        std::cout << "生成的TOTP: " << otp << std::endl;
    } else {
        std::cerr << "生成TOTP失败" << std::endl;
    }

    return 0;
}

编译注意事项

  • OpenSSL版本编译时需链接库:g++ your_code.cpp -o totp -lssl -lcrypto
  • oath库版本编译时需链接库:g++ your_code.cpp -o totp -loath

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 14:04:50