Nessun risultato. Prova con un altro termine.
Guide
Notizie
Software
Tutorial

La sicurezza dei dati in un'app

Link copiato negli appunti

Gli algoritmi criptografici introdotti in questo articolo servono a proteggere le comunicazioni tramite reti pubbliche, come internet. Per loro stessa natura, i messaggi che viaggiano sulla rete possono essere facilmente intercettati da "individui" interessati a conoscerne o a manipolarne il contenuto. In questo articolo vedremo come rendere la nostra applicazione Windows Store più sicura, in modo da poter scambiare dati da e verso l'esterno.

In particolare, grazie alla criptografia è possibile raggiungere uno o più dei seguenti obiettivi:

  • Autenticazione: grazie all'uso di algoritmi criptografici è possibile identificare con sicurezza l'identità del soggetto da cui la comunicazione proviene.
  • Confidenzialità: gli algoritmi criptografici aiutano a evitare che il contenuto di una comunicazione possa essere "leggibile" da persone diverse da quelle autorizzate.
  • Integrità dei dati: la criptografia consente anche di evitare che i dati possano essere manipolati da terze parti.
  • Non-repudiation: come risultato dell'autenticazione, confidenzialità e integrità dei dati, la criptografia previene anche il disconoscimento da parte dei terzi dei messaggi da loro inviati.

Vale la pena aggiungere che il Bureau of Industry and Security (Department of Commerce) degli Stati Uniti disciplina l'esportazione dei vari meccanismi di criptazione. Queste limitazioni valgono anche per le applicazioni distribuite tramite il Windows Store (per maggiori informazioni su questo argomento si rinvia alla documentazione ufficiale su MSDN)

Il namespace Windows.Security.Cryptography espone tipi e metodi che possono essere utilizzati per rendere più sicuri i dati di un'applicazione Windows Store. Queste nuove librerie sostituiscono le tradizionali librerie .NET incluse nel namespace System.Security.Cryptography. Vediamo adesso più nel dettaglio le principali classi incluse nel nuovo namespace.

Gli algoritmi di hashing

Gli algoritmi di hashing trasformano, tramite particolari funzioni matematiche, valori binari di lunghezza arbitraria in valori binari più piccoli e di lunghezza predeterminata. Applicando un algoritmo di hashing a un messaggio di testo, il risultato è un codice binario, noto come "digest", che rappresenta una sorta di "impronta" che identifica univocamente il contenuto del messaggio stesso. Infatti, anche la più piccola modifica al messaggio originale (anche un singolo bit) produce un digest differente rispetto a quello iniziale.

Grazie a questa caratteristica, gli algoritmi di hash aiutano a garantire l'integrità del messaggio. Una volta inviato il messaggio assieme al corrispondente digest, è infatti sufficiente per il ricevente applicare lo stesso algoritmo di hashing e confrontare il digest così prodotto con quello inviato assieme al messaggio: se corrispondono, vuol dire che il messaggio non è stato manipolato durante la comunicazione.

È importante precisare che un algoritmo di hashing, di per sé, non garantisce la confidenzialità della comunicazione, poiché il messaggio è inviato in chiaro. Inoltre, dato un digest, è impossibile risalire al messaggio originario. Gli algoritmi di hash, insomma, funzionano solo in una direzione: vengono infatti definiti anche One-way alghoritm.

In un'app Windows Store, per produrre un digest a partire da un messaggio di testo è possibile sfruttare la classe HashAlgorithmProvider, come mostrato nel prossimo snippet:

app.onloaded = function () {
    tbMessage1.innerText = message1;
    tbMessage2.innerText = message2;
    document.getElementById("btnHashMessage").addEventListener("click", hashMessage_click);
};
var message1 = "Ciao da Html.it";
var message2 = "Ciao da Html.it!";
function hashMessage_click() {
    var cryptography = Windows.Security.Cryptography;
    var cb = cryptography.CryptographicBuffer;
    var binaryMessage1 = cb.convertStringToBinary(message1, cryptography.BinaryStringEncoding.utf8);
    var binaryMessage2 = cb.convertStringToBinary(message2, cryptography.BinaryStringEncoding.utf8);
    var algorithmName = cryptography.Core.HashAlgorithmNames.sha512;
    var hashProvider = cryptography.Core.HashAlgorithmProvider.openAlgorithm(algorithmName);
    var digest1 = hashProvider.hashData(binaryMessage1);
    var digest2 = hashProvider.hashData(binaryMessage1);
    if (digest1.length != hashProvider.hashLength || digest2.length != hashProvider.hashLength) {
        tbDigest1.innerHTML = "<p>Qualcosa è andato storto durante la creazione del digest</p>";
        return;
    }
    tbDigest1.innerHTML = "<p>Digest messaggio 1: " + cb.encodeToBase64String(digest1) + "</p>";
    tbDigest2.innerHTML = "<p>Digest messaggio 2: " + cb.encodeToBase64String(digest2) + "</p>";
}

Questo esempio utilizza due messaggi di testo quasi identici (l'unica differenza è il punto esclamativo nella prima stringa) per mostrare come anche minime variazioni nella base di partenza produca digest differenti.

All'interno della funzione hashMessage_click, il codice per prima cosa converte i due messaggi nella relative rappresentazione binaria tramite il metodo ConvertStringToBinary, uno dei numerosi metodi messi a disposizione dalla classe CryptographicBuffer per eseguire conversioni tra i vari tipi coinvolti nelle operazioni criptografiche (come i metodi EncodeToBase64String/DecodeToBase64String e EncodeToHexString/DecodeToHexString).

Il secondo passaggio consiste nella scelta dell'algoritmo da utilizzare per produrre il digest. Per questa operazione possiamo sfruttare l'enum HashAlgorithmNames, che elenca i vari algoritmi supportati (Md5, Sha1, Sha256, Sha384, e Sha512):

var algorithmName = cryptography.Core.HashAlgorithmNames.sha512;

Il nome dell'algoritmo prescelto (Sha512, nel nostro caso) viene quindi passato come parametro al metodo HashAlgorithmProvider.OpenAlgorithm, ottenendo come risultato un oggetto di tipo HashAlgorithmProvider. È questo oggetto a incapsulare la logica interna relativa a quel particolare algoritmo.

A questo punto, per produrre l'hash corrispondente è sufficiente invocare il metodo HashData.

var digest1 = hashProvider.hashData(binaryMessage1);
var digest2 = hashProvider.hashData(binaryMessage1);

Il blocco if successivo controlla che la lunghezza dell'hash così prodotto coincida con quella specifica di quel particolare algoritmo (indicatadalla proprietà HashLength dell'oggetto HashAlgorithmProvider).

Per testare questo codice, possiamo questo markup HTML per la pagina di default dell' applicazione.

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo.Html.it.CryptoSample.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Demo.Html.it.CryptoSample.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div id="tbMessage1"></div>
    <div id="tbMessage2"></div>
    <button id="btnHashMessage">Genera hash</button>
    <div id="tbDigest1"></div>
    <div id="tbDigest2"></div>
</body>
</html>

La prossima immagine mostra il risultato dell'operazione di hashing (da notare come i due digest prodotti siano decisamente diversi l'uno dall'altro).

Per verificare che due digest coincidano (e quindi che il messaggio non sia stato alterato) possiamo sfruttare il metodo statico Compare, esposto dalla classe HashAlgorithmProvider, come illustrato nel prossimo snippet:

function compareDigest(originalMessage, originalDigest) {
    var cryptography = Windows.Security.Cryptography;
    var binaryMessage = cryptography.CryptographicBuffer.convertStringToBinary(originalMessage, cryptography.BinaryStringEncoding.utf8);
    var hashAlgorithmName = cryptography.Core.HashAlgorithmNames.sha512;
    var hashProvider = cryptography.Core.HashAlgorithmProvider.openAlgorithm(hashAlgorithmName);
    var reusableHash = hashProvider.createHash();
    reusableHash.append(binaryMessage);
    var hashedOriginalMessage = reusableHash.getValueAndReset();
    if (!cryptography.CryptographicBuffer.compare(hashedOriginalMessage, originalDigest)) {
        // il messaggio è stato alterato
    }
}

Il metodo CreateHash della classe HashAlgorithmProvider consente di creare un oggetto di tipo CryptographicHash, ossiaun container che permette di operare più trasformazioni in sequenza. Per creare un digest, è sufficiente passare il testo (sotto forma di un oggetto IBuffer) al metodo Append, quindi invocare il metodo GetValueAndReset, il quale restituisce il relativo digest e resetta l'oggetto CryptographicHash affinché sia pronto a ricevere un nuovo messaggio (e conseguentemente a produrre un nuovo digest).

Infine, il metodo Compare mette a confronto il digest ricevuto assieme al messaggio con quello prodotto applicando al messaggio originale lo stesso algoritmo usato per produrre il primo digest. Se i due digest coincidono, il messaggio non può essere stato manipolato fra le due operazioni.

Generare numeri e dati random

La generazione di numeri random rappresenta un momento importante in molte operazioni criptografiche, come ad esempio nella generazione di chiavi criptografiche o di password. La classe CryptographicBuffer espone un metodo GenerateRandomNumber che rende questa operazione particolarmente semplice:

var rndNumber = Windows.Security.Cryptography.CryptographicBuffer.generateRandomNumber();

Questo metodo restituisce un numero intero a 32-bit. Questo numero può quindi essere utilizzato per successive operazioni criptografiche (ne vedremo un esempio più avanti).

Oltre alla generazione di numeri casuali, la classe CryptographicBuffer espone anche un metodo GenerateRandom che può essere utilizzato per generare un buffer di dati random della lunghezza desiderata. Il prossimo snippet mostra un esempio di generazione di numeri e dati random:

app.onloaded = function(){
    document.getElementById("btnGenerateRandom").addEventListener("click", generateRandom_click);
};
function generateRandom_click(args) {
    var cryptography = Windows.Security.Cryptography;
    var rndNumber = cryptography.CryptographicBuffer.generateRandomNumber();
    document.getElementById("randomNumber").innerText = "Numero random: " + rndNumber.toString();
    var length = 32;
    var rndData = cryptography.CryptographicBuffer.generateRandom(length);
    document.getElementById("randomData").innerText = "Dati random: " + cryptography.CryptographicBuffer.encodeToHexString(rndData);
}

Per testare questo codice, si può utilizzare il seguente markup HTML per la pagina di default dell'app:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo.Html.it.CryptoSample.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Demo.Html.it.CryptoSample.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <button id="btnGenerateRandom">Genera numeri e dati random</button>
    <div id="randomNumber"></div>
    <div id="randomData"></div>
</body>
</html>

La prossima immagine mostra un esempio di numeri e dati random generate tramite i metodi sopra descritti:

Criptare un messaggio con algoritmi MAC

Una forma particolare di algoritmo di hash è rappresentato dal Message Authentication Code (MAC), una famiglia di algoritmi anche noti come "keyed hashing algorithm". Un MAC consiste in un breve set di dati che viene utilizzato per assicurare non solo l'integrità del messaggio, ma anche la sua autenticità, grazie al fatto che l'algoritmo si basa su una chiave segreta che è conosciuta sia da mittente che dal ricevente (criptografia simmetrica).

Nei casi più comuni, la chiave segreta viene utilizzata in combinazione con un algoritmo di hashing (detto hash-based message authentication code, or HMAC) per produrre un digest criptato che può essere decriptato unicamente da coloro in possesso della chiave segreta. Talvolta, invece, la chiave segreta viene utilizzata per criptare l'intero messaggio, e gli ultimi bit di questo sono usati come codice hash (anche se nessun algoritmo di hash è stato effettivamente applicator), mentre il resto dei dati viene scartato.

Per criptare un messaggio tramite un algoritmo MAC, è possibile sfruttare la classe MacAlgorithmProvider, la cui logica di funzionamento è molto simile a quella della classe HashAlgorithmProvider vista in precedenza. La differenza principale sta nel fatto che, per produrre il digest, viene impiegata una chiave segreta. Il prossimo snippet mostra questo punto:

app.onloaded = function () {
    document.getElementById("btnGenerateMacSignature").addEventListener("click", generateMacSignature_click);
    document.getElementById("btnVerifyMacSignature").addEventListener("click", verifyMacSignature_click);
};
var macSignature;
var message = "Ciao da Html.it";
var key;
var cryptography = Windows.Security.Cryptography;
function generateMacSignature_click(args) {
    var macAlgorithmName = cryptography.Core.MacAlgorithmNames.hmacSha256;
    var macProvider = cryptography.Core.MacAlgorithmProvider.openAlgorithm(macAlgorithmName);
    key = cryptography.CryptographicBuffer.generateRandom(macProvider.macLength);
    var hmacKey = macProvider.createKey(key);
    var binaryMessage = cryptography.CryptographicBuffer.convertStringToBinary(message, cryptography.BinaryStringEncoding.utf8);
    macSignature = cryptography.Core.CryptographicEngine.sign(hmacKey, binaryMessage);
    document.getElementById("signature").innerHTML += "
MAC Signature: " + cryptography.CryptographicBuffer.encodeToHexString(macSignature) + "
";
}

Le prime due righe di codice all'interno del metodo generateMacSignature_click sono molto simili a quelle viste in relazione alla classe HashAlgorithmProvider: per prima cosa, il codice recupera il nome dell'algoritmo MAC da utilizzare (gli algoritmi supportati sono AesCmac, HmacMd5, HmacSha1, HmacSha256, HmacSha384, e HmacSha512) attraverso la classe statica MacAlgorithmNames, quindi invoca il metodo OpenAlgorithm per recuperare il relativo provider, passando come parametro il nome dell'algoritmo da utilizzare (HmacSha256, in questo esempio).

Il passo successivo consiste nel creare la chiave segreta per firmare il messaggio. Per far questo, il codice utilizza il metodo GenerateRandom descritto in precedenza. Questo metodo restituisce un buffer di dati random che viene quindi passato come parametro al metodo CreateKey del provider MAC. Infine, il codice invoca il metodo Sign della classe CryptographicEngine per produrre un codice hash criptato basato sulla chiave privata.

L'autenticità del digest così criptato può essere verificata tramite il metodo VerifySignature, il quale accetta i seguenti parametri: la chiave segreta usata per criptare il digest, il messaggio e la firma da verificare. Questo metodo decripta la firma tramite la chiave fornita e quindi compara i due digest. Se questi coincidono, significa non solo che il messagggio non è stato alterato durante il trasporto, ma anche che vi è una relativa certezza circa l'identità del mittente. Il prossimo snippet mostra un esempio di verifica:

function verifyMacSignature_click(args) {
    var macAlgorithmName = cryptography.Core.MacAlgorithmNames.hmacSha256;
    var macProvider = cryptography.Core.MacAlgorithmProvider.openAlgorithm(macAlgorithmName);
    var hmacKey = macProvider.createKey(key);
    var binaryMessage = cryptography.CryptographicBuffer.convertStringToBinary(message, cryptography.BinaryStringEncoding.utf8);
    var isAuthenticated = cryptography.Core.CryptographicEngine.verifySignature(
        hmacKey,
        binaryMessage,
        macSignature);
    if (!isAuthenticated) {
        document.getElementById("verification").innerHTML += "Attenzione! Le firme non coincidono.";
    }
    else {
        document.getElementById("verification").innerHTML += "Ok, le firme coincidono!";
    }
}

Per prima cosa, il codice ottiene una reference all'algoritmo MAC utilizzato per firmare il digest e crea un nuovo oggetto CryptographicKey basato sulla stessa chiave utilizzata per criptare l'hash. Quindi, il codice invoca il metodo VerifySignature per accertarsi che, data la chiave segreta, il messaggio e la firma a questo associate, la firma sia valida.

Per testare questo codice, puoi usare la seguente definizione HMTL come riferimento per la pagina di default della tua app.

<StackPanel>
    <StackPanel Orientation="Horizontal">
        <Button Content="Genera firma MAC" Margin="5" FontSize="18" Click="GenerateMacSignature_Click" />
        <TextBlock x:Name="tbMacSignature" Margin="5" VerticalAlignment="Center" FontSize="18"/>
    </StackPanel>
    <StackPanel Orientation="Horizontal">
        <Button Content="Validate MAC signature" Margin="5" FontSize="18" Click="VerifyMacSignature_Click" />
        <TextBlock x:Name="VerificationTextBlock" Margin="5" VerticalAlignment="Center" FontSize="18"/>
    </StackPanel>
</StackPanel>

La prossima immagine mostra il risultato delle operazioni di generazione di una firma MAC:

La firma digitale

Una firma digitale è concettualmente simile a quanto appena visto a proposito dell'autenticazione MAC. La differenza è che mentre quest'ultima si basa su una chiave segreta condivisa (o chiave simmetrica), la firma digitale utilizza una chiave asimmetrica per raggiungere lo stesso risultato.

Più nel dettaglio, la criptografia asimmetrica si basa su una coppia di chiavi generate matematicamente a partire da numeri random: una chiave privata, che deve essere tenuta segreta, e una chiave pubblica che può essere liberamente diffusa. I dati criptati utilizzando la chiave pubblica possono essere decriptati unicamente con la chiave privata e viceversa.

Per firmare digitalmente un messaggio, per prima cosa il mittente applica un algoritmo di hash al messaggio, creando un digest. Questo digest viene criptato utilizzato la chiave privata. Il ricevente, dal canto suo, utilizzerà la corrispondente chiave pubblica per decriptare il digest associato al messaggio, calcolare il codice di hash di quest'ultimo e quindi confrontare i due digest: se questi coincidono, significa che il messaggio arriva effettivamente dal possessore della chiave privata e che il messaggio non è stato alterato durante il trasporto.

È anche possibile l'operazione inversa, ossia firmare digitalmente un messaggio utilizzando la chiave pubblica del destinatario. Il messaggio potrà essere decriptato unicamente dal possessore della chiave privata, assicurando così la confidenzialità del messaggio.

Il prossimo snippet mostra un esempio di creazione di una firma digitale:

app.onloaded = function () {
    document.getElementById("btnGenerateDigitalSignature").addEventListener("click", generateDigitalSignature_click);
};
function generateDigitalSignature_click(args) {
    var keySize = 256;
    var keyPair;
    var message = "Ciao da Html.it";
    var cryptography = Windows.Security.Cryptography;
    var cb = cryptography.CryptographicBuffer;
    var asymmetricAlgorithmName = cryptography.Core.AsymmetricAlgorithmNames.ecdsaP256Sha256;
    var asymmetricKeyProvider = cryptography.Core.AsymmetricKeyAlgorithmProvider.openAlgorithm(asymmetricAlgorithmName);
    try {
        keyPair = asymmetricKeyProvider.createKeyPair(keySize);
    }
    catch (ex) {
        // errore nella generazione delle chiavi
        return;
    }
    var binaryMessage = cryptography.CryptographicBuffer.convertStringToBinary(message, cryptography.BinaryStringEncoding.utf8);
    var signature = Windows.Security.Cryptography.Core.CryptographicEngine.sign(keyPair, binaryMessage);
    var publicKeyBuffer = keyPair.exportPublicKey();
    var keyPairBuffer = keyPair.export();
    var keyPublic = asymmetricKeyProvider.importPublicKey(publicKeyBuffer);
    if (keyPublic.keySize != keyPair.keySize) {
        // importazione fallita
        return;
    }
    keyPair = asymmetricKeyProvider.importKeyPair(keyPairBuffer);
    if (keyPublic.keySize != keyPair.keySize) {
        // importazione fallita
        return;
    }
    var isAuthenticated = cryptography.Core.CryptographicEngine.verifySignature(keyPublic, binaryMessage, signature);
    if (!isAuthenticated) {
        messageBox.innerHTML = "Verifica fallita!";
        return;
    }
    digitalSignature.innerHTML = "Firma verificata: " + cryptography.CryptographicBuffer.encodeToBase64String(signature);
}

La differenza principale rispetto al codice visto a proposito degli algoritmi MAC è che, in questo caso, per firmare il messaggio viene utilizzata una coppia di chiavi, anziché una chiave segreta condivisa.

Anche in questo caso, il primo passo consiste nella scelta del provider che incapsula la logica dell'algoritmo; poi, una volta recuperato il nome dell'algoritmo tramite l'enum AsymmetricalAlgorithmNames, per creare la coppia chiave pubblica/chiave privata il codice invoca il metodo CreateKeyPair della classe AsymmetricKeyProvider (per l'elenco degli algoritmi supportati si rinvia alla documentazione su MSDN).

var asymmetricAlgorithmName = cryptography.Core.AsymmetricAlgorithmNames.ecdsaP256Sha256;
var asymmetricKeyProvider = cryptography.Core.AsymmetricKeyAlgorithmProvider.openAlgorithm(asymmetricAlgorithmName);
try {
    keyPair = asymmetricKeyProvider.createKeyPair(keySize);
}
catch (ex) {
    // errore nella generazione delle chiavi
    return;
}

La chiave privata è quindi usata per firmare digitalmente il messaggio tramite il metodo CryptographicEngine.Sign.

var signature = Windows.Security.Cryptography.Core.CryptographicEngine.sign(keyPair, binaryMessage);

Dopo aver firmato il messaggio con la chiave privata, è possibile esportare (sotto forma di array di byte) l'intera coppia di chiavi, o anche solo la chiave pubblica, tramite i metodi Export e ExportPublicKey della classe CryptographicKey.

var publicKeyBuffer = keyPair.exportPublicKey();
var keyPairBuffer = keyPair.export();

Analogamente, è possibile importare una o entrambe le chiavi tramite i metodi ImportKeyPair e ImportPublicKey (sempre sotto forma di buffer).

var keyPublic = asymmetricKeyProvider.importPublicKey(publicKeyBuffer);

Infine, per verificare la firma, la classe CryptographicEngine espone il metodo VerifySignature, il quale accetta come parametri la chiave pubblica importata, il messaggio e il digest firmato con la chiave privata.

var isAuthenticated = cryptography.Core.CryptographicEngine.verifySignature(keyPublic, binaryMessage, signature);

Per testare il codice qui presentato, possiamo usare il seguente markup HTML come riferimento per l'app:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo.Html.it.CryptoSample.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Demo.Html.it.CryptoSample.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <button id="btnGenerateDigitalSignature">Crea firma digitale</button>
    <div id="digitalSignature"></div>
</body>
</html>

La prossima immagine mostra la firma generata dal codice:

Emettere e richiedere certificati

Nella vita reale, la nostra identità è comprovata da un documento emesso da una autorità di certificazione. Questa autorità può essere riconosciuta solo a livello locale (si pensi al comune di residenza per le carte di identità, la cui validità è nazionale) oppure riconosciuta a livello internazionale (la questura per i passaporti), per indentificare in modo sicuro un "individuo" informatico, occorre un documento emesso da una autorità di certificazione. In pratica la verifica di un documento parte dal riconoscimento dell'autorità di certificazione che lo ha emesso. Ad esempio, la nostra carta di identità non è valida all'estero in quanto non viene riconosciuta l'autorità di ogni singolo comune italiano al di fuori del nostro paese, mentre il passaporto è riconosciuto come documento valido in quanto esiste una relazione di "fiducia" fra i vari stati del globo.

Il documento di identità per utenti, computer, aziende, etc prende il nome di certificato digitale.

Come si è detto, la criptografia asimmetrica si basa su una coppia di chiavi, di cui una pubblica e una privata, che viene utilizzata per criptare/decriptare i dati. Mentre la chiave privata deve rimanere segreta, la chiave pubblica è generalmente incorporata in un certificato che collega quella chiave a una particolare persona, computer o organizzazione.

X.509 PKI è uno standard che identifica i requisiti per un certificato a chiave pubblica. In base a questo standard, un certificato contiene informazioni relative a un soggetto, inclusa la sua chiave pubblica. Un'autorità di certificazione (Certification Authority, o "CA") ha il compito di emette certificati e le parti coinvolte nella comunicazione si affidano alla CA per verificare l'identità del soggetto.

La struttura di un certificato X.509 è la seguente:
---------------------------------------------------------------------
-- X.509 signed certificate
---------------------------------------------------------------------
SignedContent ::= SEQUENCE
{
  certificate         CertificateToBeSigned,
  algorithm           Object Identifier,
  signature           BITSTRING
}
---------------------------------------------------------------------
-- X.509 certificate to be signed
---------------------------------------------------------------------
CertificateToBeSigned ::= SEQUENCE
{
  version                 [0] CertificateVersion DEFAULT v1,
  serialNumber            CertificateSerialNumber,
  signature               AlgorithmIdentifier,
  issuer                  Name
  validity                Validity,
  subject                 Name
  subjectPublicKeyInfo    SubjectPublicKeyInfo,
  issuerUniqueIdentifier  [1] IMPLICIT UniqueIdentifier OPTIONAL,
  subjectUniqueIdentifier [2] IMPLICIT UniqueIdentifier OPTIONAL,
  extensions              [3] Extensions OPTIONAL
}

Il namespace Windows.Security.Cryptography.Certificates contiene tipi e metodi che consentono di richiedere, installare o importare certificati digitali. Il seguente snippet mostra come creare la richiesta per un certificato.

function createRequest() {
    try {
        var crp = cryptography.Certificates.CertificateRequestProperties();
        crp.friendlyName = "MyCertificate";
        crp.subject = "Html.it";
        crp.keyProtectionLevel = cryptography.Certificates.KeyProtectionLevel.noConsent;
        crp.keyUsages = cryptography.Certificates.EnrollKeyUsages.all;
        crp.exportable = cryptography.Certificates.ExportOption.exportable;
        crp.keySize = 2048;
        crp.keyStorageProviderName = cryptography.Certificates.KeyStorageProviderNames.softwareKeyStorageProvider;
        request = cryptography.Certificates.CertificateEnrollmentManager.createRequestAsync(crp).done(
            function (req) {
                document.getElementById("request").innerHTML = req;
                return request;
            },
            function (err) {
                document.getElementById("error").innerHTML = "createRequestAsync failed";
            });
    }
    catch (ex) {
        // gestire eccezione
    }
}

Per prima cosa, il codice istanzia un oggetto di tipo CertificateRequestProperties con le proprietà della richiesta. In particolare, l'enum KeyProtectionLevel indica il livello di protezione e accetta uno dei seguenti valori:

  • NoConsent: Nessuna particolare protezione (questo è il valore di default);
  • ConsentOnly: l'utente viene notificato tramite un dialog quando la chiave privata viene creata o utilizzata;
  • ConsentWithPassword: l'utente viene richiesto di inserire una password per la chiave, non appena questa viene create o viene utilizzata.

La prossima immagine mostra un esempio di dialog per la creazione di una password da associare al certificato:

La proprietà KeyUsages (di tipo EnrollKeyUsage) specifica il tipo di operazione che può essere eseguito con la chiave privata associata al certificato richiesto. I valori possibili sono i seguenti:

  • None: nessun uso specifico indicato.
  • Decryption: la chiave può essere usata solo per decriptare un messaggio;
  • Signing: la chiave può essere usata per firmare (questo rappresenta il valore di default);
  • KeyAgreement: la chiave può essere usata per lo scambio di chiavi di sessione;
  • All: la chiave può essere usata per uno qualunque degli scopi sopra indicate.

La proprietà Exportable specifica se la chiave privata possa essere o meno esportata (di default, non è esportabile per ragioni di sicurezza), mentre la proprietà KeySize permette di indicare la lunghezza della chiave da generare (per gli algoritmi RSA e DSA, il valore di default è di 2048 bit).

Un'altra proprietà da menzionare è KeyStorageProviderName, la quale indica il key storage provider (KSP) da usare per generare la chiave privata. Un'applicazione Windows Store può accedere a tre KSP:

  • PlatformKeyStorageProvider: corrisponde al Microsoft Platform Key Storage Provider;
  • SmartcardKeyStorageProvider: corrisponde al Microsoft Smart Card Key Storage Provider;
  • SoftwareKeyStorageProvider: corrisponde al Microsoft Software Key Storage Provider (questo è il valore di default).

Infine, la proprietà KeyAlgorithmName specifica il tipo di algoritmo da usare per generare la chiave pubblica. Il valore di default è RSA.

La classe CertificateEnrollmentManager espone anche un metodo asincrono, InstallCertificateAsync, che permette di installare un certificato nell'app container del computer. Il seguente snippet illustra questo punto.

function installCertificate_click(args) {
    var response = createRequest();
    if (request == null || request == "") {
        // qualcosa è andato storto nella generazione della richiesta
    }
    try {
        response = submitCertificateRequestAndGetResponse(request, "http://www.contoso.org/");
        if (response != null & response.length > 0) {
            cryptography.Certificates.CertificateEnrollmentManager.installCertificateAsync(response, cryptography.Certificates.InstallOptions.none)
                .then(function () {
                    document.getElementById("response")
                        .innerHTML = "Certificato installato";
                }, function (err) {
                    document.getElementById("response")
                        .innerHTML = "Errore nell'installazione del certificato";
                });
        }
    }
    catch (ex) {
        // gestire l'eccezione
    }
}
function submitCertificateRequestAndGetResponse(request, uri) {
    // invia la richiesta alla CA e attende la risposta
}

Dopo aver richiesto un certificato tramite il metodo CreateRequestAsync, il codice invoca il metodo SubmitCertificateRequestAndGetResponseAsync per creare una richiesta HTTP, inviare la richiesta di certificato al server indicato come secondo parametro, e quindi restituisce il certificato proveniente dal server.

Una volta recuperato il certificato, il codice chiama il metodo InstallCertificateAsync passando come parametro il certificato stesso, assieme a un oggetto di tipo InstallOption che specifica le opzioni di installazione.

In Windows, i certificati rilasciati così come le richieste di certificati sono memorizzati nel Microsoft Certificate Store: per l'elenco e la descrizione dei vari store, si rinvia alla documentazione su MSDN.

I certificati sono normalmente salvati per singolo utente e per singola applicazione. Un'applicazione Windows Store può accedere in scrittura unicamente al proprio storage di certificati, mentre in lettura può accedere, oltre che ai certificati nello storage applicativo, anche a quelli presenti sulla macchina locale. I certificati aggiunti da un'app non possono essere letti da altre applicazioni Windows Store e quando l'app viene disinstallata, anche i relativi certificati vengono rimossi.

Infine, per importare in modo asincrono un certificato a partire da un messaggio Personal Information Exchange (PFX), puoi sfruttare il metodo ImportPfxDataAsync della classe CertificateEnrollmentManager. Il prossimo snippet ne mostra un esempio:

function importCertificate_click(args) {
    try {
        var pfxCertificate = Windows.ApplicationModel.Resources.ResourceLoader().getString("MyCertificate");
        var password = "password";
        var friendlyName = "Pfx Certificate Sample";
        cryptography.Certificates.CertificateEnrollmentManager.importPfxDataAsync(
            pfxCertificate,
            password,
            ExportOption.NotExportable,
            KeyProtectionLevel.NoConsent,
            InstallOptions.None,
            friendlyName)
        .done(function () {
            // certificato importato con successo
        }, function (err) {
            // qualcosa non ha funzionato
        });;
    }
    catch (ex) {
        // gestire l'eccezione
    }
}

È possibile esportare da un device per importarlo successivamente su altri device.

Proteggere i tuoi dati con la classe DataProtectionProvider

La classe DataProtectionProvider, contenuta nel namespace Windows.Security.Cryptography.DataProtection), espone metodi che permettono di proteggere dati sensibili tramite criptografia. È importante ricordare che, nel momento in cui istanziamo un nuovo oggetto di questo tipo per eseguire un'operazione di criptazione (dunque per proteggere i dati), è necessario passare come parametro una stringa che indica il tipo di sicurezza da utilizzare (per un approfondimento dei "security descriptor" si rinvia alla documentazione su MSDN); questo parametro non è invece necessario se l'oggetto DataProtectionProvider viene istanziato per l'operazione inversa, ossia di decriptazione del messaggio.

Il codice che segue mostra come usare il metodo ProtectAsync della classe DataProtectionProvider per criptare un buffer di dati (da notare il security descriptor passato al costruttore dell'oggetto):

app.onloaded = function () {
    btnProtect.addEventListener("click", protectButton_click);
    btnUnprotect.addEventListener("click", unprotectButton_click);
}
var protectedBuffer = null;
var cryptography = Windows.Security.Cryptography;
function protectButton_click(args) {
    var descriptor = "LOCAL=user";
    var plainText = tbPlaintext.innerText;
    var dpp = cryptography.DataProtection.DataProtectionProvider(descriptor);
    var binaryMessage = cryptography.CryptographicBuffer.convertStringToBinary(plainText, cryptography.BinaryStringEncoding.utf8);
    dpp.protectAsync(binaryMessage).done(
        function (result) {
            protectedBuffer = result;
        },
        function (err) {
            // gestire l'errore
        });
    document.getElementById("tbEncrypted").innerHTML = cryptography.CryptographicBuffer.encodeToBase64String(protectedBuffer);
}

Per decriptare i dati precedentemente protetti è possibile sfruttare il metodo UnprotectAsync, il quale accetta come parametro un buffer di byte che rappresenta il messaggio criptato, come mostrato nel seguente snippet:

function unprotectButton_click(args) {
    if (protectedBuffer !== null) {
        var dpp = cryptography.DataProtection.DataProtectionProvider();
        dpp.unprotectAsync(protectedBuffer)
            .done(function (result) {
                document.getElementById("tbUnprotected").innerText = cryptography.CryptographicBuffer.convertBinaryToString(cryptography.BinaryStringEncoding.utf8, result);
            },
            function (err) {
                // gestire l'errore
            });
    }
}

Per testare questo codice, puoi usare la seguente definizione HTML come riferimento per la pagina di default della tua app:

<!DOCTYPE html>
<html>
<head>
    <meta charset="utf-8" />
    <title>Demo.Html.it.CryptoSample.JS</title>
    <!-- WinJS references -->
    <link href="//Microsoft.WinJS.2.0/css/ui-dark.css" rel="stylesheet" />
    <script src="//Microsoft.WinJS.2.0/js/base.js"></script>
    <script src="//Microsoft.WinJS.2.0/js/ui.js"></script>
    <!-- Demo.Html.it.CryptoSample.JS references -->
    <link href="/css/default.css" rel="stylesheet" />
    <script src="/js/default.js"></script>
</head>
<body>
    <div id="tbPlaintext">Messaggio da proteggere</div>
    <button id="btnProtect">Cripta il messaggio</button>
    <div id="tbEncrypted"></div>
    <button id="btnUnprotect">Decripta il messaggio</button>
    <div id="tbUnprotected"></div>
</body>
</html>

Se adesso eseguiamo l'applicazione, il risultato dovrebbe essere simile a quello mostrato nella prossima immagine.

Infine, vale la pena di ricordare che per proteggere la password dell'utente, è possibile sfruttare anche la classe PasswordVault contenuta nel namespace Windows.Security.Credentials.


Ti consigliamo anche