C ++ Crypto: Część 2 - HMAC
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ą:
- Haszowanie
- Hashed Key
- Klucz hasła
- 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
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:
- Kluczowy materiał jest przygotowywany w ramach konstruktora
- Wiadomość jest dodawana do skrótu za jednym razem lub przy użyciu wielu wywołań funkcji
- 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.