C ++ Crypto: Parte 1- Hash
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:
Questo è il codice di hashing e fornisce un semplice wrapper attorno a SHA-1 e SHA-256, ma il modello è abbastanza semplice da poterlo espandere per altri meccanismi di hashing.
Le strutture dati e l'implementazione presentate in queste domande si basano su RFC2104 e questo post su codeproject .
Esempio di utilizzo:
DigestStore<Sha1> hash; // <- destination of hash
Sha1 hasher;
hasher.hash("This string can be hashsed", hash);
hash.h
#ifndef THORS_ANVIL_CRYPTO_HASH_H
#define THORS_ANVIL_CRYPTO_HASH_H
#ifdef __APPLE__
#define COMMON_DIGEST_FOR_OPENSSL
#include <CommonCrypto/CommonDigest.h>
#define THOR_SHA1(data, len, dst) CC_SHA1(data, len, dst)
#define THOR_SHA256(data, len, dst) CC_SHA256(data, len, dst)
#else
#include <openssl/sha.h>
#define THOR_SHA1(data, len, dst) SHA1(data, len, dst)
#define THOR_SHA256(data, len, dst) SHA256(data, len, dst)
#endif
#include <string>
#include <array>
//
// Wrapper for sha1 and sha256 hashing algorithms
//
// Provides a simple wrapper class with the appropriates types and size
// for the resulting "digest" object. Also provides several type safe
// versions of the hashing algorithm to allow multiple know types to
// be safely hashed.
namespace ThorsAnvil::Crypto
{
using Byte = char unsigned;
using DigestPtr = Byte*;
template<typename Hash>
using Digest = typename Hash::DigestStore;
template<std::size_t size>
class DigestStore
{
std::array<Byte, size> data;
public:
using iterator = typename std::array<Byte, size>::iterator;
operator Digest() {return &data[0];}
std::string_view view() {return std::string_view(reinterpret_cast<char const*>(&data[0]), std::size(data));}
Byte& operator[](std::size_t i) {return data[i];}
iterator begin() {return std::begin(data);}
iterator end() {return std::end(data);}
};
// These versions of the hashing function are good for hashing short
// amounts of text. Use these for passwords and validation hashes
// do not use them for hashing large documents.
struct Sha1
{
static constexpr std::size_t digestSize = SHA_DIGEST_LENGTH;
using DigestStore = DigestStore<SHA_DIGEST_LENGTH>;
void hash(DigestStore& src, DigestStore& dst) {THOR_SHA1(src, SHA_DIGEST_LENGTH, dst);}
void hash(std::string_view src, DigestStore& dst) {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
void hash(std::string const& src, DigestStore& dst) {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
// Use only if you know the destination is large enough!!
void hashUnsafe(std::string_view src, DigestPtr dst) {THOR_SHA1(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
};
struct Sha256
{
static constexpr std::size_t digestSize = SHA256_DIGEST_LENGTH;
using DigestStore = DigestStore<SHA256_DIGEST_LENGTH>;
void hash(DigestStore& src, DigestStore& dst) {THOR_SHA256(src, SHA256_DIGEST_LENGTH, dst);}
void hash(std::string_view src, DigestStore& dst) {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
void hash(std::string const& src, DigestStore& dst) {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
// Use only if you know the destination is large enough!
void hashUnsafe(std::string_view src, Digestptr dst) {THOR_SHA256(reinterpret_cast<Byte const*>(&src[0]), std::size(src), dst);}
};
}
#endif
Risposte
Uso corretto delle maiuscole di SHA1
Il nome dell'algoritmo è SHA1, non Sha1, quindi penso che sia meglio usare tutte le maiuscole qui. Ciò semplifica il grepping del codice per un particolare algoritmo.
Hai solo bisogno di una classe per algoritmo hash
In effetti, come hai detto nella tua risposta, la Sha1classe sembra superflua, poiché non memorizza alcuno stato. Tuttavia, invece di creare funzioni statiche all'interno di uno Sha1spazio dei nomi, è possibile rendere tali funzioni funzioni membro della classe che detiene lo stato effettivo. Ciò evita di ripetere il tipo; per esempio:
Sha1::DigestStore digest;
Sha1::hash("Bob", digest)
Diventa:
Sha1::DigestStore digest;
digest.hash("Bob");
Sha1::Digest vs. Digest<Sha1>
Penso che avere uno spazio dei nomi Sha1con DigestStorefunzioni e al suo interno sia una cattiva scelta. C'è di più che puoi fare con SHA1 oltre a creare semplici hash, ad esempio potresti voler creare un HMAC invece di un semplice hash. Quindi dovresti aggiungere funzioni per creare un HMAC a ogni spazio dei nomi che implementa un algoritmo hash. È molto meglio avere classi Digeste HMACche sono basate su modelli sull'algoritmo hash.
Consenti l'aggiornamento degli hash
Il codice che hai scritto esegue solo conversioni one-shot di alcuni input in un hash. Tuttavia, non è raro che i programmi non dispongano di tutti i dati per i quali desiderano creare un hash in una singola regione di memoria contigua. In questi casi, vuoi scrivere:
std::ostream output;
Digest<SHA1> digest;
digest.add("Header");
digest.add("Data");
digest.add("Footer");
output << digest.view();
Alcuni algoritmi digest potrebbero richiedere la chiamata di una funzione per calcolare il valore hash finale dopo aver aggiunto tutti i dati. È possibile aggiungere una finish()funzione esplicita o chiamarla implicitamente quando si accede al risultato del digest.
Ottenere il risultato
Archivia internamente l'hash come file std::array<std::byte, size>. Questa è la cosa giusta da fare. Non penso sia necessario fornire funzioni membro diverse da una che ti dia un constriferimento a quell'array. Spetta al chiamante convertirlo in qualsiasi forma preferisca. A std::arrayè già implicitamente convertibile in a std::span. E una volta che hai un riferimento all'array, è facile ottenere gli iteratori di inizio e fine da esso.
Aggiungi operatori di confronto
È abbastanza comune voler verificare se due hash sono identici, quindi sarebbe utile definire almeno operator==()e operator!=()alla classe che contiene il risultato del digest.
Penso che cambierò l'interfaccia.
Attualmente lo schema di utilizzo è:
typename Sha1::DigestStore digest;
Sha1 hasher;
hasher.hash("Bob", digest);
Non sembra necessario creare un Sha1oggetto. Penso che un'interfaccia migliore potrebbe essere quella di rendere tutti i metodi in staticmodo che l'utilizzo diventi:
typename Sha1::DigestStore digest;
Sha1::hash("Bob", digest);
L' DigestStorepotrebbero aver bisogno di altre funzioni accessi. Attualmente consente, iterationma può esserci un caso d'uso in cui abbiamo un file const_iterator.
Sto ancora cercando di capire quando è meglio usare string_view. Purtroppo continua a non funzionare bene con le corde normali. Quindi potremmo dover fornire un modo per estrarre anche una stringa dal buffer. In tal caso sarebbe bello se potessimo avere i dati dal DigestStore in una stringa (che significa non usare std::array) ma abbiamo bisogno di un buon caso d'uso per farlo funzionare meglio.
Non sono ancora sicuro di come funzionerà. Fornisci un suggerimento se hai un'idea.