C ++ Crypto: Parte 2- HMAC
Procurando por bibliotecas Crypto modernas.
Não foi possível encontrar nada de bom.
Eu sei que provavelmente fiz tudo errado, então trabalhe comigo aqui. Haverá quatro análises diferentes para quatro estruturas construídas umas sobre as outras:
- Hashing
- Hashed Key
- Chave de senha
- Salted Challenge Response
As estruturas de dados e a implementação apresentadas nessas questões são baseadas na RFC2104 e nesta postagem no codeproject .
Esta revisão é para uma implementação do HMAC. Esta é uma técnica de hash de senha usando uma chave.
Exemplo 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
Respostas
Evite usar o mesmo tipo para armazenar os resultados de resumos simples e HMACs
Você não pode (ou pelo menos nunca deveria poder) comparar um HMAC a um resumo simples. Portanto, seria bom se o sistema de tipos pudesse detectar esse erro potencial. Em vez de ter uma DigestStore<Hash>classe que é usada para resumos simples e HMACs, eu teria apenas Digest<Hash>e HMAC<Hash>cada um armazenasse seu próprio resultado diretamente.
Permitir adicionar dados ao HMAC em várias etapas
Conforme mencionado na revisão da parte 1, não é incomum ter que adicionar várias partes descontíguas de dados a serem adicionadas ao HMAC, portanto, tenha uma função de membro add()que possa atualizar o HMAC. Isso significaria dividir a criação do HMAC em três partes:
- O material principal é preparado como parte do construtor
- A mensagem é adicionada ao hash, de uma só vez ou usando várias chamadas de função
- O valor final é calculado
Eu estruturaria a classe assim:
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();
}
};
Você também pode querer adicionar alguma forma de evitar que finish()seja chamado mais de uma vez.
Evite operações inseguras
Suas próprias palavras:
Eu simplesmente odeio ler esses projetos C ++ mal escritos (e é por isso que comecei este hack) que são invólucros de baixa qualidade em torno de C em vez de usar boa segurança de tipo e interfaces e técnicas C ++ limpas.
Você deseja uma boa segurança de tipo, mas também acho que deseja uma boa segurança em geral. Criar funções inseguras vai contra esse objetivo. Se você armazenar os resultados de um hash em um Digestobjeto e tiver uma maneira de obter uma referência const para os dados que ele armazena, não precisará de uma hashUnsafe()função.