Search code examples
c++opensslshaxxd

How to generate sha256 hashes of hexadecimal binary data in c++?


I need a function in C++ that generate similar result what this command generate in linux.

echo -n "616161616161616" | xxd -r -p | sha256sum -b | awk '{print $1}'

What this command does is: given an hexadump, convert it to binary file format, and display the sha256 hash of the binary file.xxd -r converts the hexadump to binary file

Can anyone help me please ?


Solution

  • First you should know that your command-line version is missing a nibble. I.e., if you send '616161616161616'` through xxd -r -p, then back through xxd, you'll see this:

    echo -n "616161616161616" | xxd -r -p | xxd
    00000000: 6161 6161 6161 61                        aaaaaaa
    

    Note the last nibble 6 is missing. The xxd utility consumes hex in pairs of digits. Once there are no more pairs it stops and emits the final output. Chances are you were expecting behavior that treated the leading nibble as a single octet, and therefore something like this:

    echo -n "0616161616161616" | xxd -r -p | xxd
    00000000: 0616 1616 1616 1616                      ........
    

    In either case, and however you want to handle that, you should nonetheless be made aware of the issue.

    Regarding your actual problem, the OpenSSL library has a fairly rich set of features that let you do most of what you're asking near-trivially. Calculating the digest from the input string is fairly simple:

    #include <iostream>
    #include <vector>
    #include <string>
    
    #include <openssl/evp.h>
    #include <openssl/sha.h>
    #include <openssl/crypto.h>
    
    int main()
    {
        std::string strHex = "616161616161616"; // get this from wherever
    
        // prepend zero-nibble for odd character counts.
        if (strHex.length() % 2)
            strHex.insert(strHex.begin(), '0');
    
        // convert to blob. returns dynamic memory allocated with
        //  OPENSSL_malloc. Use OPENSSL_free to destroy it.
        long len = 0;
        unsigned char *bin = OPENSSL_hexstr2buf(strHex.c_str(), &len);
    
        // digest the blob
        const EVP_MD *md_algo = EVP_sha256();
        unsigned int md_len = EVP_MD_size(md_algo);
        std::vector<unsigned char> md( md_len );
        EVP_Digest(bin, len, md.data(), &md_len, md_algo, nullptr);
    
        // free the input data.
        OPENSSL_free(bin);
    
        // print the buffer to stdout in hex.
        char *hexOut = OPENSSL_buf2hexstr(md.data(), md_len);
        std::cout << hexOut << '\n';
        OPENSSL_free(hexOut);
    
        return 0;
    }
    

    Note the above code takes the approach of prepending a zero-nibble in the case of an odd input length. If that isn't the behavior you want (i.e. if you want the exact same ignore-the-trailing-odd-nibble behavior as your question text), the alternative is to trim the last nibble off (which I seriously doubt is what you want is it removes relevant bits and is a recipe for digest collisions, but your call).

    #include <iostream>
    #include <vector>
    #include <string>
    
    #include <openssl/evp.h>
    #include <openssl/sha.h>
    #include <openssl/crypto.h>
    
    int main()
    {
        std::string strHex = "616161616161616"; // get this from wherever
    
        // prepend zero-nibble for odd character counts.
        if (strHex.length() % 2)
            strHex.pop_back();
    
        // convert to blob. returns dynamic memory allocated with
        //  OPENSSL_malloc. Use OPENSSL_free to destroy it.
        long len = 0;
        unsigned char *bin = OPENSSL_hexstr2buf(strHex.c_str(), &len);
    
        // digest the blob
        const EVP_MD *md_algo = EVP_sha256();
        unsigned int md_len = EVP_MD_size(md_algo);
        std::vector<unsigned char> md( md_len );
        EVP_Digest(bin, len, md.data(), &md_len, md_algo, nullptr);
    
        // free the input data.
        OPENSSL_free(bin);
    
        // print the buffer to stdout in hex.
        char *hexOut = OPENSSL_buf2hexstr(md.data(), md_len);
        std::cout << hexOut << '\n';
        OPENSSL_free(hexOut);
    
        return 0;
    }
    

    In both cases above, the resulting output is preconfigured as colon-separated octets. I.e. the latter example output looks like this:

    E4:62:40:71:4B:5D:B3:A2:3E:EE:60:47:9A:62:3E:FB:A4:D6:33:D2:7F:E4:F0:3C:90:4B:9E:21:9A:7F:BE:60
    

    This is due to the rather-limited feature capability of OPENSSL_buf2hexstr. If this isn't what you want (probably isn't), you could always manually print it yourself using a simple lookup table, processing each octet as two nibbles:

    #include <iostream>
    #include <vector>
    #include <string>
    
    #include <openssl/evp.h>
    #include <openssl/sha.h>
    #include <openssl/crypto.h>
    
    int main()
    {
        std::string strHex = "616161616161616";
    
        // prepend zero-nibble for odd character counts.
        if (strHex.length() % 2)
            strHex.pop_back();
    
        // convert to blob. returns dynamic memory allocated with
        //  OPENSSL_malloc. Use OPENSSL_free to destroy it.
        long len = 0;
        unsigned char *bin = OPENSSL_hexstr2buf(strHex.c_str(), &len);
    
        // digest the blob
        const EVP_MD *md_algo = EVP_sha256();
        unsigned int md_len = EVP_MD_size(md_algo);
        std::vector<unsigned char> md( md_len );
        EVP_Digest(bin, len, md.data(), &md_len, md_algo, nullptr);
    
        // free the input data.
        OPENSSL_free(bin);
    
        // dump as continuous hex string
        static const char alpha[] = "0123456789abcdef";
        for (auto c : md)
        {
            std::cout << alpha[ (c >> 4) & 0xF];
            std::cout << alpha[ c & 0xF];
        }
        std::cout.put('\n');
    
        return 0;
    }
    

    Output

    e46240714b5db3a23eee60479a623efba4d633d27fe4f03c904b9e219a7fbe60
    

    Anyway, longer than I intended, but hope it was what you were looking for.