如何在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, ¤t_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, ¤t_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
相关产品推荐
相关产品推荐

