如何在 C++ 中可移植地计算 sha1 哈希?

Posted

技术标签:

【中文标题】如何在 C++ 中可移植地计算 sha1 哈希?【英文标题】:How to portably compute a sha1 hash in C++? 【发布时间】:2015-04-13 21:04:48 【问题描述】:

目标是计算一个或多个缓冲区的 SHA1 哈希值作为 C++ 程序的一部分。

【问题讨论】:

您需要询问您的具体问题。陈述你的任务并不是一个真正的问题。这就是为什么你的票数越来越少。 @DavidSchwartz 可能会出现更具体的问题,以请求 SO 之外的第 3 方资源。为此已标记为关闭。 @DavidSchwartz,嗯,非常具体的问题在标题中,不是吗?你的意思是我应该删除需求块吗?我只是添加它以使其更加具体。我真的没有看到我的问题与 - 说 - ***.com/questions/34490/… ... 这根本不是一个具体的问题。那是一个任务。甚至不清楚您是在寻找编写代码的帮助、编写代码的人,还是向您指出现有代码的人。如果不是后者,这不是问题。如果是后者,则可以通过搜索引擎轻松回答。 我也有同样的问题。我用了一个搜索引擎。它把我带到了这里。我得到了我的答案。我不明白这有什么问题。 【参考方案1】:

我不确定使用 boost 的 UUID 的人是否会正确地在哈希值中使用前导零(您的字符串应该始终具有相同的长度 afaik),所以这是上面示例的简化版本,可以做到这一点:

#include <cstdio>
#include <string>
#include <boost/uuid/sha1.hpp>

std::string get_sha1(const std::string& p_arg)

    boost::uuids::detail::sha1 sha1;
    sha1.process_bytes(p_arg.data(), p_arg.size());
    unsigned hash[5] = 0;
    sha1.get_digest(hash);

    // Back to string
    char buf[41] = 0;

    for (int i = 0; i < 5; i++)
    
        std::sprintf(buf + (i << 3), "%08x", hash[i]);
    

    return std::string(buf);

【讨论】:

您的意思是您不确定 boost hex 算法是否打印前导零?这个minimal example shows that it does。顺便说一句,您的 sprintf 方法在小端架构上不会产生相同的结果。这意味着如果您像这样使用 sprintf ,则不能进行字节交换。另一个版本交换字节,因为十六进制算法在 4 字节整数上逐字节迭代。 是的,你是对的,我删除了 bswap32() 调用,谢谢。现在产生的结果与 Python 的 hashlib.sha1(str).hexdigest() 相同。 事实证明,Boost 已经为 SHA1 哈希字符串获得了一个更高级的 API,它允许更简洁的代码:***.com/a/28489154/427158【参考方案2】:

Qt 库从 4.3 版开始包含类 QCryptographicHash,它支持各种散列算法,包括 SHA1。尽管 Qt 的可移植性可以说不如 OpenSSL,但至少对于已经依赖 Qt 的项目来说,QCryptographicHash 是计算 SHA1 哈希的明显方法。

计算文件的 SHA1 哈希的示例程序:

#include <QCryptographicHash>
#include <QByteArray>
#include <QFile>
#include <iostream>
#include <stdexcept>
using namespace std;
int main(int argc, char **argv)

  try 
    if (argc < 2)
      throw runtime_error(string("Call: ") + *argv + string(" FILE"));
    const char *filename = argv[1];
    QFile file(filename);
    if (!file.open(QIODevice::ReadOnly | QIODevice::Unbuffered))
      throw runtime_error("Could not open: " + string(filename));
    QCryptographicHash hash(QCryptographicHash::Sha1);
    vector<char> v(128*1024);
    for (;;) 
      qint64 n = file.read(v.data(), v.size());
      if (!n)
        break;
      if (n == -1)
        throw runtime_error("Read error");
      hash.addData(v.data(), n);
    
    QByteArray h(hash.result().toHex());
    cout << h.data() << '\n';
   catch (const exception &e) 
    cerr << "Error: " << e.what() << '\n';
    return 1;
  
  return 0;

使用的 Qt 类都是 Qt 核心库的一部分。一个示例 cmake 构建文件:

cmake_minimum_required(VERSION 2.8.11)
project(hash_qt CXX)
set(CMAKE_CXX_FLAGS "$CMAKE_CXX_FLAGS -Wall -std=c++11")
find_package(Qt5Core)
add_executable(hash_qt hash_qt.cc)
target_link_libraries(hash_qt Qt5::Core)

【讨论】:

【参考方案3】:

Boost 提供了一个简单的 API 来计算字符串的 SHA1 哈希:

#include <iostream>
#include <string>

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

int main(int argc, char **argv)

  if (argc < 2) 
      std::cerr << "Call: " << *argv << " STR\n";
      return 1;
  

  boost::compute::detail::sha1 sha1  argv[1] ;
  std::string s  sha1 ;

  std::cout << s << '\n';

  return 0;

不过,该 API 对 Boost Compute library 是私有的,因为它是详细名称空间的一部分。这意味着它没有任何稳定性保证。


Boost 还提供了一个 SHA1 散列类作为 Boost Uuid Library 的一部分,其 API 更适合散列任意二进制输入,例如文件。虽然它是 detail 命名空间的一部分,这意味着它是一种库私有的,但它存在多年且稳定。

一个计算文件的 SHA1 哈希并将其打印到标准输出的小示例:

前奏:

#include <boost/uuid/detail/sha1.hpp>
#include <boost/predef/other/endian.h>
#include <boost/endian/conversion.hpp>
#include <boost/algorithm/hex.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <iostream>
#include <vector>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;

主要功能:


  if (argc < 2)  cerr << "Call: " << *argv << " FILE\n"; return 1; 
  const char *filename = argv[1];
  int fd = open(filename, O_RDONLY);
  if (fd == -1)  cerr << "open: " << strerror(errno) << ")\n"; return 1; 
  vector<char> v(128*1024);
  boost::uuids::detail::sha1 sha1;
  for (;;) 
    ssize_t n = read(fd, v.data(), v.size());
    if (n == -1) 
      if (errno == EINTR) continue;
      cerr << "read error: " << strerror(errno) << '\n';
      return 1; 
    
    if (!n) break;
    sha1.process_bytes(v.data(), n);
   
  boost::uuids::detail::sha1::digest_type hash;
  sha1.get_digest(hash);
#ifdef  BOOST_ENDIAN_BIG_BYTE
  for (unsigned i = 0; i < sizeof hash / sizeof hash[0]; ++i)
    boost::endian::endian_reverse_inplace(hash[i]);
#endif
  boost::algorithm::hex(boost::make_iterator_range(
        reinterpret_cast<const char*>(hash),
        reinterpret_cast<const char*>(hash) + sizeof hash),
        std::ostream_iterator<char>(cout)); cout << '\n';
  int r = close(fd);
  if (r == -1)  cerr << "close error: " << strerror(errno) << '\n';
                 return 1; 
  return 0;



Boost 的使用部分不会对任何 boost 共享库创建依赖关系。由于 Boost 非常便携并且可用于各种架构,因此使用 Boost 计算 SHA1 哈希值也非常便携。

【讨论】:

【参考方案4】:

OpenSSL 库是可移植的、高效的、实现 SHA1 支持以及其他有用的功能。在大多数平台上都可用...

https://www.openssl.org/docs/crypto/sha.html

【讨论】:

【参考方案5】:

OpenSSL 库包含一个用于不同哈希方法的 API,并且非常便携,并且在许多系统上都可以轻松使用。

使用推荐的 OpenSSL EVP API 计算文件的 SHA1 哈希的 C++ 示例:

int main(int argc, char **argv)

  try 
    if (argc < 2) throw runtime_error(string("Call: ") + *argv
                                    + string(" FILE"));
    const char *filename = argv[1];
    int fd = open(filename, O_RDONLY);
    if (fd == -1) throw runtime_error("Could not open " + string(filename)
                                    + " (" + string(strerror(errno)) + ")");
    BOOST_SCOPE_EXIT(&fd)  close(fd);  BOOST_SCOPE_EXIT_END
    const EVP_MD *md = EVP_sha1();
    if (!md) throw logic_error("Couldn't get SHA1 md");
    unique_ptr<EVP_MD_CTX, void (*)(EVP_MD_CTX*)> md_ctx(EVP_MD_CTX_create(),
        EVP_MD_CTX_destroy);
    if (!md_ctx) throw logic_error("Couldn't create md context");
    int r = EVP_DigestInit_ex(md_ctx.get(), md, 0);
    if (!r) throw logic_error("Could not init digest");
    vector<char> v(128*1024);
    for (;;) 
      ssize_t n = read(fd, v.data(), v.size());
      if (n == -1) 
        if (errno == EINTR)
          continue;
        throw runtime_error(string("read error: ") + strerror(errno));
      
      if (!n) 
        break;
      int r = EVP_DigestUpdate(md_ctx.get(), v.data(), n);
      if (!r) throw logic_error("Digest update failed");
    
    array<unsigned char, EVP_MAX_MD_SIZE> hash;
    unsigned int n =  0;
    r = EVP_DigestFinal_ex(md_ctx.get(), hash.data(), &n);
    if (!r) throw logic_error("Could not finalize digest");
    boost::algorithm::hex(boost::make_iterator_range( 
          reinterpret_cast<const char*>(hash.data()),
          reinterpret_cast<const char*>(hash.data()+n)),
          std::ostream_iterator<char>(cout));
    cout << '\n';
   catch (const exception &e) 
    cerr << "Error: " << e.what() << '\n';
    return 1;
   
  return 0; 

例子的前奏:

#include <openssl/evp.h>
#include <boost/algorithm/hex.hpp>
#include <boost/range/iterator_range_core.hpp>
#include <boost/scope_exit.hpp>
#include <iostream>
#include <vector>
#include <array>
#include <memory>
#include <string>
#include <stdexcept>
#include <sys/types.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <unistd.h>
#include <string.h>
using namespace std;

对于 EVP API,程序必须与 libcrypto 链接,例如:

g++ -g -std=c++11 sha1_example.cc -lcrypto

【讨论】:

【参考方案6】:

Botan 库实现了加密算法的“厨房水槽”,包括 SHA1(当然)。它可以在提供最新 C++ 编译器的各种系统之间移植。

使用 Botan 用于构建管道的高级流式 C++ API 直接计算 SHA1 哈希值并将其作为十六进制字符串获取。

计算文件的 SHA1 哈希的示例:

#include <botan/pipe.h>
#include <botan/basefilt.h>
#include <botan/filters.h>
#include <botan/data_snk.h>
using namespace Botan;
#include <fstream>
#include <iostream>
using namespace std;

int main(int argc, char **argv)

  try 
    if (argc < 2)
      throw runtime_error(string("Call: ") + *argv + string(" FILE"));
    const char *filename = argv[1];

    ifstream in(filename, ios::binary);
    in.exceptions(ifstream::badbit);
    Pipe pipe(new Chain(new Hash_Filter("SHA-1"),
          new Hex_Encoder(Hex_Encoder::Lowercase)),
        new DataSink_Stream(cout));
    pipe.start_msg();
    in >> pipe;
    pipe.end_msg();
   catch (const exception &e) 
    cerr << "Error: " << e.what() << '\n';
    return 1;
  
  return 0;

当哈希值作为字符串处理时,可以使用ostringstream(而不是cout)作为数据接收器流。

根据目标系统/发行版,头文件可能放置在稍微不寻常的位置,并且库可能包含稍微意外的后缀(例如在 Fedora 21 上)。以下 cmake sn-p 说明了这一点:

cmake_minimum_required(VERSION 2.8.11)
project(hash CXX)
set(CMAKE_CXX_FLAGS "$CMAKE_CXX_FLAGS -Wall -std=c++11")
find_library(LIB_BOTAN NAMES botan botan-1.10)
find_path(HEADER_BOTAN NAMES botan/pipe.h PATH_SUFFIXES botan-1.10)
add_executable(hash_botan hash_botan.cc)
set_property(TARGET hash_botan PROPERTY INCLUDE_DIRECTORIES $HEADER_BOTAN)
target_link_libraries(hash_botan $LIB_BOTAN)

【讨论】:

【参考方案7】:

Crypto++ 库是一个可移植的 C++ 库,其中包含多种加密算法,包括 SHA1 等多种哈希算法。

API 提供各种源和接收器类,其中可以附加一堆转换。

计算文件的 SHA1 哈希的示例:

#include <cryptopp/files.h>
#include <cryptopp/filters.h>
#include <cryptopp/hex.h>
#include <cryptopp/sha.h>
using namespace CryptoPP;
#include <iostream>
using namespace std;

int main(int argc, char **argv)

  try 
    if (argc < 2)
      throw runtime_error(string("Call: ") + *argv + string(" FILE"));
    const char *filename = argv[1];

    SHA1 sha1;
    FileSource source(filename, true,
        new HashFilter(sha1,
          new HexEncoder(new FileSink(cout), false, 0, ""),
          false)
        );

   catch (const exception &e) 
    cerr << "Error: " << e.what() << '\n';
    return 1;
  
  return 0;

可以通过例如:g++ -Wall -g -std=c++11 hash_cryptopp.cc -lcryptopp进行编译

Crypto++ 通过几个附加的转换将内容从源“泵送”到接收器中。除了FileSink,还可以使用其他接收器,例如StringSinkTemplate 用于直接写入字符串对象。

附加的对象是引用计数的,因此它们会在范围退出时自动销毁。

【讨论】:

以上是关于如何在 C++ 中可移植地计算 sha1 哈希?的主要内容,如果未能解决你的问题,请参考以下文章

如何在派生类中可移植地初始化继承的模板化 POD 结构?

如何(可移植地)使用 C++ 类层次结构和动态链接库

git 如何计算文件哈希?

如何使用 SecureString 创建 SHA1 或 SHA512 哈希?

可移植地识别非标准 C++?

Mono不会将SHA1识别为RSA签名的哈希算法