Crypto C ++: Partie 1 - Hash
À la recherche de bibliothèques Crypto modernes.
Je n'ai rien trouvé de bon.
Je sais que j'ai probablement mal fait tout cela, alors travaillez avec moi ici. Il y aura quatre revues différentes pour quatre structures qui s'appuient les unes sur les autres:
C'est le code de hachage et fournit un wrapper simple autour de SHA-1 et SHA-256 mais le modèle est suffisamment simple pour que nous puissions l'étendre pour d'autres mécanismes de hachage.
Les structures de données et l'implémentation présentées dans ces questions sont basées sur RFC2104 et cet article sur codeproject .
Exemple d'utilisation:
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
Réponses
Capitalisation appropriée de SHA1
Le nom de l'algorithme est SHA1, pas Sha1, donc je pense qu'il est préférable d'utiliser toutes les majuscules ici. Cela facilite la greffe du code pour un algorithme particulier.
Vous n'avez besoin que d'une classe par algorithme de hachage
En effet, comme vous l'avez mentionné dans votre propre réponse, la Sha1classe semble superflue, puisqu'elle ne stocke aucun état. Cependant, au lieu de créer des fonctions statiques à l'intérieur d'un Sha1espace de noms, vous pouvez faire de ces fonctions des fonctions membres de la classe qui contient l'état réel. Cela évite de répéter le type; par exemple:
Sha1::DigestStore digest;
Sha1::hash("Bob", digest)
Devient:
Sha1::DigestStore digest;
digest.hash("Bob");
Sha1::Digest contre. Digest<Sha1>
Je pense qu'avoir un espace Sha1de noms avec des DigestStorefonctions et à l'intérieur est un mauvais choix. Vous pouvez faire plus avec SHA1 que simplement créer des hachages simples, par exemple, vous pourriez vouloir créer un HMAC au lieu d'un hachage brut. Vous devrez donc ajouter des fonctions pour créer un HMAC à chaque espace de noms qui implémente un algorithme de hachage. Il est bien préférable d'avoir des classes Digestet HMACqui sont basées sur l'algorithme de hachage.
Autoriser la mise à jour des hachages
Le code que vous avez écrit n'effectue que des conversions uniques de certaines entrées en hachage. Cependant, il n'est pas rare que les programmes ne disposent pas de toutes les données pour lesquelles ils souhaitent créer un hachage dans une seule région de mémoire contiguë. Dans ces cas, vous voulez écrire:
std::ostream output;
Digest<SHA1> digest;
digest.add("Header");
digest.add("Data");
digest.add("Footer");
output << digest.view();
Certains algorithmes de résumé peuvent vous obliger à appeler une fonction pour calculer la valeur de hachage finale après avoir ajouté toutes les données. Vous pouvez ajouter une finish()fonction explicite ou l'appeler implicitement lors de l'accès au résultat du condensé.
Obtenir le résultat
Vous stockez en interne le hachage sous forme de fichier std::array<std::byte, size>. C'est la bonne chose à faire. Je ne pense pas qu'il soit nécessaire de fournir des fonctions membres autres que celle qui vous donne une constréférence à ce tableau. C'est à l'appelant de le convertir sous la forme qu'il souhaite. A std::arrayest déjà implicitement convertible en a std::span. Et une fois que vous avez une référence au tableau, il est facile d'en obtenir les itérateurs de début et de fin.
Ajouter des opérateurs de comparaison
Il est assez fréquent de vouloir vérifier si deux hash sont identiques, il serait utile d'au moins définir operator==()et operator!=()à la classe qui détient le résultat digérer.
Je pense que je vais changer l'interface.
Actuellement, le modèle d'utilisation est:
typename Sha1::DigestStore digest;
Sha1 hasher;
hasher.hash("Bob", digest);
Il ne semble pas nécessaire de créer un Sha1objet. Je pense qu'une meilleure interface peut être de faire toutes les méthodes staticpour que l'utilisation devienne:
typename Sha1::DigestStore digest;
Sha1::hash("Bob", digest);
Le DigestStorepeut nécessiter d'autres fonctions d'accès. Cela le permet actuellement, iterationmais il peut y avoir un cas d'utilisation où nous avons un fichier const_iterator.
J'essaie toujours de comprendre quand il est préférable de l'utiliser string_view. Malheureusement, il ne joue toujours pas bien avec les cordes normales. Il se peut donc que nous devions fournir un moyen d'extraire également une chaîne du tampon. Dans ce cas, ce serait bien si nous pouvions avoir les données du DigestStore dans une chaîne (ce qui signifie ne pas utiliser std::array) mais que nous devions avoir un bon cas d'utilisation pour que cela fonctionne mieux.
Je ne sais pas encore comment cela fonctionnera. Veuillez fournir un indice si vous avez une idée.