C ++ Crypto: Parte 2- HMAC
Alla ricerca di moderne librerie Crypto.
Impossibile trovare niente di buono.
So che probabilmente ho sbagliato tutto, quindi lavora con me qui. Ci saranno quattro diverse revisioni per quattro strutture che si basano l'una sull'altra:
- Hashing
- Chiave con hash
- Chiave password
- Risposta alla sfida salata
Le strutture dati e l'implementazione presentate in queste domande si basano su RFC2104 e questo post su codeproject .
Questa recensione è per un'implementazione di HMAC. Questa è una tecnica per l'hashing della password utilizzando una chiave.
Esempio di utilizzo:
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
Risposte
Evita di utilizzare lo stesso tipo per memorizzare i risultati di semplici digest e HMAC
Non puoi (o almeno non dovresti mai essere in grado di) confrontare un HMAC con un semplice digest. Quindi sarebbe positivo se il sistema di tipi potesse rilevare quel potenziale errore. Invece di avere una DigestStore<Hash>classe che viene utilizzata sia per i semplici digest che per gli HMAC, avrei semplicemente Digest<Hash>e HMAC<Hash>ciascuno memorizzò direttamente il proprio risultato.
Consenti l'aggiunta di dati all'HMAC in più passaggi
Come menzionato nella recensione per la parte 1, non è raro dover aggiungere più dati non contigui da aggiungere all'HMAC, quindi avere una funzione membro in add()grado di aggiornare l'HMAC. Ciò significherebbe dividere la creazione dell'HMAC in tre parti:
- Il materiale chiave viene preparato come parte del costruttore
- Il messaggio viene aggiunto all'hash, in una volta sola o utilizzando più chiamate di funzione
- Viene calcolato il valore finale
Vorrei strutturare la classe in questo modo:
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();
}
};
Potresti anche voler aggiungere un modo per evitare finish()di essere chiamato più di una volta.
Evita operazioni non sicure
Le tue stesse parole:
Odio leggere questi progetti in C ++ scritti male (motivo per cui ho iniziato questo hack) che sono pessimi wrapper attorno a C piuttosto che utilizzare una buona sicurezza dei tipi e interfacce e tecniche C ++ belle e pulite.
Vuoi una buona sicurezza di tipo, ma penso anche che tu voglia una buona sicurezza in generale. La creazione di funzioni non sicure va contro questo obiettivo. Se memorizzi i risultati di un hash in un Digestoggetto e hai un modo per ottenere un riferimento const ai dati che memorizza, non hai bisogno di una hashUnsafe()funzione.