Criptografía C ++: Parte 2- HMAC
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í:
- Hashing
- Clave hash
- Clave de contraseña
- 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
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:
- El material clave se prepara como parte del constructor.
- El mensaje se agrega al hash, ya sea de una vez o usando múltiples llamadas a funciones
- 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.