基于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; }
关键修正说明
- 添加Base32解码:实现标准Base32解码,将生成器的Base32密钥转为原始二进制字节,符合TOTP密钥要求。
- 修正参数顺序:确保调用时
code和key参数传递正确,匹配函数定义。 - 计数器转大端字节序:将时间计数器转换为网络字节序后再传入HMAC,严格遵循RFC 6238标准。
- 移除错误的内存释放:不再释放外部传入的指针,避免内存泄漏或野指针问题。
- 统一变量命名:修正
keyLen大小写不一致问题,减少逻辑混淆。
内容的提问来源于stack exchange,提问作者mbs9
相关产品推荐
相关产品推荐

