Vi è mai capitato di avere la necessità di assicurare che il contenuto di un file, scaricato dal vostro server, non debba essere modificato ma allo stesso tempo debba essere leggibile dalla vostra applicazione? Mi spiego meglio: diciamo che dovete fornire un file che contiene una configurazione, la quale deve essere leggibile dall'applicazione che state configurando ma non dovrebbe essere modificabile dall'utente che maliziosamente voglia ottenere "qualcosa di più" manipolandone il contenuto. Qualcuno potrebbe pensare che la soluzione stia nel cifrare il file ma questo è solo un ostacolo per i più pigri e meno motivati, ma qualcuno potrebbe agevolmente superarlo decompilando il vostro codice e scoprendo dove avete messo la chiave da usare per decifrarla. Chi conosce il framework sa che si tratta di un'operazione tutt'altro che complessa.

Una possibile soluzione è quella di aggiungere una firma digitale che assicuri l'applicazione che il contenuto non è cambiato rispetto a quello originario. Con "firma digitale" qui intendo un hash che rappresenti il contenuto del file e che lo accompagni. L'applicazione potrebbe rigenerare l'hash prima di usare il contenuto del file e verificare che quello nel file corrisponda a quello generato. Ora, dato che gli algoritmi di hash sono pochi e ben conosciuti, tutto questo sarebbe una mera speculazione se non esistesse quello che pare essere un miracolo della crittografia: le chiavi asimmetriche.

Per chiave asimmetrica si intende una chiave suddivisa in due parti, dette pubblica e privata, in cui una viene usata per cifrare (quella pubblica) e solo l'altra può decifrare (quella privata). Nelle applicazioni della cifratura asimmetrica (l'SSL ad esempio) la chiave privata rimane segreta mentre quella pubblica serve per cifrare i contenuti che solo il detentore della chiave privata può decifrare. Ma questo cosa c'entra con gli hash?

Beh, giusto oggi ho fatto qualche prova ed ho scoperto che l'implementazione dell'algoritmo asimmetrico RSA nel framework, può essere usato per effettuare una "blind signature" ovvero una firma digitale generata con una chiave privata e verificata con la chiave pubblica. Per intenderci la verifica non si limita a controllare che l'origine della firma sia la chiave privata corrispondente, ma anche che il contenuto che ha dato origine all'hash non sia cambiato... ed ecco la soluzione di cui sopra; Il codice qui sotto genera una firma digitale corrispondente alla stringa passata in input:

   1: private static string SignData(string value, out string publicKey)
   2: {
   3:     byte[] valueToSign = Encoding.UTF8.GetBytes(value);
   4:     HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
   5:     RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
   6:     byte[] hash = provider.SignData(valueToSign, sha1);
   7:     publicKey = provider.ToXmlString(false);
   8:     return Convert.ToBase64String(hash);
   9: }

In breve: la stringa viene dapprima convertita in un array di byte, poi si sceglie l'algoritmo di hashing SHA1 (MD5 non vale perché è stato recentemente violato). Poi si crea un provider RSA per la cifratura e si genera l'hash con SignData. Infine il metodo ToXmlString estrae la parte pubblica della chiave privata che viene automaticamente generata quando si crea l'istanza del provider RSA. Tale chiave ritorna come argomento di output perchè dovrà essere usata nella parte di verifica:

   1: private static bool VerifyData(string value, string hash, string publicKey)
   2: {
   3:     byte[] hashBytes = Convert.FromBase64String(hash);
   4:     byte[] valueToVerify = Encoding.UTF8.GetBytes(value);
   5:     HashAlgorithm sha1 = HashAlgorithm.Create("SHA1");
   6:     RSACryptoServiceProvider provider = new RSACryptoServiceProvider();
   7:     provider.FromXmlString(publicKey);
   8:     return provider.VerifyData(valueToVerify, sha1, hashBytes);
   9: }

Il metodo qui sopra invece prende l'hash (la firma risultante dal metodo precedente), il valore originario da verificare (nel caso in esame sarebbe il contenuto del file da verificare) e la famosa chiave pubblica. Dopo che FromXmlString ha caricato nel provider RSA la parte pubblica il metodo VerifyData fa la magia e ritorna "true" se tutto corrisponde. In tal caso quindi nessuno avrà modificato la sorgente (o la chiave di cifratura). Come si usa?

   1: string value = "Andrea Boschin";
   2: string publicKey = string.Empty;
   3:  
   4: // genero la firma
   5: string hash = SignData(value, out publicKey);
   6:  
   7: // verifico il valore con la firma e la chiave pubblica
   8: bool success = VerifyData(value, hash, publicKey);
   9:  
  10: Console.WriteLine(success);
  11:  
  12: Console.ReadLine();

A questo punto potremmo pure compilare la chiave pubblica all'interno dell'applicazione (tanto è pubblica...) e verificare che firma e contenuto siano corrispondenti l'uno con l'altro sollevando una bella eccezione qualora un malintenzionato cambi anche solo una virgola.

Il sistema è violabile? Dato per scontato che la violabilità di un sistema è probabilemente inversamente proporzionale al tempo a disposizione, anche questo sistema lo è. Ma il malintenzionato dovrebbe  quindi generarsi una chiave privata con cui firmare il file e sostituire la chiave pubblica all'interno del vostro codice (a quel punto però non aggiorna più l'applicazione...). Come dicevo, il sistema non è infallibile, ma probabilmente se lo accoppiate con un assembly firmato allora il tempo necessario per violarlo cresce a sufficienza.


Aggiungi Commento