C ++ Crypto: Часть 2 - HMAC

Aug 26 2020

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

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

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

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

Этот обзор предназначен для реализации HMAC. Это метод хеширования пароля с помощью ключа.

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

Digest<HMac<Sha1>>      digest;
HMac<Sha1>              hasher;

hasher.hash("This is the Key", "This is the message", digest);

hmac.h

#ifndef THORS_ANVIL_CRYPTO_HMAC_H
#define THORS_ANVIL_CRYPTO_HMAC_H

#include "hash.h"

// HMAC: Keyed-Hashing for Message Authentication RFC-2104
namespace ThorsAnvil::Crypto
{

// Look in hash.h for good examples of THash
// ThorsAnvil::Crypto::Sha1
template<typename THash>
struct HMac
{
    static constexpr std::size_t digestSize = THash::digestSize;
    using Hash        = THash;
    using DigestStore = typename Hash::DigestStore;

    void hash(std::string_view key, std::string_view message, DigestStore& digest)
    {
        Hash    hasher;

        enum { BLOCK_SIZE     = 64 };

        /* STEP 1 */
        std::array<Byte, BLOCK_SIZE>   SHA1_Key{'\x00'};
        if (key.size() > BLOCK_SIZE)
        {
            hasher.hashUnsafe(key, &SHA1_Key[0]);
        }
        else
        {
            std::copy(std::begin(key), std::end(key), &SHA1_Key[0]);
        }

        /* STEP 2 */
        std::string     ipad;
        std::string     opad;

        ipad.reserve(BLOCK_SIZE + std::size(message));
        opad.reserve(BLOCK_SIZE + digestSize);
        ipad.resize(BLOCK_SIZE, '\x36');
        opad.resize(BLOCK_SIZE, '\x5c');

        for (int i=0; i< BLOCK_SIZE; i++)
        {
            ipad[i] ^= SHA1_Key[i];
            opad[i] ^= SHA1_Key[i];
        }

        /* STEP 3 */
        std::copy(std::begin(message), std::end(message), std::back_inserter(ipad));

        /* STEP 4 */
        opad.resize(BLOCK_SIZE + digestSize);
        hasher.hashUnsafe(ipad, reinterpret_cast<Byte*>(&opad[BLOCK_SIZE]));

        /* STEP 5 */
        // Moved XOR of opad to STEP 2

        /* STEP 6 */
        // Don't need to copy the hash of ipad onto opad as we hashed
        // into the correct destination.

        /*STEP 7 */
        hasher.hash(opad, digest);
    }
};

}

#endif

Ответы

1 G.Sliepen Aug 27 2020 at 04:33

Избегайте использования одного и того же типа для хранения результатов простых дайджестов и HMAC.

Вы не можете (или, по крайней мере, никогда не должны иметь возможности) сравнивать HMAC с простым дайджестом. Было бы хорошо, если бы система типов могла уловить эту потенциальную ошибку. Вместо того , чтобы иметь DigestStore<Hash>класс , который используется как для обычных сборников и HMACs, я бы просто Digest<Hash>и HMAC<Hash>каждый магазин своего собственного результата напрямую.

Разрешить добавление данных в HMAC за несколько шагов

Как упоминалось в обзоре к части 1, нередко приходится добавлять несколько несмежных частей данных для добавления в HMAC, поэтому необходимо иметь функцию-член, add()которая может обновлять HMAC. Это означало бы разделение создания HMAC на три части:

  1. Основной материал готовится в рамках конструктора.
  2. Сообщение добавляется в хэш за один раз или с использованием нескольких вызовов функций.
  3. Окончательное значение рассчитывается

Я бы структурировал класс так:

template<typename Hash>
class HMAC {
    Digest<Hash> outer_digest;
    Digest<Hash> inner_digest;

public:
    HMAC(std::string_view key) {
        // Add key XOR opad to outer_digest
        // Add key XOR ipad to inner_digest
    }

    // Convenience constructor to do a one-shot HMAC creation
    HMAC(std::string_view key, std::string_view message): HMAC(key) {
        add(message);
        finish();
    }

    void add(std::string_view message) {
        // Add message to inner_digest
    }

    void finish() {
        // Finish inner_digest, add it to outer_digest
        // Finish outer_digest
    }

    // Something to get the bits out
    const auto &get() {
        return outer_digest.get();
    }
};

Вы также можете добавить способ запретить finish()вызов более одного раза.

Избегайте небезопасных операций

Ваши собственные слова:

Я просто ненавижу читать эти плохо написанные C ++ (именно поэтому я начал этот хак) проекты, которые являются дрянной оберткой вокруг C, вместо того, чтобы использовать хорошую безопасность типов и красивые чистые интерфейсы и методы C ++.

Вам нужна хорошая безопасность типов, но я также думаю, что вам нужна хорошая безопасность в целом. Создание небезопасных функций противоречит этой цели. Если вы храните результаты хеширования в Digestобъекте и имеете способ получить константную ссылку на данные, которые он хранит, тогда вам не нужна hashUnsafe()функция.