Crypto C ++: Partie 2 - HMAC

Aug 26 2020

À la recherche de bibliothèques Crypto modernes.
Je n'ai rien trouvé de bon.

Je sais que j'ai probablement mal fait tout cela, alors travaillez avec moi ici. Il y aura quatre revues différentes pour quatre structures qui s'appuient les unes sur les autres:

  1. Hashing
  2. Clé hachée
  3. Clé de mot de passe
  4. Réponse au défi salé

Les structures de données et l'implémentation présentées dans ces questions sont basées sur RFC2104 et cet article sur codeproject .

Cet examen concerne une mise en œuvre de HMAC. Il s'agit d'une technique de hachage de mot de passe à l'aide d'une clé.

Exemple d'utilisation:

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

Réponses

1 G.Sliepen Aug 27 2020 at 04:33

Évitez d'utiliser le même type pour stocker les résultats des résumés simples et des HMAC

Vous ne pouvez pas (ou du moins ne devriez jamais pouvoir) comparer un HMAC à un condensé simple. Ce serait bien si le système de typage pouvait détecter cette erreur potentielle. Au lieu d'avoir une DigestStore<Hash>classe qui est utilisée à la fois pour les résumés simples et les HMAC, j'aurais juste Digest<Hash>et HMAC<Hash>chacun stocke son propre résultat directement.

Autoriser l'ajout de données au HMAC en plusieurs étapes

Comme mentionné dans la revue de la partie 1, il n'est pas rare d'avoir à ajouter plusieurs éléments de données non contigus à ajouter au HMAC, donc avoir une fonction membre add()qui peut mettre à jour le HMAC. Cela signifierait scinder la création du HMAC en trois parties:

  1. Le matériel clé est préparé dans le cadre du constructeur
  2. Le message est ajouté au hachage, en une seule fois ou en utilisant plusieurs appels de fonction
  3. La valeur finale est calculée

Je structurerais la classe comme ceci:

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

Vous voudrez peut-être également ajouter un moyen d'éviter finish()d'être appelé plus d'une fois.

Évitez les opérations dangereuses

Vos propres mots:

Je déteste juste lire ces projets C ++ mal écrits (c'est pourquoi j'ai commencé ce hack) qui sont des wrappers merdiques autour de C plutôt que d'utiliser une bonne sécurité de type et de belles interfaces et techniques C ++ propres.

Vous voulez une bonne sécurité de type, mais je pense aussi que vous voulez une bonne sécurité en général. La création de fonctions dangereuses va à l'encontre de cet objectif. Si vous stockez les résultats d'un hachage dans un Digestobjet et que vous avez un moyen d'obtenir une référence const aux données qu'il stocke, vous n'avez pas besoin d'une hashUnsafe()fonction.