最快的SHA1实现是什么?

5

我正在寻找最快的SHA1实现,因为我需要计算数百万次。我尝试了boost::uuids::detail::sha1OpenSSL SHA1我发现OpenSSL比boost快2.5倍。我还检查了Crypto++,但它比其他两个要慢得多。以下是我用来检查它们性能的方法:

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;
    }
}

然后我使用随机数据测试这些函数:

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

性能结果

我使用g++ -O3 -std=c++2a编译了所有的代码,得到以下结果。我发现OpenSSL比其他实现更快,而Crypto++最慢:

各种SHA1算法的性能

问题

  • 哪种SHA1实现是最快的?
  • 如何改进我的Crypto++函数?

欢迎提供任何改进性能的反馈。


4
这个链接看起来很有趣。似乎 OpenSSL 已经是最好的选择了。 - Paul Sanders
@PaulSanders谢谢你,Paul。这个链接非常有用。看起来OpenSSL是最快的实现。 - Ali
一些库是否已经针对侧信道(时间)攻击进行了加固,而另一些则没有? - Richard Critten
1
你有两个问题。一个是询问库的推荐,另一个更适合于代码审查。 - cigien
我的最后一次实验证实,OpenSSL是最快的,其中包括Crypto++和一些单源C实现(我忘记了哪些)的失误。 - sehe
1个回答

2
我的最新实验证实,openssl是最快的,包括Crypto++和一些我忘记了哪些的单一源C实现。

关于代码审查类型的部分:

  • boost的“实现”来自细节命名空间,不应依赖它。

  • CryptoPP的使用可能会受益于使用过程式接口而不是每次动态组合管道。具体来说,您不应将其转换为字符串以检查摘要的前n个字节。由于重复分配,这很可能是运行时的主要部分。

    遵循过程式接口也可以让您使用重置/清除成员(从内存中引用)


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

应该是一个简单的任务
    if (!(hash[0] || hash[1] || hash[2] || hash[3]))
        break;

甚至可以这样做:
    if (!std::any_of(hash+0, hash+4, std::identity{}))
        break;

改进的 Bench 代码

结合以上一些内容和更多(主要是关于良好的风格,避免指针错误,展示有效的迭代并允许dump检查摘要的准确性):

在编译器资源管理器上实时查看

#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
}

输出:

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

或在线(没有Crypto ++):

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

这是一个重要的改进,但胜者依旧不变。


我在“timed”中遇到了一点小错误,也许你已经意识到时间是累积的:(现在已经修复了,所以不会再那么令人困惑了! - sehe
你说 OpenSSL "是最快的" ... 但从编译器探索器中我看到:sha1_boost: 10000 in 9.2606ms,sha1_ossl: 10000 in 15.9871ms。我正在尝试找出这些库中哪一个在速度和 CPU 使用方面提供了最佳的 SHA-256 实现 :/ - Mecanik
@NorbertBoros 速度取决于编译器、标志、架构等因素。因此,一定要进行自己的性能分析。令人惊讶的是,Compiler Explorer 上的结果发生了变化。我猜这表明当版本更改时需要重复基准测试。您可以在我的帖子中查看来自2020/08/20的结果,这些结果对于boost显然快了3倍以上。 - sehe
@NorbertBoros我刚刚在我的i7-3770K CPU @ 3.50GHz上对基准测试进行了重复,并始终获得相同的计时:(100'000'000) sha1_boost:78.8104秒 sha1_ossl: 21.8883秒 sha1_cryptoPP: 30.4004秒。这是在Ubuntu 18.04上使用libssl1.1:amd64、boost 1.75、GCC 10、c++17和libcrypto++6 (=5.6.4-8),虽然旧但不应该有太大影响。 - sehe
是的,我想那是有效的。我刚在 AMD Ryzen 上进行了自己的测试,与 Crypto++ 相比,OpenSSL 在 SHA256 方面表现出色。毫无疑问,优胜者是 OpenSSL,因为它似乎更好地进行了优化。 - Mecanik

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接