文件的 C++ OpenSSL 哈希值不正确

标签 c++ hash openssl c++17 c++14

我想使用 OpenSSL 库计算 C++ 中任何给定文件的 Sha1。

我已经阅读了互联网上所有关于这样做的文章(包括所有来自 stackoverflow 的文章)近 3 天。

最后我让我的程序运行起来,但是任何给定文件生成的哈希值都不是应有的样子。

我的代码在某种程度上与发现的here类似。和 here但在我编写的程序中更易于阅读和进一步使用。

另外,我想使用 C++ 代码而不是 C 代码,因为它们在上面的链接中编写,其次,它们使用:

SHA256_Init(&context);
 SHA256_Update(&context, (unsigned char*)input, length);
 SHA256_Final(md, &context);

在新的/当前的 OpenSSL 版本(我认为是 3.0 左右)中不再可用。

所以,我认为这个问题将帮助我观察到的许多其他读者遇到与我使用新 OpenSSL 版本相同的问题,并且不能再使用旧的代码示例。

这是我的 C++ 代码,它是为了按 block 读取大文件而不将它们加载到内存中而创建的(希望这对这篇文章的 future 读者有所帮助,因为它有许多有用的行,但正如您将看到的那样,它并没有完全工作):

bool hashFullFile(const std::string& FilePath, std::string &hashed, std::string &hash_type) {
    bool success = false;
    EVP_MD_CTX *context = EVP_MD_CTX_new();
    //read file by chuncks:
    const int BUFFER_SIZE = 1024;
    std::vector<char> buffer (BUFFER_SIZE + 1, 0);

    // check if the file to read from exists and if so read the file in chunks
    std::ifstream fin(FilePath, std::ifstream::binary | std::ifstream::in);

    if (hash_type == "SHA1") {
        if (context != NULL) {
            if (EVP_DigestInit_ex(context, EVP_sha1(), NULL)) {
                while (fin.good()){


                    fin.read(buffer.data(), BUFFER_SIZE);
                    std::streamsize s = ((fin) ? BUFFER_SIZE : fin.gcount());
                    buffer[s] = 0;
                    //convert vector of chars to string:
                    std::string str(buffer.data());
                    if (!EVP_DigestUpdate(context, str.c_str(), str.length())) {
                        fprintf(stderr, "Error while digesting file.\n");
                        return false;
                    }


                }
                unsigned char hash[EVP_MAX_MD_SIZE];
                unsigned int lengthOfHash = 0;
                if (EVP_DigestFinal_ex(context, hash, &lengthOfHash)) {
                    std::stringstream ss;
                    for (unsigned int i = 0; i < lengthOfHash; ++i) {
                        ss << std::hex << std::setw(2) << std::setfill('0') << (int) hash[i];
                    }

                    hashed = ss.str();
                    success = true;
                }else{
                    fprintf(stderr, "Error while finalizing digest.\n");
                    return false;
                }
            }else{
                fprintf(stderr, "Error while initializing digest context.\n");
                return false;
            }
            EVP_MD_CTX_free(context);
        }else{
            fprintf(stderr, "Error while creating digest context.\n");
            return false;
        }
    }
    fin.close();
    return success;
}

我在主函数中像这样使用它:

std::string myhash;
std::string myhash_type = "SHA1";
hashFullFile(R"(C:\Users\UserName\data.bin)", myhash, myhash_type);
cout<<myhash<<endl;

问题是对于给定的文件,它计算哈希值:

例如169ed28c9796a8065f96c98d205f21ddac11b14e 作为哈希输出,但同一文件具有哈希:

 openssl dgst -sha1 data.bin
SHA1(data.bin)= 1927f720a858d0c3b53893695879ae2a7897eedb

由 Openssl 命令行以及互联网上的任何站点生成。

我不知道我做错了什么,因为我的代码似乎是正确的。

请帮忙。

提前非常感谢您!

最佳答案

您错过了 EVP API 尝试的完成计算。中间字符串的使用也是不必要的。最后,该函数应以字节 vector 的形式返回摘要。让调用者做他们想做的事。

下面显示了同时使用 EVP API 和 BIO 链的示例。

#include <iostream>
#include <fstream>
#include <algorithm>
#include <array>
#include <vector>
#include <memory>

#include <openssl/evp.h>
#include <openssl/sha.h>

namespace
{
    struct Delete
    {
        void operator()(BIO * p) const
        {
            BIO_free(p);
        }

        void operator()(EVP_MD_CTX *p) const
        {
            EVP_MD_CTX_free(p);
        }
    };

    using BIO_ptr = std::unique_ptr<BIO, Delete>;
    using EVP_MD_CTX_ptr = std::unique_ptr<EVP_MD_CTX, Delete>;
}

std::vector<uint8_t> hashFileEVP(const std::string &fname, std::string const &mdname = "sha1")
{
    // will hold the resulting digest
    std::vector<uint8_t> md;

    // set this to however big you want the chunk size to be
    static constexpr size_t BUFFER_SIZE = 1024;
    std::array<char, BUFFER_SIZE> buff;

    // get the digest algorithm by name
    const EVP_MD *mthd = EVP_get_digestbyname(mdname.c_str());
    if (mthd)
    {
        std::ifstream inp(fname, std::ios::in | std::ios::binary);
        if (inp.is_open())
        {
            EVP_MD_CTX_ptr ctx{EVP_MD_CTX_new()};
            EVP_DigestInit_ex(ctx.get(), mthd, nullptr);

            while (inp.read(buff.data(), BUFFER_SIZE).gcount() > 0)
                EVP_DigestUpdate(ctx.get(), buff.data(), inp.gcount());

            // size output vector
            unsigned int mdlen = EVP_MD_size(mthd);
            md.resize(mdlen);

            // general final digest
            EVP_DigestFinal_ex(ctx.get(), md.data(), &mdlen);
        }
    }
    return md;
}

std::vector<uint8_t> hashFileBIO(std::string const &fname, std::string const &mdname = "sha1")
{
    // the fixed-size read buffer
    static constexpr size_t BUFFER_SIZE = 1024;

    // will hold the resulting digest
    std::vector<uint8_t> md;

    // select this however you want.
    const EVP_MD *mthd = EVP_get_digestbyname(mdname.c_str());
    if (mthd)
    {
        // open the file and a message digest BIO
        BIO_ptr bio_f(BIO_new_file(fname.c_str(), "rb"));
        BIO_ptr bio_md(BIO_new(BIO_f_md()));
        BIO_set_md(bio_md.get(), mthd);

        // chain the bios together. note this bio is NOT
        //  held together with a smart pointer; all the
        //  bios in the chain are.
        BIO *bio = BIO_push(bio_md.get(), bio_f.get());

        // read through file one buffer at a time.
        std::array<char, BUFFER_SIZE> buff;
        while (BIO_read(bio, buff.data(), buff.size()) > 0)
            ; // intentionally empty

        // size output buffer
        unsigned int mdlen = EVP_MD_size(mthd);
        md.resize(mdlen);

        // read final digest from md bio.
        BIO_gets(bio_md.get(), (char *)md.data(), mdlen);
    }
    return md;
}

// convert a vector of byte to std::string
std::string bin2hex(std::vector<uint8_t> const& bin)
{
    std::string res;
    size_t len = 0;
    if (OPENSSL_buf2hexstr_ex(nullptr, 0, &len, bin.data(), bin.size(), 0) != 0)
    {
        res.resize(len);
        OPENSSL_buf2hexstr_ex(&res[0], len, &len, bin.data(), bin.size(), 0);
    }
    return res;
}

int main()
{    
    OpenSSL_add_all_digests();

    // i have this on my rig. use whatever you want
    //  or get the name from argv or some such.
    static const char fname[] = "dictionary.txt";

    auto md1 = hashFileEVP(fname);
    auto md1str = bin2hex(md1);
    std::cout << "hashed with EVP API\n";
    std::cout << md1str << '\n';

    auto md2 = hashFileBIO(fname);
    auto md2str = bin2hex(md1);
    std::cout << "hashed with BIO chain\n";
    std::cout << md2str << '\n';
}

输出

hashed with EVP API
0A97D663ADA2E039FD904846ABC5361291BD2D8E
hashed with BIO chain
0A97D663ADA2E039FD904846ABC5361291BD2D8E

openssl 命令行的输出

craig@rogue1 % openssl dgst -sha1 dictionary.txt
SHA1(dictionary.txt)= 0a97d663ada2e039fd904846abc5361291bd2d8e

请注意,所有三种情况下的摘要都是相同的。

关于文件的 C++ OpenSSL 哈希值不正确,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/71718818/

相关文章:

openssl - 如何查看 PKCS 7 文件中签署了哪些属性?

c++ - 在这里真的有必要将 std::move 放在 lambda 中吗?

c++ - 将 QStringList 中的值设置为 QComboBox

C++ Qt 无法正确解析 JSON 数组

hash - 非唯一 str : md5 or sha1 的冲突最少

c# - DotNet Core - 加密哈希函数返回不一致的结果

linux - 如何从源代码升级 CentOS 6.5/Linux/Unix 中的 OpenSSL?

openssl - 从现有证书生成 CSR

c++ - 为什么这段代码在实际的 BCM2837 (pi 3) 上运行时会挂起,但在 qemu 上运行良好

c++ - 目录校验和