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

C++ OpenSSL hash of the file is not the right one

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

我已经阅读了互联网上关于执行此操作的任何文章(也包括来自 Whosebug 的所有文章)将近 3 天。

我的程序终于可以运行了,但是任何给定文件的生成散列都不是它应该的那样。

我的代码与找到的代码有些相似 and here 但更易于阅读和在我编写的程序中进一步使用。

此外,我想使用 C++ 代码而不是上面链接中所写的 C 代码,其次,他们使用:

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

在 new/current OpenSSL 版本(我认为是 3.0 左右)中不再可用。

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

这是我的 C++ 代码,创建它是为了通过 chuncks 读取大文件而不将它们加载到内存中(希望这对以后阅读这篇文章的读者有所帮助 post 因为它有很多有用的行,但它不能完全工作如您所见):

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 命令行生成,也可由 Internet 上的任何站点生成。

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

请帮忙

非常感谢您!

您缺少 EVP API 尝试的最终计算。也不需要使用中间字符串。最后,函数应该 return 将摘要作为字节向量。让来电者随心所欲。

同时使用 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

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