C ++ Crypto: Часть 1 - Хеш

Aug 26 2020

Ищу современные библиотеки Crypto.
Не нашел ничего хорошего.

Я знаю, что, вероятно, сделал все неправильно, поэтому работайте со мной здесь. Будет четыре разных обзора четырех структур, которые опираются друг на друга:

  1. Хеширование
  2. Хешированный ключ
  3. Ключ пароля
  4. Ответ на соленый вызов

Это код хеширования, который предоставляет простую оболочку для SHA-1 и SHA-256, но шаблон достаточно прост, чтобы мы могли расширить его для других механизмов хеширования.

Структуры данных и реализация, представленные в этих вопросах, основаны на RFC2104 и этой публикации на codeproject .

Пример использования:

DigestStore<Sha1>    hash;    // <- destination of hash
Sha1                 hasher;

hasher.hash("This string can be hashsed", hash);

hash.h

#ifndef THORS_ANVIL_CRYPTO_HASH_H
#define THORS_ANVIL_CRYPTO_HASH_H

#ifdef  __APPLE__
#define COMMON_DIGEST_FOR_OPENSSL
#include <CommonCrypto/CommonDigest.h>
#define THOR_SHA1(data, len, dst)       CC_SHA1(data, len, dst)
#define THOR_SHA256(data, len, dst)     CC_SHA256(data, len, dst)
#else
#include <openssl/sha.h>
#define THOR_SHA1(data, len, dst)       SHA1(data, len, dst)
#define THOR_SHA256(data, len, dst)     SHA256(data, len, dst)
#endif

#include <string>
#include <array>

//
// Wrapper for sha1 and sha256 hashing algorithms
//
// Provides a simple wrapper class with the appropriates types and size
// for the resulting "digest" object. Also provides several type safe
// versions of the hashing algorithm to allow multiple know types to
// be safely hashed.

namespace ThorsAnvil::Crypto
{

using Byte      = char unsigned;
using DigestPtr = Byte*;

template<typename Hash>
using Digest = typename Hash::DigestStore;

template<std::size_t size>
class DigestStore
{
    std::array<Byte, size>      data;
    public:
        using iterator = typename std::array<Byte, size>::iterator;

        operator Digest()                           {return &data[0];}
        std::string_view  view()                    {return std::string_view(reinterpret_cast<char const*>(&data[0]), std::size(data));}
        Byte&             operator[](std::size_t i) {return data[i];}
        iterator          begin()                   {return std::begin(data);}
        iterator          end()                     {return std::end(data);}
};

// These versions of the hashing function are good for hashing short
// amounts of text. Use these for passwords and validation hashes
// do not use them for hashing large documents.
struct Sha1
{
    static constexpr std::size_t digestSize = SHA_DIGEST_LENGTH;
    using DigestStore = DigestStore<SHA_DIGEST_LENGTH>;

    void hash(DigestStore& src,             DigestStore& dst)   {THOR_SHA1(src, SHA_DIGEST_LENGTH, dst);}
    void hash(std::string_view src,         DigestStore& dst)   {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
    void hash(std::string const& src,       DigestStore& dst)   {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
    // Use only if you know the destination is large enough!!
    void hashUnsafe(std::string_view src,   DigestPtr dst)      {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
};
struct Sha256
{
    static constexpr std::size_t digestSize = SHA256_DIGEST_LENGTH;
    using DigestStore = DigestStore<SHA256_DIGEST_LENGTH>;

    void hash(DigestStore& src,             DigestStore& dst)   {THOR_SHA256(src, SHA256_DIGEST_LENGTH, dst);}
    void hash(std::string_view src,         DigestStore& dst)   {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
    void hash(std::string const& src,       DigestStore& dst)   {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
    // Use only if you know the destination is large enough!
    void hashUnsafe(std::string_view src,   Digestptr dst)      {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
};

}

#endif

Ответы

3 G.Sliepen Aug 27 2020 at 03:58

Правильное использование заглавных букв в SHA1

Имя алгоритма - SHA1, а не Sha1, поэтому я думаю, что здесь лучше использовать все заглавные буквы. Это упрощает поиск кода для конкретного алгоритма.

Вам нужен только один класс для каждого алгоритма хеширования

Действительно, как вы упомянули в своем собственном ответе, Sha1класс кажется излишним, поскольку он не хранит никакого состояния. Однако вместо создания статических функций внутри Sha1пространства имен вы можете сделать эти функции функциями-членами класса, который содержит фактическое состояние. Это позволяет избежать повторения типа; например:

Sha1::DigestStore digest;
Sha1::hash("Bob", digest)

Становится:

Sha1::DigestStore digest;
digest.hash("Bob");

Sha1::Digest vs. Digest<Sha1>

Я думаю, что иметь пространство имен Sha1с DigestStoreфункциями и внутри - плохой выбор. С SHA1 вы можете делать больше, чем просто создавать простые хэши, например, вы можете захотеть создать HMAC вместо простого хэша. Таким образом, вам придется добавить функции для создания HMAC в каждое пространство имен, реализующее алгоритм хеширования. Это гораздо лучше иметь классы Digestи HMACкоторые шаблонного на хэш - алгоритма.

Разрешить обновление хешей

Написанный вами код выполняет только одноразовые преобразования некоторого ввода в хэш. Однако нередко программы не имеют всех данных, для которых они хотят создать хэш, в единственной непрерывной области памяти. В этих случаях вы хотите написать:

std::ostream output;
Digest<SHA1> digest;

digest.add("Header");
digest.add("Data");
digest.add("Footer");

output << digest.view();

Некоторые алгоритмы дайджеста могут потребовать от вас вызова некоторой функции для вычисления окончательного хеш-значения после добавления всех данных. Вы можете добавить явную finish()функцию или вызвать ее неявно при доступе к результату дайджеста.

Получение результата

Вы храните хэш внутри как файл std::array<std::byte, size>. Это правильный поступок. Я не думаю, что необходимо предоставлять какие-либо функции-члены, кроме той, которая дает вам constссылку на этот массив. Вызывающий абонент должен преобразовать его в любую форму. A std::arrayуже неявно конвертируется в a std::span. И если у вас есть ссылка на массив, легко получить из него начальный и конечный итераторы.

Добавить операторы сравнения

Довольно часто возникает необходимость проверить, идентичны ли два хэша, поэтому было бы полезно, по крайней мере, определить operator==()и operator!=()класс, который содержит результат дайджеста.

1 MartinYork Aug 26 2020 at 05:27

Думаю собираюсь поменять интерфейс.

В настоящее время характер использования:

typename Sha1::DigestStore      digest;
Sha1                            hasher;

hasher.hash("Bob", digest);

Нет необходимости создавать Sha1объект. Я думаю, что лучший интерфейс может заключаться в том, чтобы сделать все методы staticтак, чтобы использование стало:

typename Sha1::DigestStore      digest;

Sha1::hash("Bob", digest);

DigestStoreМожет потребоваться некоторые другие функции доступа. В настоящее время это разрешено, iterationно может быть случай использования, когда у нас есть const_iterator.

Все еще пытаюсь понять, когда лучше всего использовать string_view. К сожалению, он по-прежнему не работает с обычными струнами. Поэтому нам, возможно, придется предоставить способ также извлечь строку из буфера. В этом случае было бы неплохо, если бы мы могли преобразовать данные из DigestStore в строку (что означает не использовать std::array), но нам нужен хороший вариант использования, чтобы это работало лучше.

Пока не уверен, как это будет работать. Пожалуйста, дайте подсказку, если у вас есть идея.