Il namespace System.Security.Cryptography di .NET mette
a disposizione un gran numero di classi per fare encrypting e
decrypting di informazioni. In questo post vedremo
innanzitutto le classi che servono a generare l'hash di un messaggio. Con il
termine messaggio intendo qualsiasi flusso di informazioni, sia esso una banale
stringa o uno stream proveniente da un file. Come dice lo stesso Alessio
Marziali
nei suoi
post, "gli hashes sono usati per creare un unico,
compatto valore per un ben specificato messaggio. Il messaggio (pre-image) è
usato come input per ricevere l'algoritmo di hash finale. Il valore di output
dell'Hash utilizzato è di tipologia Fixed-Size per ogni tipo di valore
input.".
Istanziare una classe HashAlgorithm
Vediamo di rendere
più chiari questi concetti teorici con un bell'esempio pratico. La classe
HashAlgorithm è una classe
abstract: cioè significa che non possiamo istanziarla direttamente, ma
dobbiamo obbligatoriamente creare un oggetto derivato da tale classe (SHA1CryptoServiceProvider, SHA256Managed, SHA384Managed, SHA512Managed, etc.). Quindi,
per esempio:
HashAlgorithm algo1 = SHA384.Create();
HashAlgorithm algo2 = SHA256.Create();
HashAlgorithm algo3 = SHA512.Create();
Con le tre righe di codice nel riquadro qui sopra, abbiamo istanziato 3
oggetti HashAlgorithm, che andranno però a calcolare l'hash con lunghezze
diverse, rispettivamente 384, 256 e 512 bit (48, 32 e 64 bytes).
L'abstract class HashAlgorithm espone un metodo Create molto
interessante, perchè ci permette di creare un oggetto hash basandosi su una
stringa che deve contenere il nome della classe reale da istanziare. Ok, lo so,
sono stato un po' impreciso: per maggiori informazioni, è possibile consultare
su MSDN la pagina che descrive nel dettaglio il metodo
Create. A noi interessare sapere quindi che possiamo fare
quanto segue:
SHA256 sha = SHA256.Create();
HashAlgorithm algo = HashAlgorithm.Create("SHA256");
I nostri due oggi sha e algo calcolano
l'hash allo stesso modo: il primo è stato creato tramite il metodo statico
Create della classe SHA256. Nel secondo caso, creiamo lo stesso
tipo di oggetto, ma usando il metodo Create con il parametro
SHA256.
Calcolare il valore hash di un byte[]
Dobbiamo partire
dal presupposto fondamentale che le classi derivate di HashAlgorithm ci
permettono di calcolare l'hash su qualsiasi tipo di messaggio: è sufficiente che
tale messaggio possa essere memorizzato in un array di bytes. Il metodo seguente
ad esempio prende in input una stringa e ritorna il valore hash:
public static byte[] CriptaStringa(string contents)
{
HashAlgorithm algo = SHA256.Create();
byte[] buffer = Encoding.Unicode.GetBytes(contents);
byte[] hash = algo.ComputeHash(buffer);
return (hash);
}
Dopo aver convertito la stringa in un byte[], uso il
metodo ComputeHash della classe SHA256 che prende in input i
dati di cui calcolare l'hash e ritorna - manco a farlo apposta - un altro byte[]
che rappresenta a tutti gli effetti l'hash. In questo caso, hash.Length
= 32, perchè abbiamo calcolato l'hash a 256 bit: la lunghezza
dell'array rimane quella fintanto chè usiamo la classe SHA256. Per spiegarvi la
logica che sta dietro a questo calcolo, preferisco citare ancora Alessio
Marziali - "Parlando ad un livello molto generale si può
tranquillamente affermare che creare un hash value a partire da una pre-image è
decisamente facile. Tuttavia, trovare la pre-image corrispondente all'hash value
è, a livelli statistici, quasi impossibile".
Il codice per calcolare l'hash value della string "value" è il
seguente...
byte[] ret = HashString.CriptaStringa("value");
for (int i = 0; i < ret.Length; i++)
Console.Write(ret[i].ToString() + ";");
...il cui output nella finestra Console è...
108;93;21;0;46;13;14;100;230;88;70;197;189;221;108;79;176;89;80;66;98;57;126;118;185;163;133;239;115;55;102;157;
Altri piccoli dettagli sulle classi derivate da
HashAlgorithm
La classe HashAlgorithm espone due proprietà
interessanti: HashSize (che ritorna il
numero di bit che rappresenta l'hash value) e Hash (per evitare di
ricalcolarlo inutilmente se l'abbiamo già fatto precedentemente con
ComputeHash). Inoltre, segnalo il metodo TransformBlock (che permette
di calcolare l'hash value solo su una porzione del byte[] passato in input) ed
il metodo Clear (che rilascia le risorse
impegnate durante il calcolo).
La classe MD5CryptoServiceProvider
Oltre all'algoritmo
SHA con differenti HashSize, .NET mette a disposizione anche la classe
MD5CryptoServiceProvider, che
calcola un hash value a 128 bit, corrispondenti - indovinate un po' - a ben 16
amici bytes. Riporto qui sotto il campo
Remarks tratto da MSDN che in questo caso spiega con altre parole
quello che ci aveva già detto Alessio attraverso il suo blog.
Hash functions map binary strings of an arbitrary length to small binary
strings of a fixed length. A cryptographic hash function has the property that
it is computationally infeasible to find two distinct inputs that hash to the
same value; that is, hashes of two sets of data should match if the
corresponding data also matches. Small changes to the data result in large,
unpredictable changes in the hash.