C ++ Crypto: Część 2 - HMAC

Aug 26 2020

Rozglądanie się za nowoczesnymi bibliotekami Crypto.
Nie udało się znaleźć nic dobrego.

Wiem, że prawdopodobnie zrobiłem to wszystko źle, więc pracuj ze mną tutaj. Będą cztery różne przeglądy czterech struktur, które wzajemnie się uzupełniają:

  1. Haszowanie
  2. Hashed Key
  3. Klucz hasła
  4. Odpowiedź na solone wyzwanie

Struktury danych i implementacja przedstawione w tych pytaniach są oparte na RFC2104 i tym poście w codeproject .

Ta recenzja dotyczy implementacji HMAC. Jest to technika haszowania hasła za pomocą klucza.

Przykład użycia:

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

Odpowiedzi

1 G.Sliepen Aug 27 2020 at 04:33

Unikaj używania tego samego typu do przechowywania wyników zwykłego trawienia i HMAC

Nie możesz (a przynajmniej nigdy nie powinieneś być w stanie) porównać HMAC do zwykłego podsumowania. Byłoby więc dobrze, gdyby system typów mógł wyłapać ten potencjalny błąd. Zamiast DigestStore<Hash>klasy, która jest używana zarówno do zwykłych podsumowań, jak i HMAC, po prostu miałbym Digest<Hash>i HMAC<Hash>każdy zapisałby bezpośrednio swój własny wynik.

Zezwalaj na dodawanie danych do HMAC w wielu krokach

Jak wspomniano w przeglądzie części 1, nierzadko trzeba dodawać wiele nieciągłych fragmentów danych do HMAC, więc należy mieć funkcję składową, add()która może aktualizować HMAC. Oznaczałoby to podzielenie tworzenia HMAC na trzy części:

  1. Kluczowy materiał jest przygotowywany w ramach konstruktora
  2. Wiadomość jest dodawana do skrótu za jednym razem lub przy użyciu wielu wywołań funkcji
  3. Ostateczna wartość jest obliczana

Skonstruowałbym klasę w następujący sposób:

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

Możesz także chcieć dodać sposób, aby uniknąć finish()wielokrotnego wywoływania.

Unikaj niebezpiecznych operacji

Własnymi słowami:

Po prostu nienawidzę czytać tych źle napisanych C ++ (dlatego zacząłem ten hack) projektów, które są kiepskimi opakowaniami wokół C, zamiast używać dobrego bezpieczeństwa typów i ładnych, czystych interfejsów i technik C ++.

Chcesz dobrego bezpieczeństwa typu, ale myślę również, że chcesz ogólnie dobrego bezpieczeństwa. Tworzenie niebezpiecznych funkcji jest sprzeczne z tym celem. Jeśli przechowujesz wyniki skrótu w Digestobiekcie i masz sposób na uzyskanie odniesienia do stałej wartości przechowywanych w nim danych, to nie potrzebujesz hashUnsafe()funkcji.