C ++ Crypto: Teil 2 - HMAC

Aug 26 2020

Suchen Sie nach modernen Crypto-Bibliotheken.
Konnte nichts Gutes finden.

Ich weiß, dass ich das wahrscheinlich alles falsch gemacht habe, also arbeite hier mit mir. Es gibt vier verschiedene Überprüfungen für vier Strukturen, die aufeinander aufbauen:

  1. Hashing
  2. Hashed Key
  3. Passwortschlüssel
  4. Antwort auf gesalzene Herausforderung

Die in diesen Fragen vorgestellten Datenstrukturen und Implementierungen basieren auf RFC2104 und diesem Beitrag zum Codeprojekt .

Diese Überprüfung ist für eine Implementierung von HMAC. Dies ist eine Technik zum Hashing von Passwörtern mit einem Schlüssel.

Anwendungsbeispiel:

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

Antworten

1 G.Sliepen Aug 27 2020 at 04:33

Verwenden Sie nicht denselben Typ, um die Ergebnisse von einfachen Digests und HMACs zu speichern

Sie können (oder sollten zumindest nie in der Lage sein) einen HMAC mit einem einfachen Digest vergleichen. Es wäre also gut, wenn das Typsystem diesen potenziellen Fehler erkennen könnte. Anstatt eine DigestStore<Hash>Klasse zu haben, die sowohl für einfache Digests als auch für HMACs verwendet wird, hätte ich nur Digest<Hash>und HMAC<Hash>jedes speichert sein eigenes Ergebnis direkt.

Ermöglichen Sie das Hinzufügen von Daten zum HMAC in mehreren Schritten

Wie in der Überprüfung für Teil 1 erwähnt, ist es nicht ungewöhnlich, dass mehrere, nicht zusammenhängende Daten zum HMAC hinzugefügt werden müssen, sodass eine Mitgliedsfunktion vorhanden ist add(), mit der der HMAC aktualisiert werden kann. Dies würde bedeuten, die Schaffung des HMAC in drei Teile aufzuteilen:

  1. Das Schlüsselmaterial wird als Teil des Konstruktors vorbereitet
  2. Die Nachricht wird dem Hash entweder auf einmal oder mithilfe mehrerer Funktionsaufrufe hinzugefügt
  3. Der Endwert wird berechnet

Ich würde die Klasse so strukturieren:

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

Möglicherweise möchten Sie auch eine Möglichkeit hinzufügen, um zu verhindern, dass Sie finish()mehrmals aufgerufen werden.

Vermeiden Sie unsichere Vorgänge

Deine eigenen Worte:

Ich hasse es einfach, diese schlecht geschriebenen C ++ - Projekte zu lesen (weshalb ich diesen Hack gestartet habe), die beschissene Wrapper um C sind, anstatt gute Typensicherheit und schöne, saubere C ++ - Schnittstellen und -Techniken zu verwenden.

Sie wollen eine gute Typensicherheit, aber ich denke auch, dass Sie im Allgemeinen eine gute Sicherheit wollen. Das Erstellen unsicherer Funktionen widerspricht diesem Ziel. Wenn Sie die Ergebnisse eines Hashs in einem DigestObjekt speichern und eine Möglichkeit haben, einen konstanten Verweis auf die darin gespeicherten Daten zu erhalten, benötigen Sie keine hashUnsafe()Funktion.