C ++ Crypto: Часть 1 - Хеш
Ищу современные библиотеки Crypto.
Не нашел ничего хорошего.
Я знаю, что, вероятно, сделал все неправильно, поэтому работайте со мной здесь. Будет четыре разных обзора четырех структур, которые опираются друг на друга:
- Хеширование
- Хешированный ключ
- Ключ пароля
- Ответ на соленый вызов
Это код хеширования, который предоставляет простую оболочку для 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
Ответы
Правильное использование заглавных букв в 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!=()класс, который содержит результат дайджеста.
Думаю собираюсь поменять интерфейс.
В настоящее время характер использования:
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), но нам нужен хороший вариант использования, чтобы это работало лучше.
Пока не уверен, как это будет работать. Пожалуйста, дайте подсказку, если у вас есть идея.