SHA256哈希十六进制输出缺失前导零问题排查求助
问题:SHA256哈希转十六进制时缺失前导零
我用C++结合OpenSSL库实现了一个函数,将字符串做SHA256哈希后转换为十六进制格式。输出结果和预期几乎一致,但因为缺失前导零导致长度变短:
对比输出:
64: ae6a9df8bdf4545392e6b1354252af8546282b49033a9118b12e9511892197c6 // 预期结果 64: ae6a9df8bdf4545392e6b1354252af8546282b4933a9118b12e9511892197c6 // 我的输出
我的代码如下:
#include <openssl/evp.h> #include <cstdint> #include <array> #include <string_view> #include <charconv> #include <iostream> int main(){ std::string oceanic = "oceanic 815"; EVP_MD_CTX *context = EVP_MD_CTX_new(); EVP_DigestInit(context, EVP_sha256()); EVP_DigestUpdate(context, oceanic.data(), oceanic.size()); std::array<uint8_t, EVP_MAX_MD_SIZE> hash{}; unsigned int written = 0; EVP_DigestFinal(context, hash.data(), &written); std::cout << written << '/' << EVP_MAX_MD_SIZE << '\n'; // 预期结果来自在线SHA256工具 std::string_view expected = "ae6a9df8bdf4545392e6b1354252af8546282b49033a9118b12e9511892197c6"; std::cout << expected.size() << ": " << expected << '\n'; std::array<char, 64> hex{}; for (size_t iHash = 0, iHex = 0; iHash < written; ++iHash) { std::to_chars(&hex[iHex], &hex[iHex + 2], hash[iHash], 16); iHex += 2; // 下面这段写法同样会出问题 // const char hexMap[] = "0123456789ABCDEF"; // const char ch = hash[iHash]; // hex[iHash] = hexMap[(ch & 0xF0) >> 4]; // hex[iHash + 1] = hexMap[ch & 0xF]; // iHash += 2; } std::cout << hex.size() << ": " << std::string_view(hex.data(), hex.size()) << '\n'; return 0; }
补充:我不想使用std::stringstream,因为不需要动态分配内存。这段代码基于Stack Overflow的一个回答编写。请问我哪里出错了?
问题原因与解决办法
1. std::to_chars的行为导致前导零丢失
std::to_chars默认不会为小于0x10的字节补前导零,比如字节值0x03会被转换成"3"而不是"03"。这会导致后续字节的写入位置偏移,最终生成的十六进制字符串缺失前导零,结构错误。
要修复这个问题,需要手动检查转换后的字符长度,不足两位时补前导零:
for (size_t iHash = 0, iHex = 0; iHash < written; ++iHash) { auto [ptr, ec] = std::to_chars(&hex[iHex], &hex[iHex + 2], hash[iHash], 16); // 如果只生成了1个字符,手动插入前导零 if (ptr - &hex[iHex] == 1) { std::memmove(&hex[iHex + 1], &hex[iHex], 1); hex[iHex] = '0'; } iHex += 2; }
2. 注释中手动映射写法的错误
注释里的手动转十六进制代码有两个明显错误:
- 索引错误:用
iHash作为十六进制数组的索引,而正确的索引应该是iHex(iHash是原哈希字节的索引,iHex是十六进制字符串的当前位置); - 循环变量冲突:循环内的
iHash += 2会破坏外层循环的计数逻辑,导致跳过大量哈希字节。
修正后的手动映射写法(更推荐,格式可控性更强):
std::array<char, 64> hex{}; const char hexMap[] = "0123456789abcdef"; // 和预期结果的小写格式保持一致 for (size_t iHash = 0, iHex = 0; iHash < written; ++iHash) { uint8_t ch = hash[iHash]; hex[iHex++] = hexMap[(ch >> 4) & 0x0F]; // 取高4位 hex[iHex++] = hexMap[ch & 0x0F]; // 取低4位 }
最终效果
使用修正后的代码,就能生成和预期完全一致的、带有完整前导零的64位十六进制哈希字符串了。
内容的提问来源于stack exchange,提问作者Sergey Kolesnik
相关产品推荐
相关产品推荐

