Search code examples
c++boostopensslcrypto++

What is the fastest SHA1 implementation?


I am looking for the fastest SHA1 implementation since I have to compute it millions of times. I tried boost::uuids::detail::sha1 and OpenSSL SHA1 and I find OpenSSL 2.5 times faster than boost. I also checked Crypto++ which is much slower than the other two. Here is what I do to check their performances:

OpenSSL SHA1:

#include "openssl/sha.h"

void sha1_ossl (const unsigned char* data) {
    unsigned char  hash[20];
    for (long i=0; i<100000000; ++i) {
      SHA1(data, 64, hash);
    
      if ((unsigned int)hash[0]==0 && (unsigned int)hash[1]==0 && (unsigned int)hash[2]==0 && (unsigned int)hash[3]==0)
          break;
    }
}

Boost::SHA1:

#include <boost/uuid/detail/sha1.hpp>

void sha1_boost (const unsigned char* data) {
    boost::uuids::detail::sha1 sha1;
    unsigned hash[5];
    for (long i=0; i<100000000; ++i) {
        sha1.process_bytes(data, 64);
        sha1.get_digest(hash);
        sha1.reset();
        if (hash[0]==0) break;
    }
}

CryptoPP::SHA1:

#include <cryptopp/sha.h>
#include <cryptopp/hex.h>

void sha1_cryptoPP (const unsigned char* data) {
    std::string data_s (reinterpret_cast<char const*>(data));
    std::string hash_hex;
    CryptoPP::SHA1 sha1;
    for (long i=0; i<100000000; ++i) {
        CryptoPP::StringSource ss(data_s, true, new CryptoPP::HashFilter(sha1, new CryptoPP::HexEncoder(new CryptoPP::StringSink(hash_hex))));
        if (hash_hex.starts_with("00000000")) break;
    }
}

Then I test these functions with a random data:

int main() {
  const unsigned char data[65] = "tJQVfvcjGMNIvJfowXBjmSRcKtSjCcyQvaAdakfEJtgSNZHnOHCjkzGFwngiLFPm";
  sha1_boost (data);
  sha1_ossl (data);
  sha1_cryptoPP (data);
}

Performance Results

I compiled all the codes with g++ -O3 -std=c++2a to get the following results. I found OpenSSL faster than other implementations and Crypto++ the slowest:

Performance of various SHA1 algorithms

Questions

  • What is the fastest SHA1 implementation?
  • How can I improve my Crypto++ function?

Any feedback to improve performance is appreciated.


Solution

  • My last experiments confirmed openssl's was fastest among several (including Crypto++ and some lose single-source C implementations that I forgot which ones)

    Re: the code-review type parts of the question:

    • the boost "implementation" is from a detail namespace and should not be relied on.

    • The CryptoPP take might benefit from using the procedural interface instead of dynamically composing the pipeline each time. Specifically, you should not be converting to string to check the first n bytes of the digest. This is likely the dominant part of the runtime due to the repeated allocations.

      Adhering to the procedural interface may also allow you to to use the reset/clear member (quoting from memory)


        if ((unsigned int)hash[0] == 0 && (unsigned int)hash[1] == 0 &&
            (unsigned int)hash[2] == 0 && (unsigned int)hash[3] == 0)
            break;
    

    Should have been a simple

        if (!(hash[0] || hash[1] || hash[2] || hash[3]))
            break;
    

    Or even

        if (!std::any_of(hash+0, hash+4, std::identity{}))
            break;
    

    Improved Bench Code

    Incorporating some of the above and some more (mostly around good style, avoiding pointer errors, showing effective iterations and allow dump to inspect digests for accuracy):

    Live On Compiler Explorer

    #include "openssl/sha.h"
    #include <boost/uuid/detail/sha1.hpp>
    #include <algorithm>
    #include <iostream>
    #include <iomanip>
    using byte = unsigned char;
    
    #ifndef ONLINE_DEMO
    auto constexpr iterations = 100'000'000;
    #else
    auto constexpr iterations = 10000;
    #endif
    
    static void dump(byte const (&a)[20]) {
        for (unsigned b : a) {
            std::cout << std::setw(2) << std::setfill('0') << std::hex << b;
        }
        std::cout << std::dec << std::endl;
    }
    
    static void dump(uint32_t const (&a)[5]) {
        for (auto b : a) {
            std::cout << std::setw(8) << std::setfill('0') << std::hex << b;
        }
        std::cout << std::dec << std::endl;
    }
    
    long sha1_ossl(std::string_view data) {
        byte hash[20];
        for (long i = 0; i < iterations; ++i) {
            SHA1(reinterpret_cast<byte const*>(data.data()), data.size(), hash);
    
            //dump(hash);
            if (!std::any_of(hash+0, hash+4, std::identity{}))
                return i;
        }
        return iterations;
    }
    
    long sha1_boost(std::string_view data) {
        boost::uuids::detail::sha1 sha1;
        uint32_t hash[5];
        for (long i = 0; i < iterations; ++i) {
            sha1.process_bytes(reinterpret_cast<byte const*>(data.data()), data.size());
            sha1.get_digest(hash);
            sha1.reset();
            //dump(hash);
            if (hash[0] == 0)
                return i;
        }
        return iterations;
    }
    
    #ifndef ONLINE_DEMO
    #include <cryptopp/hex.h>
    #include <cryptopp/sha.h>
    long sha1_cryptoPP(std::string_view data) {
        byte digest[20];
    
        CryptoPP::SHA1 sha1;
        for (long i = 0; i < iterations; ++i) {
            sha1.Restart();
            sha1.Update(reinterpret_cast<byte const*>(data.data()), data.size());
            sha1.Final(digest);
    
            //dump(digest);
            if (!std::any_of(digest+0, digest+4, std::identity{}))
                return i;
        }
        return iterations;
    }
    #endif
    
    #include <chrono>
    using namespace std::chrono_literals;
    
    int main() {
        static auto now = std::chrono::high_resolution_clock::now;
        constexpr static std::string_view data =
            "tJQVfvcjGMNIvJfowXBjmSRcKtSjCcyQvaAdakfEJtgSNZHnOHCjkzGFwngiLFPm";
    
        auto timed = [](auto caption, auto f) {
            auto const start = now();
            auto n = f(data);
            std::cout << caption << ": " << n << " in " << (now()-start)/1.0ms << "ms\n";
        };
    
        timed("sha1_boost", sha1_boost);
        timed("sha1_ossl", sha1_ossl);
    #ifndef ONLINE_DEMO
        timed("sha1_cryptoPP", sha1_cryptoPP);
    #endif
    }
    

    Prints:

    sha1_boost: 100000000 in 85660.5ms
    sha1_ossl: 100000000 in 24652.6ms
    sha1_cryptoPP: 100000000 in 34921.3ms
    

    Or Online (no Crypto++ there):

    sha1_boost: 10000 in 8.71938ms
    sha1_ossl: 10000 in 2.32025ms
    

    That's a significant improvement, but same winner.