尝试打印文件的十六进制 Blake2 哈希时出现一个非常奇怪的错误
A Very weird bug when trying to print the hexadecimal Blake2 hash of a file
我正在编写一个小程序来使用 libsodium 计算文件的哈希值 (blake2b),发现自己正盯着一个奇怪的错误。
我的十六进制输出中缺少一个零,这不是由哈希过程引起的,因为我们使用的是相同的 256 位截断哈希函数。
两者都使用 Blake2b(针对 x64 进行了优化)。
我确保验证文件已全部读取,即使输出完全不同也是如此,因为它是一个哈希函数(1 位足以产生不同的输出)。
我还使用了 C 风格的打印和 C++ 流来查看它是否与格式说明符有关,结果表明并非如此。
我的程序输出如下:
479b5e6da5eb90a19ae1777c8ccc614b5c8f695c9cffbfe78d38b89e40b865
使用b2sum命令行工具时
b2sum /bin/ls -l 256
479b5e6da5eb90a19ae1777c8ccc614b**0**5c8f695c9cffbfe78d38b89**0**e40b865
#include<iostream>
#include<fstream>
#include<sstream>
#include<ios>
#include<vector>
#include<sodium.h>
using namespace std;
int main(int argc, char** argv)
{
using buffer = vector<char>;
ifstream input(argv[1],ios::binary | ios::ate);
// get file size
streamsize filesize = input.tellg();
input.seekg(0,ios::beg);
// make a buffer with that filesize
buffer buf(filesize);
// read the file
input.read(buf.data(),buf.size());
input.close();
// show filesize
cout << "Filesize : " << filesize << endl;
// using the snipped from libsodium docs
// https://libsodium.gitbook.io/doc/hashing/generic_hashing
// Example 1
unsigned char hash[crypto_generichash_BYTES];
crypto_generichash(hash,sizeof(hash),(unsigned char*)buf.data(),buf.size(),NULL,0);
// Print the hash in hexadecimal
for(int i = 0; i < crypto_generichash_BYTES; i++)
{
printf("%x",hash[i]);
}
cout << endl;
// load the hash into a stringstream using hexadecimal
stringstream ss;
for(int i=0; i<crypto_generichash_BYTES;++i)
ss << std::hex << (int)hash[i];
std::string mystr = ss.str();
// output the stringstream
cout << mystr << endl;
cout << "hash length :" << mystr.length() << endl;
}
你应该使用类似的东西:
printf("%02x",hash[i]);
打印出字节。这将正确处理小于 16 的十六进制值,在您的版本中,它只会输出一个 单个 十六进制数字。
你可以在下面的程序中看到:
#include <cstdio>
#define FMT "%02x"
int main() {
printf(FMT, 0x4b);
printf(FMT, 0x05);
printf(FMT, 0xc8);
putchar('\n');
}
使用如上定义的 FMT
,您会看到正确的 4b05c8
。将其定义(如您所定义)为 "%x"
,您会看到错误的 4b5c8
.
顺便说一句,您可能想考虑放弃 C 遗留的东西(a),例如 printf
.我知道它在标准中,但由于它的局限性,几乎没有人(b) 使用它,尽管 iostream
等价物更加冗长。
或者做我们已经做过的事情,只使用 fmt
库来获得更简洁但仍然类型安全的输出,特别是因为它目前正针对 C+20(因此几乎肯定会成为一部分在某些时候的标准)。
(a) 没有人愿意被称为 C+ 程序员,那个从来没有完全接受语言的全部力量的奇怪品种:-)
(b) 基于与我共事过的中等数量的 C++ 开发人员样本:-)
printf("%x",hash[i]);
不会为小于 0x10 的十六进制值输出前导零。您需要改用 printf("%02x", hash[i]);
,它告诉 printf()
输出至少 2 个十六进制数字,如果需要,在前面加上前导零。
否则,请改用 C++ 流输出:
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
您还需要为 std::streamstream
执行此操作,因为您的代码还省略了十六进制值 < 0x10 的前导零。
我正在编写一个小程序来使用 libsodium 计算文件的哈希值 (blake2b),发现自己正盯着一个奇怪的错误。
我的十六进制输出中缺少一个零,这不是由哈希过程引起的,因为我们使用的是相同的 256 位截断哈希函数。
两者都使用 Blake2b(针对 x64 进行了优化)。
我确保验证文件已全部读取,即使输出完全不同也是如此,因为它是一个哈希函数(1 位足以产生不同的输出)。
我还使用了 C 风格的打印和 C++ 流来查看它是否与格式说明符有关,结果表明并非如此。
我的程序输出如下:
479b5e6da5eb90a19ae1777c8ccc614b5c8f695c9cffbfe78d38b89e40b865
使用b2sum命令行工具时
b2sum /bin/ls -l 256 479b5e6da5eb90a19ae1777c8ccc614b**0**5c8f695c9cffbfe78d38b89**0**e40b865
#include<iostream>
#include<fstream>
#include<sstream>
#include<ios>
#include<vector>
#include<sodium.h>
using namespace std;
int main(int argc, char** argv)
{
using buffer = vector<char>;
ifstream input(argv[1],ios::binary | ios::ate);
// get file size
streamsize filesize = input.tellg();
input.seekg(0,ios::beg);
// make a buffer with that filesize
buffer buf(filesize);
// read the file
input.read(buf.data(),buf.size());
input.close();
// show filesize
cout << "Filesize : " << filesize << endl;
// using the snipped from libsodium docs
// https://libsodium.gitbook.io/doc/hashing/generic_hashing
// Example 1
unsigned char hash[crypto_generichash_BYTES];
crypto_generichash(hash,sizeof(hash),(unsigned char*)buf.data(),buf.size(),NULL,0);
// Print the hash in hexadecimal
for(int i = 0; i < crypto_generichash_BYTES; i++)
{
printf("%x",hash[i]);
}
cout << endl;
// load the hash into a stringstream using hexadecimal
stringstream ss;
for(int i=0; i<crypto_generichash_BYTES;++i)
ss << std::hex << (int)hash[i];
std::string mystr = ss.str();
// output the stringstream
cout << mystr << endl;
cout << "hash length :" << mystr.length() << endl;
}
你应该使用类似的东西:
printf("%02x",hash[i]);
打印出字节。这将正确处理小于 16 的十六进制值,在您的版本中,它只会输出一个 单个 十六进制数字。
你可以在下面的程序中看到:
#include <cstdio>
#define FMT "%02x"
int main() {
printf(FMT, 0x4b);
printf(FMT, 0x05);
printf(FMT, 0xc8);
putchar('\n');
}
使用如上定义的 FMT
,您会看到正确的 4b05c8
。将其定义(如您所定义)为 "%x"
,您会看到错误的 4b5c8
.
顺便说一句,您可能想考虑放弃 C 遗留的东西(a),例如 printf
.我知道它在标准中,但由于它的局限性,几乎没有人(b) 使用它,尽管 iostream
等价物更加冗长。
或者做我们已经做过的事情,只使用 fmt
库来获得更简洁但仍然类型安全的输出,特别是因为它目前正针对 C+20(因此几乎肯定会成为一部分在某些时候的标准)。
(a) 没有人愿意被称为 C+ 程序员,那个从来没有完全接受语言的全部力量的奇怪品种:-)
(b) 基于与我共事过的中等数量的 C++ 开发人员样本:-)
printf("%x",hash[i]);
不会为小于 0x10 的十六进制值输出前导零。您需要改用 printf("%02x", hash[i]);
,它告诉 printf()
输出至少 2 个十六进制数字,如果需要,在前面加上前导零。
否则,请改用 C++ 流输出:
std::cout << std::hex << std::setw(2) << std::setfill('0') << (int)hash[i];
您还需要为 std::streamstream
执行此操作,因为您的代码还省略了十六进制值 < 0x10 的前导零。