Criptografía C ++: Parte 2- HMAC

Aug 26 2020

Buscando bibliotecas criptográficas modernas.
No se pudo encontrar nada bueno.

Sé que probablemente hice todo mal, así que trabaja conmigo aquí. Habrá cuatro revisiones diferentes para cuatro estructuras que se basan entre sí:

  1. Hashing
  2. Clave hash
  3. Clave de contraseña
  4. Respuesta al desafío salado

Las estructuras de datos y la implementación que se presentan en estas preguntas se basan en RFC2104 y en esta publicación sobre codeproject .

Esta revisión es para una implementación de HMAC. Esta es una técnica para hacer hash de contraseñas usando una clave.

Ejemplo de uso:

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

Respuestas

1 G.Sliepen Aug 27 2020 at 04:33

Evite usar el mismo tipo para almacenar los resultados de resúmenes simples y HMAC

No puede (o al menos nunca debería poder) comparar un HMAC con un resumen simple. Así que sería bueno si el sistema de tipos pudiera detectar ese posible error. En lugar de tener una DigestStore<Hash>clase que se usa tanto para resúmenes simples como para HMAC, solo tendría Digest<Hash>y HMAC<Hash>cada uno almacenará su propio resultado directamente.

Permitir agregar datos al HMAC en varios pasos

Como se mencionó en la revisión de la parte 1, no es raro tener que agregar múltiples datos no contiguos para agregarlos al HMAC, de modo que tenga una función de miembro add()que pueda actualizar el HMAC. Esto significaría dividir la creación del HMAC en tres partes:

  1. El material clave se prepara como parte del constructor.
  2. El mensaje se agrega al hash, ya sea de una vez o usando múltiples llamadas a funciones
  3. Se calcula el valor final

Estructuraría la clase así:

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

También es posible que desee agregar alguna forma de evitar finish()que lo llamen más de una vez.

Evite operaciones inseguras

Tus propias palabras:

Odio leer estos proyectos de C ++ mal escritos (por eso comencé este truco) que son envoltorios de mierda alrededor de C en lugar de usar una buena seguridad de tipos y buenas interfaces y técnicas limpias de C ++.

Quieres un buen tipo de seguridad, pero también creo que quieres una buena seguridad en general. La creación de funciones inseguras va en contra de ese objetivo. Si almacena los resultados de un hash en un Digestobjeto y tiene una forma de obtener una referencia constante a los datos que almacena, entonces no necesita una hashUnsafe()función.