CryptoAPI

 

La CryptoAPI  fornisce servizi che consentono agli sviluppatori di applicazioni di aggiungere, a queste, sicurezza basata sulla crittografia. Inoltre, include funzionalità per codificare e decodificare da ASN.1, per l’hashing, per la cifratura e la decifratura dei dati, per l'autenticazione usando i certificati digitali e per la gestione dei certificati negli archivi. La cifratura e la decifratura sono fornite usando sia le chiavi di sessione sia le coppie di chiavi pubblica/privata.

Gli sviluppatori delle applicazioni possono usare le funzioni della CryptoAPI senza conoscere i dettagli dell'implementazione sottostante, allo stesso modo in cui possono usare una libreria grafica senza conoscere nulla sulla particolare configurazione dell’hardware grafico.

 

Crittografia e CryptoAPI

 

La comunicazione sicura su reti non sicure generalmente coinvolge tre aree di maggiore importanza: segretezza, autenticazione ed integrità.

In aggiunta a queste tre funzionalità, la CryptoAPI permette anche:

·        La codifica dei messaggi in formato ASN.1.

·        La decodifica di questi messaggi.

·        La gestione delle raccolte negli archivi dei certificati

·        Di lavorare con liste di fiducia e catene di certificati per la verifica della validità dei certificati.

 

Segretezza

 

Per avere la segretezza, gli utenti devono prevenire a tutti, eccetto al destinatario designato, la lettura del messaggio. Assicurare la segretezza generalmente include alcune forme di crittografia. Per fornire la segretezza, la crittografia permette di cifrare i messaggi in modo che essi possano essere memorizzati e trasmessi in maniera sicura.

La cifratura dei dati trasforma un messaggio scritto in chiaro (detto testo in chiaro nella crittografia comune) in modo che appaia come un discorso incomprensibile. Un buon sistema di cifratura dei dati rende difficile trasformare dati cifrati nel testo in chiaro senza una chiave segreta. I dati da cifrare possono essere testi ASCII, un file di database oppure qualche altro dato che si vuole memorizzare o trasmettere  in modo sicuro.

I dati cifrati possono essere memorizzati su un mezzo non sicuro o trasmessi su una rete non sicura e rimanere ancora privati. In seguito, i dati possono essere decifrati nella loro forma originale. Questo processo è mostrato nella seguente illustrazione :

 

 

 

 

La cifratura e la decifratura sono processi semplici. Quando i dati vengono cifrati, viene usata  una chiave di cifratura. Questa chiave è paragonabile ad una chiave fisica che viene usata per chiudere un lucchetto. Per decifrare i dati viene usata una chiave di decifratura. La chiave di decifratura è paragonabile a una chiave per sbloccare un lucchetto. La cifratura e la decifratura vengono spesso effettuate usando la stessa chiave, ma a differenza di quando si ha a che fare con chiavi fisiche, a volte cifratura e decifratura possono usare chiavi differenti di una coppia di chiavi pubblica/privata.

Le chiavi di cifratura devono essere tenute segreta e al sicuro e devono essere trasmesse in modo sicuro agli altri utenti. L’obiettivo principale è il restringimento degli accessi alla chiave di decifratura, perché ognuno che la possiede è in grado di decifrare tutti i messaggi che sono stati cifrati con la chiave di cifratura corrispondente.

 

Autenticazione

 

Le comunicazioni sicure richiedono che gli individui comunicanti conoscano l'identità di coloro con i quali essi comunicano. L'autenticazione include la verifica dell'identità di una persona o di un'entità. Per verificare l'identità di una persona, vengono spesso usate forme di documentazione fisica, spesso dette credenziali.

L'autenticazione implica anche l'accertamento che i dati ricevuti siano quelli che erano stati spediti. Se A manda un messaggio a B, B deve essere in grado di provare che il messaggio ricevuto era il messaggio che A aveva inviato e non un messaggio che è stato sostituito a quel messaggio. Per fornire questa forma di autenticazione, la CryptoAPI fornisce funzioni per firmare i dati e verificare le firme usando coppie di chiavi pubblica/privata.

Visto che le comunicazioni su una rete di computer hanno luogo senza nessun contatto fisico tra i comunicanti, la verifica dell'identità spesso fa affidamento su una credenziale che può essere spedita e ricevuta su una rete. Tale credenziale deve essere rilasciata da un rilasciante fidato di credenziali.

I certificati digitali, comunemente conosciuti come certificati, sono proprio una tale credenziale. Essi sono un modo per verificare l'identità ed ottenere l'autenticazione in una rete di computer.

Un certificato digitale è una credenziale rilasciata da un'organizzazione fidata oppure da un'entità detta certification authority (CA). Questa credenziale contiene una chiave pubblica ed i dati che identificano il soggetto del certificato. Un certificato viene rilasciato da una CA solamente dopo che questa ha verificato l'identità del soggetto del certificato ed ha controllato che la chiave pubblica inclusa nel certificato appartiene a quel soggetto.

La comunicazione tra una CA ed un richiedente del certificato potrebbe essere compiuta dal richiedente trasportando fisicamente l'informazione richiesta, forse memorizzata su un floppy disk, alla CA. Comunque, la comunicazione viene generalmente effettuata con un messaggio firmato spedito nella rete. La CA spesso usa un programma fidato detto certificate server per emettere i certificati.

 

Integrità

 

Tutti i dati spediti su un mezzo non sicuro possono essere cambiati o a causa dei rumori presenti durante la trasmissione o volontariamente. Nel mondo reale vengono usati i sigilli per fornire e provare l'integrità di un qualche prodotto. Un flacone di aspirina, per esempio, viene inserito in scatole a prova di manomissione aventi un sigillo intatto che prova che niente è stato inserito nel pacco dopo il rilascio da parte del produttore.

Allo stesso modo un destinatario dei dati non ha bisogno solamente di verificare l'identità del mittente dei dati, ma deve anche essere sicuro che i dati ricevuti siano esattamente quelli che erano stati spediti; cioè che non sono stati manomessi. La determinazione dell'integrità dei dati ricevuti viene effettuata spesso inviando non solo i dati originali ma anche un messaggio di verifica, detto hash, riguardante quei dati. Entrambi i dati ed il messaggio di verifica possono essere spediti con una firma digitale che prova l'origine di entrambi.

L'integrità è fornita nella CryptoAPI per mezzo dell'uso delle firme digitali e degli hash dei dati.

 

Architettura del sistema della CryptoAPI

 

L'architettura del sistema della CryptoAPI è composta da cinque aree funzionali principali:

·        Funzioni crittografiche di base.

Le funzioni di contesto sono usate per la connessione al CSP. Queste funzioni rendono le applicazioni capaci di scegliere un CSP specifico che possa fornire la necessaria classe di funzionalità .

Le funzioni di generazione delle chiavi sono usate per generare e memorizzare chiavi crittografiche. Viene incluso un pieno supporto per modificare i vettori di inizializzazione e le altre caratteristiche della cifratura.

Le funzioni di scambio delle chiavi sono usate per scambiare o trasmettere chiavi.

·        Funzioni di codifica e decodifica di un certificato.

Sono funzioni usate per cifrare o decifrare dati. Viene incluso anche un supporto per l’hashing dei dati.

·        Funzioni per l'archivio dei certificati.

Sono funzioni usate per gestire le collezioni di certificati digitali.

·        Funzioni semplificate per i messaggi.

Funzioni usate per cifrare e decifrare messaggi e dati.

Funzioni usate per firmare messaggi e dati

Funzioni usate per verificare l'autenticità di firme sui messaggi ricevuti e sui relativi dati.

·        Funzioni a basso livello per i messaggi.

Sono funzioni usate per svolgere la maggior parte dei compiti svolti dalle funzioni semplificate per i messaggi. Le funzioni a basso livello forniscono più flessibilità delle funzioni semplificate per i messaggi ma richiedono più chiamate a funzioni.

 

Ogni funzione ha una parola chiave, all’interno del suo nome, indicante la sua area funzionale. Queste parole chiave sono le seguenti:

 

Area funzionale

Convenzione del nome della funzione

Funzioni crittografiche di base

Crypt

Funzioni di codifica e decodifica

Crypt

Funzioni per l'archivio dei certificati

Store

Funzioni semplificate per i messaggi

Message

Funzioni a basso livello per i messaggi

Msg

 

 

 

 

 

 

 

 

 

 

Le applicazioni usano funzioni in tutte queste aree. Queste funzioni, prese insieme, costituiscono la CryptoAPI. Le funzioni crittografiche di base usano i CSP per gli algoritmi di crittografia e per la generazione e la memorizzazione sicura delle chiavi crittografiche.

Vengono usati due tipi differenti di chiavi crittografiche: le chiavi di sessione usate per una singola cifratura/decifratura e le coppie di chiavi pubblica/privata usate su una base più permanente.

 

Chiavi crittografiche

 

Le chiavi crittografiche sono importanti per le operazioni di crittografia. Esse devono essere mantenute segrete, perché chiunque possegga una data chiave ha accesso a qualunque dato a cui essa è associata.

Ci sono due tipi di chiavi crittografiche: chiavi di sessione e coppie di chiavi pubblica/privata.

 

Chiavi di sessione

 

Le chiavi di sessione, anche chiamate chiavi simmetriche, vengono usate con gli algoritmi di cifratura simmetrica. Gli algoritmi simmetrici sono i tipi più comuni di algoritmi di cifratura. Sono chiamati simmetrici perché usano la stessa chiave sia per la cifratura che per la decifratura. Le chiavi di sessione vengono cambiate frequentemente; usualmente si usa una chiave di sessione differente per ogni messaggio cifrato.

Gli algoritmi simmetrici sono più veloci degli algoritmi a chiave pubblica. Quindi, sono preferibili quando si cifrano grandi quantitativi di dati. Alcuni dei più comuni algoritmi simmetrici sono RC2, RC4 ed il DES.

Le chiavi di sessione vengono create usando o la funzione CryptGenKey o CryptDeriveKey.

Visto che una buona quantità delle attività comporta che le chiavi di sessione siano mantenute segrete, è importante mantenere il più basso possibile il numero delle persone che le posseggono (è raccomandabile una o due persone). Queste chiavi sono mantenute interne al CSP.

A differenza delle coppie di chiavi pubblica/privata, le chiavi di sessione sono volatili. Le applicazioni possono salvare queste chiavi, per un uso futuro o per la trasmissione ad altri utenti, usando la funzione CryptExportKey che le esporta dal CSP e le inserisce nello spazio di memoria dell’applicazione sotto forma di BLOB della chiave cifrato.

 

Coppie di chiavi pubblica/privata

 

Le coppie di chiavi pubblica/privata vengono usate in un procedimento di cifratura più sicuro chiamato cifratura asimmetrica. La cifratura asimmetrica viene usata principalmente per cifrare e decifrare le chiavi di sessione e le firme digitali. La cifratura asimmetrica usa gli algoritmi di cifratura chiave pubblica.

Gli algoritmi a chiave pubblica usano due chiavi differenti: una chiave pubblica ed una chiave privata. Il membro chiave privata della coppia deve essere mantenuto privato e sicuro. La chiave pubblica, invece, può essere distribuita a tutti coloro che la richiedono. La chiave pubblica di una coppia di chiavi è spesso distribuita tramite un certificato digitale. Quando una chiave di una coppia di chiavi viene usata per cifrare un messaggio, l’altra chiave di quella coppia viene usata per decifrare il messaggio. Quindi se la chiave pubblica dell’utente A viene usata per cifrare i dati, solo l’utente A (o qualcuno che ha accesso alla chiave privata dell’utente A) può decifrare i dati. Se la chiave privata dell’utente A viene usata per cifrare i dati, solo la chiave pubblica dell’utente A potrà decifrare i dati, quindi solo l’utente A (o qualcuno che ha accesso alla chiave privata dell’utente A) può effettuare la cifratura.

Se la chiave privata viene usata per firmare un messaggio, la chiave pubblica di quella coppia deve essere usata per validare la firma.

Sfortunatamente, gli algoritmi a chiave pubblica sono molto lenti (1000 volte più lenti degli algoritmi simmetrici). Non è pratico usarli per cifrare ampi quantitativi di dati. In pratica, gli algoritmi a chiave pubblica vengono usati per cifrare le chiavi di sessione. Gli algoritmi simmetrici vengono usati per la cifratura/decifratura della maggior parte dei dati.

Analogamente, visto che firmare un messaggio, in effetti, implica la cifratura del messaggio, non è pratico usare un algoritmo di firma a chiave pubblica per firmare ampi messaggi. Piuttosto, viene creato un hash a lunghezza fissa per il messaggio ed il valore hash viene cifrato.

Ogni utente, generalmente, ha due coppie di chiavi pubblica/privata. Una coppia di chiavi viene usata per cifrare le chiavi di sessione e l’altra per creare la firma digitale. Queste sono conosciute rispettivamente come exchange key pair e signature key pair.

È da notare che sebbene i contenitori delle chiavi creati dalla maggior parte dei CSP contengano due coppie, ciò non è imprescindibile. Alcuni CSP non memorizzano nessuna coppia di chiavi mentre altri CSP memorizzano più di due coppie.

Tutte le chiavi della CryptoAPI sono memorizzate all’interno dei CSP. I CSP sono anche responsabili della creazione delle chiavi, la loro distruzione ed il loro uso per svolgere varie operazioni di crittografia.

 

Memorizzazione e scambio di chiavi crittografiche

 

Ci sono situazioni in cui le chiavi devono essere esportate dall’ambiente sicuro del cryptographic service provider (CSP) per essere inserite nello spazio dati di un’applicazione. Le chiavi esportate vengono memorizzate in strutture di BLOB della chiave cifrati.

Ci sono due situazioni specifiche in cui è necessario esportare le chiavi:

·        Per salvare una chiave di sessione per un utilizzo successivo da parte di un’applicazione. Se, per esempio, un’applicazione ha appena cifrato un database da decifrare in seguito. L’applicazione è responsabile della memorizzazione della chiave di cifratura. Ciò è necessario perché i CSP non conservano le chiavi simmetriche da sessione a sessione.

·        Per mandare una chiave a qualcuno. Questo dovrebbe essere facile se i rispettivi CSP potessero comunicare direttamente; ma ciò non è possibile. Visto che i CSP non possono comunicare, la chiave deve essere esportata da un CSP, trasmessa all’applicazione di destinazione e quindi importata nel CSP di destinazione. Questo processo può diventare più complicato se il tragitto di comunicazione non è fidato.

In entrambi i casi, un’applicazione deve memorizzare una chiave di sessione al di fuori del CSP per un certo periodo di tempo.

 

Key database

 

La CryptoAPI utilizza il cryptographic service provider (CSP), un modulo indipendente che svolge il lavoro reale di crittografia. Sono disponibili molti CSP ed ogni CSP ha un key database nel quale memorizza le chiavi crittografiche. Ogni key database contiene uno o più contenitori di chiavi, ognuno dei quali contiene tutte le coppie di chiavi appartenenti ad uno specifico client della CryptoAPI. Ogni contenitore di chiavi ha un nome unico che le applicazioni forniscono alla funzione CryptAcquireContext per avere accesso al contenitore delle chiavi. La seguente è un’illustrazione del contenuto di un key database.

 

 

I CSP memorizzano i contenitori delle chiavi, che includono tutte le coppie di chiavi pubblica/privata, in alcune forme di rappresentazioni permanenti come i file del disco oppure all’interno di una chiave del registro. Le chiavi di sessione non sono automaticamente persistenti su un mezzo di memorizzazione permanente.

Generalmente, viene creato un contenitore delle chiavi di default per ogni utente. Questo contenitore considera il nome di logon dell’utente come nome dell’utente, il quale viene usato da molte applicazioni. È anche possibile per un’applicazione creare un suo contenitore delle chiavi e le sue coppie di chiavi.

 

BLOB della chiave

 

I BLOB della chiave forniscono un metodo di memorizzazione delle chiavi al di fuori del CSP, al fine di spostarle su differenti computer o per trasportarle. Le chiavi sono memorizzate all’interno del CSP e le applicazioni hanno permesso di accesso alla chiave solo tramite un handle. I BLOB sono l’unica eccezione a questa regola.

I BLOB vengono creati per rendere sicuro il trasferimento di chiavi da un CSP ad un altro. CryptExportKey crea un BLOB e copia una chiave esistente da un CSP a quel BLOB. Il BLOB della chiave può quindi essere scritto in un file o in un qualunque altro mezzo di memorizzazione. Quindi, il BLOB può essere importato in un provider (spesso un CSP differente su un computer differente) usando CryptImportKey. CryptImportKey crea, nel CSP, un duplicato della chiave esportata.

I BLOB della chiave consistono di un’intestazione standard, seguita dai dati (una stringa di byte non intelligibili) che rappresentano la chiave stessa. Se il BLOB contiene una chiave di sessione, questi dati sono sempre tenuti cifrati. Le applicazioni non accedono al contesto del BLOB. È stata questa caratteristica di essere una stringa di byte inaccessibile e non intelligibile a condurre al nome di BLOB della chiave.

I BLOB della chiave sono personalizzati visto che sono cifrati con le exchange public key del destinatario designato. Questo li rende abbastanza sicuri. Per renderli a prova di intrusione, le chiavi vengono a volte firmate con la exchange private key dell’utente originario.

Sono definiti quattro tipi di BLOB:

·        BLOB della chiave semplice

·        BLOB della chiave pubblica

·        BLOB della chiave privata

·        BLOB della chiave opaca

 

Alternative alla memorizzazione delle chiavi di sessione

 

Invece di memorizzare un BLOB della chiave di sessione casuale, può essere usata una chiave derivata. Le chiavi di sessione derivate vengono create da una password usando la funzione CryptDeriveKey. In questo modo, invece di memorizzare una particolare chiave derivata, un’applicazione può creare una chiave derivata chiedendo  la password all’utente.

I BLOB della chiave memorizzati dipendono dalla stabilità delle coppie di chiavi pubblica/privata, memorizzate nel CSP. Se queste coppie di chiavi vengono, per qualche motivo, perse, i BLOB non possono essere decifrati. Ciò significa che ogni dato cifrato usando queste chiavi andrà perso. Per prevenire questo tipo di perdite, si deve usare una backup authority, quando si memorizzano dati a lungo termine di archiviazione. Una backup authority è un’applicazione fidata in esecuzione su un computer sicuro che fornisce memoria per le chiavi di sessione dei suoi client. Tutte le chiavi di sessione memorizzatevi vengono cifrate sotto forma di BLOB della chiave usando la chiave pubblica della backup authority.

 

Scambio di coppie di chiavi pubbliche

 

Il primo passo che due utenti effettuanti una comunicazione cifrata hanno bisogno di svolgere è scambiare le chiavi pubbliche. Dopo aver fatto ciò, ogni utente può mandare i dati cifrati e firmati all’altro.

Ci sono due modi principali di ottenere la chiave pubblica dell’altro:

·        Ogni utente può ottenere la chiave pubblica dell’altro sotto forma di certificato emesso da una certification authority (CA) fidata. L’utilizzo di certificati forniti da una CA fidata è il modo più sicuro per scambiare le chiavi pubbliche. Questo metodo di scambio delle chiavi non richiede l’interazione diretta dell’utente.

·        Ogni utente può leggere la chiave pubblica dell’altro tramite il telefono, usando una mail certificata da mandare all’altro o tramite l’uso di qualche altro metodo che è ragionevolmente a prova di intrusione. È da notare che la chiave pubblica di un utente non è segreta, visto che non importa se viene scoperta da una terza parte.

Questo metodo può anche essere usato per validare i valori della chiave pubblica che è stata scambiata in altri modi.

 

Scambio manuale delle coppie di chiavi pubbliche

 

Se non è disponibile una certification authority (CA), non si valuta la fiducia, oppure se gli utenti non hanno registrato le loro chiavi pubbliche in una CA, questi devono scambiarsi le chiavi pubbliche in altro modo. Lo scambio diretto delle chiavi pubbliche, che non coinvolge lo scambio di certificati, viene chiamato scambio manuale.

Quando si trasferiscono chiavi o messaggi da un utente ad un altro, uno degli utenti è designato come utente trasmittente e l’altro come utente destinatario.

Il primo passo è, per il trasmittente, di esportare la sua chiave pubblica dal CSP e di inserirla nel BLOB della chiave pubblica usando la funzione CryptExportKey. Quindi, il BLOB viene spedito in maniera sicura. Sebbene la segretezza non è necessaria, entrambi gli utenti devono essere fiduciosi che l’integrità del BLOB della chiave resti intatta durante il trasferimento. Tutto ciò viene svolto in maniera completamente indipendente dalla CryptoAPI.

I BLOB della chiave pubblica non vengono cifrati; quindi, non sarà difficile per l’applicazione trasmittente convertire il BLOB in un formato leggibile, così che il trasmettitore possa leggere la chiave pubblica del ricevitore tramite il telefono. Inoltre, non sarà difficile per l’applicazione ricevente ricostruire il BLOB della chiave pubblica.

Dopo che l’applicazione ricevente ha ottenuto i dati del BLOB della chiave dall’applicazione trasmittente, importa il BLOB nel suo CSP usando la funzione CryptImportKey.

 

Scambio di chiavi di sessione

 

Per inviare ad un altro utente un messaggio cifrato, la chiave di sessione usata per eseguire la cifratura deve essere mandata all’utente che dovrà decifrare il messaggio. Ci sono due modi d’applicarlo:                                       

·        L’utente trasmittente può creare una chiave di sessione casuale, cifrandola usando la chiave pubblica ricevuta, e la manda, sotto forma di BLOB della chiave, al ricevitore.

·        Gli utenti trasmittente e ricevente possono reciprocamente accordarsi su una chiave di sessione scambiandosi una serie di messaggi avanti e indietro. Gli utenti possono usare poi questa chiave di sessione per mandare e ricevere messaggi cifrati.

 

Codifica e decodifica dei dati

 

Per inviare dati su un mezzo di comunicazione come la linea telefonica, i dati devono essere serializzati, cioè convertiti in una stringa di 0 e 1 che possa essere trasmessa serialmente sulla linea. La serializzazione deve essere fatta in un modo che il computer che riceve i dati possa riconvertirli nel loro formato originale. Il modo in cui viene effettuata la serializzazione è chiamato protocollo di comunicazione ed è controllato sia dal software che dall’hardware di trasmissione dati. Ci sono molti livelli ai quali il dato viene convertito. La seguente illustrazione mostra una vista molto semplificata degli strati del protocollo di comunicazione.

 

 

La precedente illustrazione mostra lo strato d’applicazione sul computer numero 1 che spedisce i dati che devono essere trasmessi allo strato di codifica/decodifica. Lo strato di codifica/decodifica codifica i dati in un flusso di byte. Al livello più basso, lo strato hardware, l’hardware converte i byte di dati in un flusso seriale di 0 ed 1 che sono trasmessi sulla linea al computer numero 2 . Lo strato hardware del computer numero 2 riconverte gli 0 ed 1 indietro in byte e li passa su allo strato di codifica/decodifica per la decodifica. Il livello di codifica/decodifica decodifica i byte portandoli nella loro forma originale e li passa su allo strato d’applicazione.

Un principio di progettazione software accettato è l’uso di astrazione, cioè, il processo di descrizione di un problema o di un oggetto in termini dei suoi parametri generali, piuttosto che la descrizione di tutti i dettagli necessari a risolvere il problema, o la descrizione di tutti i dettagli di un oggetto. Usando l’astrazione, un progettista può specificare un oggetto software che ha qualità specifiche senza entrare nel dettaglio di come l’oggetto è effettivamente implementato nel codice software. Tale pratica rende libera l’implementazione. Ciò semplifica anche la specifica e rende possibile l’asserzione di assiomi sull’oggetto che devono essere provati quando l’oggetto viene implementato. Questi assiomi possono poi essere supposti veri quando l’oggetto viene utilizzato in un altro oggetto di livello più alto. L’astrazione è il marchio delle più moderne specifiche software.

La maggior parte dei protocolli di comunicazione implica una buona parte di astrazione. Gli oggetti agli strati più alti sono definiti astratti e sono destinati ad essere implementati usando oggetti di strati più bassi. Per esempio, un servizio ad un livello potrebbe richiedere il trasferimento di alcuni oggetti astratti fra computer. Uno strato di livello più basso potrebbe usare regole di codifica per trasformare gli oggetti astratti in stringhe di 0 ed 1.

Un metodo comune di specificare oggetti astratti destinati ad essere trasmessi serialmente è chiamato Abstract Syntax Notation One  (ASN.1). ASN.1 è definito nella raccomandazione CCITT X.208. Un insieme di regole di ASN.1 per rappresentare tali oggetti come stringhe di 0 ed 1 viene chiamato Distinguished Enconding Rules (DER) ed è definito nella raccomandazione CCITT X.509.

 

Fondamenti di ASN.1

 

La CryptoAPI non richiede che il programmatore di applicazioni usi la notazione ASN.1 per codificare e decodificare i dati. Comunque, ad un programmatore deve diventare familiare la notazione ASN.1 perché le strutture C usate dalla CryptoAPI per codificare e decodificare i dati rispecchiano i tipi di dati ASN.1 appropriati.

ASN.1 è una notazione d’astrazione flessibile che permette la definizione di molti tipi di dati, da semplici tipi come interi e stringhe di bit, a tipi strutturati come collezioni di uno o più altri tipi.

Il DER è un insieme di regole che definisce come le astrazioni ASN.1 vengono serializzate in stringhe di 0 e 1 per la trasmissione su una linea. Il DER descrive come rappresentare o codificare i valori di ogni tipo di dati ASN.1 sotto forma di stringa di otto bit. Una stringa di byte è anche chiamata stringa di ottetti.

 

Tipi semplici ASN.1

 

In ASN.1 un tipo è un insieme di valori. Per alcuni tipi, ci sono un numero finito di valori e per altri tipi ce ne sono un numero infinito. Un valore di un tipo di dato ASN.1 è un elemento dell’insieme del tipo. ASN.1 ha quattro specie di tipi:

·        Tipi semplici – tipi base che non hanno componenti.

·        Tipi strutturati – hanno componenti.

·        Tipi aggiuntivi – derivati da altri tipi

·        Altri tipi – incluso il tipo CHOICE ed il tipo ANY

Tipi e valori possono essere assegnati con l’operatore ASN.1 d’assegnamento (::=), e quei nomi possono essere usati nella definizione di altri tipi e valori.

I tipi semplici sono i tipi di base che non hanno componenti. ASN.1 definisce una serie di questi tipi semplici. I tipi che sono rilevanti per il Public Key Cryptography Standard (PKCS) sono i seguenti:

·        BIT STRING – una stringa arbitraria di bit (0 e 1).

·        IA5String - una stringa arbitraria di caratteri IA5 (ASCII).

·        INTEGER – un intero arbitrario.

·        NULL – un valore nullo.

·        OBJECT IDENTIFIER - un identificatore dell’oggetto, che è una sequenza di componenti intere che identificano un oggetto come un tipo di algoritmo o un tipo di attributo.

·        OCTET STRING - una stringa arbitraria di ottetti.

·        PrintableString - una stringa arbitraria di caratteri stampabili.

·        T61String - una stringa arbitraria di caratteri T.61 (ad otto bit).

·        UTCTime – il valore del Greenwich Mean Time (GMT).

 

Tipi strutturati ASN.1

 

I tipi strutturati sono quelli che consistono di componenti. ASN.1 definisce quattro tipi strutturati, i quali sono tutti relativi a PKCS:

·        SEQUENCE: una collezione ordinata di uno o più tipi.

·        SEQUENCE OF: una collezione di zero o più occorrenze di un dato tipo.

·        SET: una collezione non ordinata di uno o più tipi

·        SET OF: una collezione non ordinata di zero o più occorrenze di un dato tipo

I tipi strutturati possono avere componenti opzionali, possibilmente con valori di default.

 

Codifica e decodifica usando la CryptoAPI

 

Il processo di codifica dei dati implica la codifica di strutture C annidate. Innanzitutto, il livello più basso della struttura viene codificato. Questo è mostrato, nella seguente illustrazione, come struttura 3. Per far ciò, la funzione CryptEncodedObject può essere chiamata con la struttura 3 come suo input, ed il suo output potrà essere un BLOB contenente la struttura codificata che viene usato come membro della struttura 2. Questo processo viene ripetuto finché non si arriva alla struttura più esterna che viene codificata ed inserita nel BLOB finale .

 

Il processo di decodifica è l’inverso del processo di codifica. Esso implica l’acquisizione di una struttura codificata, generalmente nella forma di un BLOB di bit, e la decodifica di questa in varie strutture C predefinite. La seguente illustrazione descrive il processo di decodifica.

 

Il processo di decodifica di una struttura comincia dalla porzione codificata di questa. Un certificato include sia un BLOB codificato che una struttura C che duplica l’informazione codificata. Quando la funzione CryptDecodeObject è lanciata su questo BLOB, il suo output è una struttura C che ha sia membri utilizzabili (non codificabili) che membri codificati che hanno bisogno di un’ulteriore decodifica. Questo è descritto come struttura 1, nella precedente illustrazione. Se la struttura 1 è usata come input per la funzione CryptDecodeObject, i membri codificati vengono decodificati ed utilizzati come struttura 2. La struttura 2 potrà probabilmente avere ancora membri codificati, ed il processo è ripetuto fino a quando tutti i membri sono stati completamente decodificati.

Fra gli item che le funzioni CryptEncodeObject e CryptDecodeObject possono codificare e decodificare ci sono i certificati, le estensioni dei certificati, le richieste di certificato, le CRL e le CTL.

 

Cifratura e decifratura dei dati

 

La cifratura è il processo di traduzione di un testo in chiaro in qualcosa che appare essere casuale e senza significato (testo cifrato). La decifratura è il processo di conversione del testo cifrato nel testo in chiaro.

Per cifrare un ampio quantitativo di dati, viene usata la cifratura simmetrica. Sia durante il processo di cifratura che di decifratura vengono usate chiavi simmetriche o di sessione. Per decifrare un pezzo di testo cifrato, deve essere usata la chiave utilizzata per cifrare i dati. Essenzialmente, una chiave di sessione è un numero casuale, la cui lunghezza va da 40 a 2000 bit. Più lunga è la chiave, più è difficile decifrare un pezzo di testo cifrato senza possederla.

L’obiettivo di ogni algoritmo di cifratura è di rendere il più difficile possibile la decifratura del testo cifrato senza usare la chiave. Se viene usato un algoritmo di cifratura molto buono, per romperlo non esiste nessuna tecnica migliore di provare tutte le possibili chiavi.

È difficile determinare la qualità di un algoritmo di cifratura. Gli algoritmi che sembrano promettenti, a volte, dato un giusto attacco, sono molto facili da rompere. Quando si seleziona un algoritmo di cifratura, è spesso una buona idea sceglierne uno in circolazione da un po’ e che ha resistito con successo a tutti gli attacchi.

 

Cifratura/decifratura tecniche ed algoritmi

 

Gli attuali algoritmi di cifratura usati nel processo di cifratura/decifratura dipendono dal cryptographic service provider (CSP) utilizzato. Per tutte le cifrature effettuate usando le funzioni della CryptoAPI, viene usato un algoritmo simmetrico, indifferentemente da quale CSP è installato. Le applicazioni che invocano le funzioni della CryptoAPI non hanno bisogno di conoscere i dettagli dell’attuale algoritmo usato.

La seguente tabella elenca le prestazioni di alcuni algoritmi comuni di cifratura. Questi dati sono generati da un’applicazione che utilizza la CryptoAPI su un Pentium a 120 MHz.

 

Cifrario

Tipo di cifrario

Key setup time (μs)

Velocità di cifratura(byte/s)

DES

cifrario a blocchi da 64 bit

460

1138519

RC2

cifrario a blocchi da 64 bit

40

286888

RC4

stream cipher

151

2377723

 

Cifratura di file e messaggi usando la CryptoAPI

 

Per cifrare un file in modo che solo l’utente corrente possa accedere ai dati, viene utilizzato un cifrario simmetrico. La chiave di questo cifrario è mantenuta in un BLOB della chiave che può essere aperto solo con la chiave privata dell’utente.

Ci sono molti formati differenti per cifrare file e messaggi. Questi sono progettati per rendere facile la comunicazione a molte applicazioni.

Dopo aver cifrato un file o un messaggio, i seguenti dati devono essere memorizzati dall’applicazione. Questi pezzi sono usualmente mantenuti insieme:

·        I dati cifrati. Quando viene usato un cifrario a blocchi, viene fatto il padding dei dati fino a raggiungere una dimensione multipla di quella del blocco del cifrario. Viene spesso fatto il padding anche quando la dimensione del messaggio originale è già un multiplo della dimensione del blocco. Quando viene usato uno stream cipher, i dati cifrati sono generalmente della stessa dimensione del testo in chiaro originale.

·        Uno o più BLOB della chiave, ognuno contenente la chiave di sessione utilizzata per cifrare il messaggio. Ognuno di questi BLOB viene cifrato con la exchange public key dell'utente che decifrerà il messaggio in seguito. È da notare che questi non vengono memorizzati se la chiave viene derivata da una password. Infatti, quando è il momento di decifrare il messaggio, la chiave di sessione viene ricreata dalla password. L’utente deve ricordare la password.

·        Tutti i valori aggiuntivi che sono stati specificati nel momento in cui i dati sono stati cifrati. Quando i dati vengono decifrati, questi valori dovranno essere specificati (usando la funzione CryptSetKeyParam) allo stesso modo in cui erano stati specificati nel processo di cifratura.

·        Tutti i vettori di inizializzazione che sono stati specificati nel momento in cui i dati sono stati cifrati. Questi valori  sono manipolati allo stesso modo dei valori aggiuntivi.

Tutti i parametri che sono stati specificati tramite la funzione CryptSetKeyParam nel momento in cui il messaggio è stato cifrato devono anche essere specificati nel momento in cui il messaggio viene decifrato. Questi parametri possono essere memorizzati con il messaggio cifrato.

 

Decifratura di file e messaggi usando la CryptoAPI

 

Se un messaggio è stato cifrato per un particolare utente, prima di effettuare la cifratura, è stata usata la funzione CryptGenKey per creare una chiave di sessione casuale. Ciò significa che prima che il messaggio possa essere decifrato, il BLOB della chiave contenente la chiave di sessione deve essere inserito nel CSP con la funzione CryptImportKey. Questa funzione usa la exchange private key per decifrare il BLOB. Ciò significa che il BLOB deve essere stato cifrato usando la exchange private key dell’utente che decifrerà il messaggio.

Se il messaggio è stato cifrato in modo che ogni possessore di password possa accedere ai dati, la funzione CryptImportKey non viene usata. Invece, si crea una chiave di sessione di decifratura con la funzione CryptDeriveKey. È necessario fornire, insieme alla funzione, anche la password (o un altro token d’accesso).

I parametri della chiave di sessione devono essere configurati allo stesso modo in cui sono stati configurati durante la cifratura. Questi parametri possono essere specificati usando la funzione CryptSetKeyParam.

Il messaggio viene decifrato usando la funzione CryptDecrypt. Se il messaggio è troppo ampio perché sia contenuto comodamente in memoria, può essere decifrato a sezioni, attraverso chiamate multiple a CryptDecrypt.

Quando la decifratura è completa, ci si deve assicurare di distruggere la chiave di sessione usando la funzione CryptDestroyKey. Oltre alla distruzione della chiave, questa funzione libererà le risorse CSP.

 

Cifratura e decifratura simultanee usando la CryptoAPI

 

Cifrare e decifrare due flussi di dati simultaneamente con la stessa chiave crittografica provoca alcuni svantaggi. La stessa chiave di sessione non deve essere usata per entrambe le operazioni visto che la chiave di sessione contiene informazioni sullo stato interno, che verrebbero mescolate se venisse usata per più di una operazione alla volta. Una semplice soluzione a questo problema è di creare una copia della chiave di sessione. In questo modo, la chiave originale può essere usata per un’operazione e la copia per l’altra.

La copia di una chiave di sessione viene fatta esportando la chiave con CryptExportKey e usando CryptImportKey per importarla. Quando la chiave viene importata, il CSP darà alla nuova chiave una sua sezione di memoria interna, come se non fosse relata alla chiave originale.

Il seguente frammento di codice mostra come può essere ottenuta una copia della chiave di sessione.

 

HCRYPTPROV hProv;                         // Handle ad un CSP

HCRYPTKEY hKey;                           // Handle ad una chiave di sessione

HCRYPTKEY hCopyKey = 0;

HCRYPTKEY hPubKey = 0;

BYTE pbBlob[256];

DWORD dwBlobLen;

 

// Ottenere un handle alla exchange public key dell’utente corrente

CryptGetUserKey(hProv, AT_KEYEXCHANGE, &hPubKey);

 

// Esportare la chiave di sessione in un BLOB della chiave

dwBlobLen = 256;

CryptExportKey(hKey, hPubKey, SIMPLEBLOB, 0, pbBlob, &dwBlobLen);

 

// Importare la chiave di sessione nel CSP. Questa viene memorizzata

// separatamente dalla chiave di sessione originale

CryptImportKey(hProv, pbBlob, dwBlobLen, 0, 0, &hCopyKey);

 

...

 

Hash e firme digitali

 

Con le funzioni di hashing e di firma digitale, un utente può firmare i dati digitalmente in modo che ogni altro utente possa verificare che i dati non sono stati modificati una volta firmati. Anche l’identità dell’utente che firma i dati può essere verificata.

Una firma digitale consiste di un piccolo quantitativo di dati binari, tipicamente meno di 256 byte. Questa firma può essere impacchettata con il messaggio firmato o memorizzata separatamente, a seconda di come una particolare applicazione sia stata implementata.

Il Microsoft Base Cryptographic Provider crea firme digitali conformi al RSA Public-Key Cryptography Standard (PKCS).

 

Hash di dati

 

Un hash di un testo o di una qualunque stringa di byte è un valore unico e di lunghezza fissata associatogli. In alcuni documenti, un hash di un testo è anche chiamato digest. Le funzioni della CryptoAPI forniscono un mezzo per creare un hash di un testo o di qualsiasi altra stringa di byte. Quell’hash, quindi, può essere usato come identificatore unico dei dati ad esso associati.

Per assicurare l’integrità di un testo, l’hash può essere inviato assieme al testo. Il ricevitore può quindi calcolare un hash sul dato ricevuto e confrontare l’hash calcolato con l’hash ricevuto. Se i due coincidono, il dato ricevuto è lo stesso dato per il quale era stato creato l’hash ricevuto.

Per ottenere un valore hash, si deve creare un oggetto hash usando CryptCreateHash. Questo oggetto accumulerà i dati da verificare. I dati vengono quindi aggiunti all’oggetto hash tramite la funzione CryptHashData.

Dopo che l’ultimo blocco di dati è stato aggiunto all’hash, la funzione CryptGetHashParam viene usata per ottenere il valore hash dei dati.

Una sicurezza migliore è fornita distruggendo l’oggetto hash con CryptDestroyHash non appena il valore hash è stato ottenuto.

 

Firme digitali

 

Le firme digitali possono essere usate per distribuire un messaggio in chiaro quando i destinatari devono identificare e verificare il trasmittente del messaggio. Firmare un messaggio non altera il messaggio; ciò genera semplicemente una stringa che può anche essere impacchettata assieme al messaggio o trasmessa separatamente. Una firma digitale è un pezzo breve di dati cifrati con la chiave privata del trasmittente. Decifrare dei dati firmati usando la chiave pubblica del trasmittente prova che i dati sono stati cifrati dal trasmittente o da qualcuno che ha avuto accesso alla chiave privata del trasmittente.

Le firme digitali sono generate usando gli algoritmi di firma a chiave pubblica. Una chiave privata serve per cifrare i dati e generare la firma; invece, la chiave pubblica corrispondente deve essere usata per decifrare i dati e per validare la firma. Questo processo è mostrato nella seguente illustrazione.

 

 

 

Ci sono due passi coinvolti nella creazione di una firma digitale di un messaggio. Il primo passo implica la creazione di un valore hash (anche conosciuto come digest del messaggio). Questo valore hash è quindi firmato usando la chiave privata del firmatario. La seguente è un’illustrazione dei passi coinvolti nella creazione di una firma digitale.

 

 

Per verificare una firma, sono richiesti sia il messaggio che la firma. Prima, deve essere creato un valore hash del messaggio allo stesso modo in cui è stata creata la firma. Questo valore hash è quindi confrontato con la firma usando la chiave pubblica del firmatario. Se il valore hash e la firma coincidono, si può essere sicuri che il messaggio è davvero quello firmato dal firmatario e che non c’è stata interferenza. Il seguente diagramma illustra il processo coinvolto nella verifica della firma digitale.

 

Un valore hash consiste di un piccolo quantitativo di dati binari, tipicamente intorno ai 160 bit.

Questo viene fornito usando un algoritmo di hashing.

Tutti i valori hash condividono le seguenti proprietà, indifferentemente dall’algoritmo usato:

·       La lunghezza del valore hash è determinata dal tipo di algoritmo usato e la sua lunghezza non varia con la dimensione del messaggio. Le lunghezze più comuni del valore hash sono 128 o 160 bit.

·       Per ogni coppia di messaggi non identici si hanno due valori hash completamente differenti, anche se i due messaggi differiscono solo per un singolo bit. Usando la tecnologia odierna, non è possibile accorgersi, senza rompere l’algoritmo di hashing, se due messaggi sono tradotti nello stesso valore hash.

·        Ogni volta che ad un particolare messaggio viene associato un valore hash usando lo stesso algoritmo, viene prodotto lo stesso valore hash.

·       Tutti gli algoritmi di hashing sono one-way. Dato un valore hash, non è possibile riottenere il messaggio originale. Infatti, nessuna delle proprietà del messaggio originale può essere determinata dato il solo valore hash.

 

Firme di messaggi e verifica della firma

 

Per applicare una firma digitale a dei dati, viene usata una funzione hash sicura per costruire un hash (digest del messaggio) dei dati. Questo hash viene quindi cifrato con la chiave privata del firmatario. Gli altri utenti possono quindi verificare l’autenticità della firma ricostruendo il valore hash e testandolo tramite la decifrazione della firma digitale. La CryptoAPI astrae il metodo di firma, così che gli sviluppatori di applicazioni non hanno bisogno di sapere i dettagli del meccanismo di firma.

 

Messaggi firmati

 

Per firmare un messaggio od ogni altro dato, si deve creare un oggetto hash usando CryptCreateHash. Questo oggetto hash accumula i dati da firmare. In seguito, i dati sono aggiunti all’oggetto hash tramite la funzione CryptHashData.

Dopo che l’ultimo blocco di dati è stato aggiunto all’hash, la funzione CryptSignHash viene usata per firmare l’hash. Può anche essere aggiunta, a questo punto, all’oggetto hash, una descrizione dei dati. Dopo aver ottenuto la firma digitale, per fornire sicurezza, si deve distruggere l’oggetto hash con CryptDestroyHash.

Gli hash possono essere firmati o con la signature private key o con la exchange private key. La chiave di firma è raccomandata quando l’utente che la possiede sta firmando i suoi dati. La chiave di scambio è raccomandata quando si firmano dati che non appartengono direttamente all’utente, come quando si firmano le chiavi di sessione durante il protocollo di scambio della chiave.

Un singolo messaggio può essere firmato da più di un firmatario. Uno o più firmatari possono fornire un hash al messaggio originale e cifrarlo, oppure la firma del messaggio stessa può essere firmata tramite la creazione di un hash e la sua cifratura.

 

Verificare una firma

 

Per verificare una firma, si deve creare un oggetto hash usando CryptCreateHash. Questo oggetto hash accumula i dati da verificare. I dati sono quindi aggiunti all’oggetto hash tramite la funzione CryptHashData.

Dopo che l’ultimo blocco è stato aggiunto all’hash, si deve usare CryptVerifySignature per verificare la firma. L’indirizzo della firma, un handle all’oggetto hash e l’handle della chiave pubblica vengono passati, come parametri, alla funzione CryptVerifySignature.

Dopo che la firma è stata verificata (o la verifica è fallita), si deve distruggere l’oggetto hash tramite la funzione CryptDestroyHash.

 

Certificati digitali

 

L’autenticazione è cruciale per comunicazioni sicure. Gli utenti devono essere in grado di provare la loro identità a coloro con i quali essi comunicano e devono essere in grado di verificare l’identità degli altri. L’autenticazione dell’identità in una rete è complessa perché le parti della comunicazione non si incontrano fisicamente durante la comunicazione. Questo può permettere ad una persona immorale di intercettare i messaggi o di spacciarsi per un’altra persona o entità. Deve essere studiato un metodo per mantenere il necessario livello di fiducia all’interno del processo di comunicazione.

Il certificato digitale è una credenziale comune che fornisce un mezzo di verifica dell’identità. Un certificato è un insieme di dati che identifica un’entità. Un’organizzazione fiduciaria assegna un certificato ad un individuo o ad un’entità che associa una chiave pubblica ad un individuo. L’individuo o l’entità a cui il certificato viene rilasciato viene chiamato soggetto del certificato. L’organizzazione fiduciaria che rilascia il certificato è una certification authority (CA). Una CA attendibile rilascerà un certificato solo dopo aver verificato l’identità di un soggetto del certificato.

I certificati usano le tecniche di crittografia per risolvere il problema della mancanza di un contatto fisico fra i comunicanti. L’uso di queste tecniche limita, ad una persona immorale, la possibilità di intercettare, alterare o falsificare i messaggi. Queste tecniche di crittografia rendono i certificati difficili da modificare. Quindi, è difficile per un’entità di impersonarne un’altra.

I dati contenuti in un certificato includono la chiave pubblica appartenente alla coppia di chiavi pubblica/privata del soggetto del certificato. Un messaggio firmato con la chiave privata del trasmittente può essere estratto dal destinatario del messaggio solo usando la chiave pubblica del trasmittente. Questa chiave può essere trovata in una copia del certificato del trasmittente. Estrarre una firma da un certificato tramite una chiave pubblica prova che la firma è stata prodotta usando la chiave privata del soggetto del certificato. Se il trasmittente è stato vigile ed ha mantenuto la chiave privata segreta, il ricevitore può essere fiducioso dell’identità del trasmettitore del messaggio.

Su una rete, c’è spesso un’applicazione fidata conosciuta come certificate server. Un CA funzionante su un computer sicuro gestisce il certificate server. Questa applicazione ha accesso alla chiave pubblica di tutti i suoi client. I certificate server distribuiscono messaggi conosciuti come certificati, ognuno dei quali contiene la chiave pubblica di uno dei suoi utenti client. Ogni certificato è firmato con la chiave privata del CA. Quindi il ricevitore di tale certificato può verificare quale CA gli ha inviato il certificato.

I certificati digitali includono anche estensioni e proprietà estese che forniscono informazioni aggiuntive sul soggetto del certificato come l’indirizzo e-mail del soggetto e le attività che il soggetto del certificato può svolgere.

 

Certificazione digitale X.509

 

Un compito fondamentale di un certificato digitale è di fornire l’accesso alla chiave pubblica del soggetto. Il certificato conferma anche l’appartenenza della chiave pubblica del certificato al soggetto. Per esempio, una CA può firmare digitalmente un messaggio speciale contenente il nome di un utente, diciamo “Alice”, e la sua chiave pubblica. Questo deve essere fatto in modo tale che tutti possano verificare che il certificato sia stato emesso e firmato da nessuno al di fuori della CA. Se la CA è fidata e può essere verificato che il certificato di Alice sia stato emesso da quella CA, tutti i ricevitori del certificato di Alice possono estrarre la chiave pubblica di Alice da quel certificato.

L’implementazione tipica del certificato digitale implica un processo di firma del certificato. Il processo funziona così:

1.      Alice manda una richiesta firmata alla CA, contenente il suo nome, la sua chiave pubblica e facoltativamente alcune informazioni addizionali.

2.      La CA crea un messaggio, m, alla richiesta di Alice. La CA firma il messaggio con la sua chiave privata, creando un messaggio di firma separato, sig. La CA restituisce il messaggio, m, e la firma, sig, ad Alice.

3.      Alice manda entrambe le parti del suo certificato a Bob per dargli accesso alla sua chiave pubblica.

4.      Bob verifica la firma, sig, usando la chiave pubblica della CA. Se la firma risulta valida, accetta la chiave pubblica del certificato come chiave pubblica di Alice.

Come con una firma digitale, tutti i ricevitori con accesso alla chiave pubblica della CA possono determinare se una determinata CA ha firmato il certificato. Questo processo non richiede nessun accesso a nessuna informazione segreta. Lo scenario appena presentato presume che Bob abbia accesso alla chiave pubblica della CA. Bob avrebbe accesso alla chiave solo se in possesso di una copia del certificato della CA contenente la chiave pubblica.

I certificati digitali X.509 includono non solo il nome di un utente, ma anche altre informazioni sull’utente. Questi certificati abilitano la CA a fornire al ricevitore di un certificato, come mezzo di fiducia, non solo la chiave pubblica del soggetto del certificato, ma anche altre informazioni sul soggetto del certificato. Quelle altre informazioni possono includere, fra le altre cose, un indirizzo e-mail, un’autorizzazione per firmare documenti di un dato valore o l’autorizzazione a diventare una CA e firmare altri certificati.

I certificati X.509 e molti altri certificati hanno una durata di validità nel tempo. Un certificato può scadere e non sarà più valido. Una CA può revocare un certificato per molte ragioni. Per manipolare le revoche, una CA mantiene e distribuisce una lista di certificati revocati, chiamata certificate revocation list (CRL). Gli utenti della rete accedono alla CRL per determinare la validità di un certificato.

 

Certificati e CryptoAPI

 

La CryptoAPI supporta l’uso dei certificati come definito nella raccomandazione X.509.

Un certificato che rispetta lo standard X.509 contiene le seguenti informazioni.

 

Campo

Descrizione

Version

Numero di versione del certificato

Serial number

Numero seriale del certificato

Algorithm identifier

Algoritmo di firma usato dal firmatario del certificato

Issuer name

Nome dell’emittente del certificato

Not before

Data prima della quale il certificato non è valido

Not after

Data dopo la quale il certificato non è valido

Subject name

Nome della persona o dell’entità alla quale il certificato è stato emesso

Algorithm

Algoritmo usato per la chiave pubblica

Subject public key

Chiave pubblica attuale

Issuer unique ID

Campo opzionale. Se presente, la versione è la seconda

Subject unique ID

Campo opzionale. Se presente, la versione è la seconda

Extensions

Campo opzionale. Contiene dati addizionali che un emittente può voler aggiungere al certificato, come l’indirizzo e-mail o l’autorizzazione ad emettere certificati. Se presente, la versione è la terza.

 

Codifica e decodifica del contesto di un certificato

 

La CryptoAPI supporta la codifica e la decodifica dei certificati. La CryptoAPI include un sistema di funzioni e strutture C esteso e flessibile che permette la codifica e la decodifica in molti modi. La CryptoAPI supporta la struttura dei certificati secondo lo standard X.509 e la codifica secondo lo standard ASN.1 per fornire interoperabilità con altri sistemi.

 

Contesti dei certificati

 

Il contesto di un certificato, CERT_CONTEXT, è una struttura C che contiene un membro codificato, un handle ad un archivio dei certificati, un puntatore al certificate BLOB originale codificato ed un puntatore ad una struttura C CERT_INFO.

La struttura CERT_INFO è il cuore del certificato. Essa contiene, in forma diretta ed in forma codificata, tutte le informazioni fondamentali del certificato. La seguente illustrazione mostra la struttura CERT_INFO in cui tutti i membri codificati sono mostrati ombreggiati.

 

 

 

I membri IssuerUniqueID e SubjectUniqueID fanno parte della seconda versione di X.509 ma sono raramente usati.  Le estensioni del certificato, che fanno parte della terza versione, sostituiscono le funzionalità di questi membri.

Se le informazioni contenute nei membri codificati IssuerUniqueID e SubjectUniqueID sono necessarie, questi membri devono essere decodificati. Si usa CryptDecodeObject per decodificare questi membri. La seguente illustrazione descrive il processo di decodifica di uno di questi membri.

 

 

 

Nel caso illustrato, la funzione CryptDecodeObject crea una struttura CERT_NAME_INFO, un array di strutture CERT_RDN, un array corrispondente di strutture CERT_RDN_ATTR ed una stringa contenente il nome. I membri della struttura CERT_RDN_ATTR determinano i contenuti della stringa. Per esempio, se il membro pszObjId è 2.5.4.3, la stringa contiene un nome comune. Se è 2.5.4.10, la stringa conterrà il nome di un’organizzazione.

Il membro dwValueType contiene informazioni sul tipo della stringa. Se il suo valore è rappresentato dalla costante CERT_RDN_PRINTABLE_STRING, il membro contiene una stringa formata da caratteri di 7 bit, terminante per zero. Se il suo valore è la costante CERT_RDN_UNICODE_STRING, la stringa è formata da caratteri di 8 bit.

 

Operazioni con i certificati

 

La CryptoAPI fornisce funzioni per lavorare con i certificati, con le certificate revocation list (CRL) e con le certificate trust list (CTL). Queste includono funzioni per convertire i tipi codificati in tipi contesto, funzioni che duplicano gli oggetti e funzioni che rilasciano questi oggetti. Le funzioni della CryptoAPI per convertire i tipi codificati in tipi contesto codificano informazioni in contesti ma non aggiungono questi contesti a nessun archivio. Queste funzioni sono CertCreateCertificateContext, CertCreateCRLContext e CertCreateCTLContext. Si deve usare la funzione CertFree appropriata per rilasciare i contesti creati da ognuna di queste funzioni.

Le funzioni della CryptoAPI per duplicare i certificati, le CRL ed i CTL incrementano il reference counter del contesto specificato e restituiscono un puntatore al contesto. Le funzioni duplicanti non allocano spazio addizionale e non copiano nemmeno i dati da un contesto ad una nuova locazione di memoria. Queste funzioni sono CertDuplicateCertificateContext, CertDuplicateCRLContext e CertDuplicateCTLContext. I contesti creati con una di queste funzioni devono essere rilasciati usando la CertFree appropriata.

Le funzioni della CryptoAPI che rilasciano i certificati, le CRL e le CTL sono CertFreeCertificateContext, CertFreeCRLContext e CertFreeCTLContext. Ognuna di queste funzioni decrementa il reference counter di un contesto. Se il reference count raggiunge zero, la memoria allocata per il contesto viene rilasciata.

 

Proprietà estese dei certificati

 

I dati contenuti nel contesto di un certificato, di una CRL o di una CTL, incluse tutte le estensioni, sono a sola lettura e quindi non sono modificabili. Tuttavia, sulle piattaforme Microsoft, i certificati della CryptoAPI hanno anche proprietà dinamiche estese che possono essere aggiunte e cambiate.

È da notare che le proprietà estese sono associate ad un certificato, ma non vi fanno parte perché non sono emesse da una certification authority (CA). Le proprietà estese non sono disponibili su un certificato quando questo viene usato su una piattaforma non-Microsoft.

Queste proprietà includono dati che:

·        Sono legati ad una chiave privata per essere usati con i certificati.

·        Indicano il tipo di hash da svolgere sul certificato.

·        Forniscono informazioni user-defined associate al certificato.

Sulle piattaforme Microsoft, i valori di queste proprietà sono inserite e si muovono con i certificati. Le proprietà predefinite, identificate dai property ID, includono:

·        CERT_KEY_PROV_HANDLE_PROP_ID, CERT_KEY_PROV_INFO_PROP_ID, e CERT_KEY_CONTEXT_PROP_ID. Queste proprietà legano un certificato ad un particolare CSP e, all’interno di quel CSP, ad una particolare chiave privata.

·        CERT_SHA1_HASH_PROP_ID e CERT_MD5_HASH_PROP_ID. Queste proprietà indicano che l’algoritmo di hashing deve essere usato quando viene svolta un’operazione di hashing.

 

Certificati e messaggi

 

Le funzioni fondamentali di crittografia usano i certificati, le funzioni a basso livello per i messaggi e le funzioni semplificate per i messaggi. Per mandare o ricevere messaggi che incorporano certificati, si devono usare le funzioni a basso livello per i messaggi.

 

Funzioni a basso livello

 

Le funzioni a basso livello per i messaggi codificano dati per la trasmissione e decodificano i dati che sono stati ricevuti. Le funzioni a basso livello per i messaggi decifrano e verificano le firme dei messaggi ricevuti.

Quando un messaggio viene aperto, usando una funzione di open a basso livello per i messaggi, rimane aperto e disponibile (mantiene il suo stato) finché non viene chiuso. Ciò permette ad un messaggio di essere costruito usando chiamate multiple alla funzione CryptMsgUpdate.

L’utilizzo delle funzioni a basso livello per i messaggi richiede più chiamate a funzione rispetto all’utilizzo delle funzioni semplificate per i messaggi. Se vengono usate le funzioni semplificate per i messaggi, la maggior parte del lavoro è svolto all’interno delle funzioni dell’API.

L’uso delle funzioni a basso livello per i messaggi implica lavoro addizionale per effettuare delle chiamate ad altre funzioni.

 

Esempio: codifica e decodifica di un hashed message

 

Il seguente è un esempio di programma che crea un hash su un messaggio di testo e lo codifica; quindi, lo decodifica e verifica il messaggio. Sebbene per semplicità le due differenti funzioni sono state combinate insieme, in questo programma, è più realistico che le due parti siano usate separatamente.

Questo esempio illustra i seguenti compiti e funzioni della CryptoAPI:

·        Chiama CryptAcquireContext per acquisire un provider CSP.

·        Usa CryptMsgCalculateEncodedLength per calcolare la lunghezza del messaggio codificato.

·        Alloca memoria per un buffer che contenga i dati codificati.

·        Apre un messaggio per codificarlo usando CryptMsgOpenToEncode.

·        Aggiunge il contenuto al messaggio da codificare usando CryptMsgUpdate.

·        Usa CryptMsgGetParam per copiare il messaggio codificato nel buffer allocato.

·        Apre un messaggio per decodificarlo usando CryptMsgOpenToDecode.

·        Aggiunge il messaggio codificato al messaggio da decodificare usando CryptMsgUpdate.

·        Crea un puntatore duplicato al messaggio usando CryptMsgDuplicate.

·        Testa il tipo di messaggio con CryptMsgGetParam.

·        Usa CryptMsgGetParam per decodificare il messaggio.

·        Verifica l’hash usando CryptMsgControl.

·        Usa CryptMsgClose per rilasciare l’handle al messaggio.

·        Usa CryptReleaseContext per rilasciare il CSP.

 

#include <stdio.h>

#include <windows.h>

#include <wincrypt.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

 

void HandleError(char *s);

 

void main(void)

{

 //-----------------------------------------------------------------------------

 //  Dichiarazione ed inizializzazione delle variabili. Ciò include la creazione

 //  di un puntatore al contenuto del messaggio. In situazioni reali, il

 //  contenuto del messaggio esisterà laddove sarà passato all’applicazione un

 //  puntatore ad esso.

 

 BYTE* pbContent = (BYTE*) "A razzle-dazzle hashed message \n"

        "Hashing is better than trashing. \n";    // Messaggio

 DWORD cbContent = strlen((char *)pbContent)+1;   // Dimensione del messaggio,

                                                  // incluso il NULL alla fine.

 HCRYPTPROV hCryptProv;                           // Handle al CSP

 DWORD HashAlgSize;

 CRYPT_ALGORITHM_IDENTIFIER HashAlgorithm;

 CMSG_HASHED_ENCODE_INFO HashedEncodeInfo;

 DWORD cbEncodedBlob;

 BYTE *pbEncodedBlob;

 HCRYPTMSG hMsg;

 HCRYPTMSG hDupMsg;

 

 //-----------------------------------------------------------------------------

 //  Variabili usate nella decodifica.

 

 DWORD cbData = sizeof(DWORD);

 DWORD dwMsgType;

 DWORD cbDecoded;

 BYTE  *pbDecoded;

 

 //-----------------------------------------------------------------------------

 //  Inizio dell’elaborazione.

 

 printf("Inizio elaborazione. \n");

 printf("Il messaggio che deve essere hashed e codificato è: \n");

 printf("%s\n",pbContent);       // Visualizza il messaggio originale.

 printf("La lunghezza del messaggio iniziale è %d\n",cbContent);

 

 //-----------------------------------------------------------------------------

 // Acquisizione di un handle al contesto di un cryptographic provider.

 

 if (CryptAcquireContext(

            &hCryptProv,         // Indirizzo dell’handle.

            NULL,                // Usa il logon name dell’utente corrente.

            NULL,                // Usa il provider di default.

            PROV_RSA_FULL,       // Tipo del provider.

            0))                  // Zero permette l’accesso alla chiave privata.

    {

     printf("Il contesto di un CSP è stato acquisito. \n");

    }

 else

    {

     HandleError("CryptAcquireContext fallita.");

    }

 

 //-----------------------------------------------------------------------------

 // La funzione ha successo; hCryptProv è l’handle al CSP.

 

 //-----------------------------------------------------------------------------

 // Inizializza la struttura identificativa dell’algoritmo.

 

 HashAlgSize = sizeof(HashAlgorithm);

 memset(&HashAlgorithm, 0, HashAlgSize);   // Inizializza a zero.

 HashAlgorithm.pszObjId = szOID_RSA_MD5;   // Quindi setta il membro necessario.

 

 

 //-----------------------------------------------------------------------------

 // Inizializza la struttura CMSG_HASHED_ENCODE_INFO.

 

 memset(&HashedEncodeInfo, 0, sizeof(CMSG_HASHED_ENCODE_INFO));

 HashedEncodeInfo.cbSize = sizeof(CMSG_HASHED_ENCODE_INFO);

 HashedEncodeInfo.hCryptProv = hCryptProv;

 HashedEncodeInfo.HashAlgorithm = HashAlgorithm;

 HashedEncodeInfo.pvHashAuxInfo = NULL;

 

 //-----------------------------------------------------------------------------

 // Ottiene la dimensione del message BLOB codificato.

 

 if (cbEncodedBlob = CryptMsgCalculateEncodedLength(

                     MY_ENCODING_TYPE,   // Tipo di codifica del messaggio.

                     0,                  // Flags.

                     CMSG_HASHED,        // Tipo di message.

                     &HashedEncodeInfo,  // Puntatore alla struttura.

                     NULL,               // Object ID del contenuto interno.

                     cbContent))         // Dimensione del contenuto.

    {

     printf("La lunghezza da allocare è %d bytes.\n",cbEncodedBlob);

    }

 else

    {

     HandleError("Ottenimento della lunghezza di cbEncodedBlob fallito");

    }

 

 //-----------------------------------------------------------------------------

 // Alloca la memoria per il BLOB codificato.

 

 if (pbEncodedBlob = (BYTE *) malloc(cbEncodedBlob))

    {

     printf("%d bytes di memoria sono stati allocati.\n",cbEncodedBlob);

    }

 else

    {

     HandleError("Operazione malloc fallita.");

    }

 

 //-----------------------------------------------------------------------------

 // Apertura di un messaggio per la codifica.

 

 if (hMsg = CryptMsgOpenToEncode(

            MY_ENCODING_TYPE,    // Tipo di codifica.

            0,                   // Flags.

            CMSG_HASHED,         // Tipo di messaggio.

            &HashedEncodeInfo,   // Puntatore alla struttura.

            NULL,                // Object ID del contenuto interno.

            NULL))               // Informazioni sul flusso (non usato).

    {

     printf("Il messaggio da codificare è stato aperto. \n");

    }

 else

    {

     HandleError("OpenToEncode fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Aggiornamento del messaggio con i dati.

 

 if (CryptMsgUpdate(

            hMsg,                // Handle al messaggio.

            pbContent,           // Puntatore al contenuto.

            cbContent,           // Dimensione del contenuto.

            TRUE))               // Ultima chiamata?

    {

     printf("I dati sono stati aggiunti al messaggio da codificare. \n");

    }

 else

    {

     HandleError("MsgUpdate fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Creazione di un duplicato del messaggio.

 

 if (hDupMsg = CryptMsgDuplicate(hMsg))

    {

     printf("Il messaggio è stato duplicato.\n");

    }

 else

    {

     HandleError("Duplicazione del messaggio fallita.");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere il messaggio originale dal duplicato del messaggio.

 

 if (CryptMsgGetParam(

            hDupMsg,             // Handle al messaggio.

            CMSG_CONTENT_PARAM,  // Tipo del parametro.

            0,                   // Indice.

            pbEncodedBlob,       // Puntatore al BLOB.

            &cbEncodedBlob))     // Dimensione del BLOB.

    {

     printf("Messaggio codificato con successo. \n");

    }

 else   

    {

     HandleError("MsgGetParam fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Chiudere entrambi i messaggi per prepararsi alla decodifica.

 

 CryptMsgClose(hMsg);

 CryptMsgClose(hDupMsg);

 

 // Il seguente codice decodifica il messaggio hashed.

 // Usualmente, questo sarebbe in un programma separato ed i dati codificati ed

 // hashed sarebbero in input da un file, da un messaggio e-mail o da qualche

 // altra sorgente.

 //

 // Le variabili usate in questo codice sono già state dichiarate ed

 // inizializzate.

 

 //-----------------------------------------------------------------------------

 // Apertura del messaggio per la decodifica.

 

 if (hMsg = CryptMsgOpenToDecode(

            MY_ENCODING_TYPE     // Tipo di decodifica.

            0,                   // Flags.

            0,                   // Tipo di messaggio (ottenuto dal messaggio).

            hCryptProv,          // Cryptographic provider.

            NULL,                // Informazioni sul destinatario.

            NULL))               // Informazioni sul flusso.

    {

     printf("Il messaggio è stato aperto per la decodifica. \n");

    }

 else

    {

     HandleError("OpenToDecode fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Aggiornamento del messaggio con il BLOB codificato.

 

 if (CryptMsgUpdate(

            hMsg,                // Handle al messaggio.

            pbEncodedBlob,       // Puntatore al BLOB codificato.

            cbEncodedBlob,       // Dimensione del BLOB codificato.

            TRUE))               // Ultima chiamata?

    {

     printf("I dati codificati sono aggiunti al messaggio da decodificare. \n");

    }

 else

    {

     HandleError("Decodifica di MsgUpdate fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere il tipo del messaggio.

 

 if (CryptMsgGetParam(

            hMsg,                // Handle al messaggio.

            CMSG_TYPE_PARAM,     // Tipo del parametro.

            0,                   // Indice.

            &dwMsgType,          // Indirizzo delle informazioni restituite.

            &cbData))            // Dimensione delle informazioni restituite.

    {

     printf("Il tipo del messaggio è stato ottenuto. \n");

    }

 else

    {

     HandleError("Decodifica di CMSG_TYPE_PARAM fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Alcune applicazioni possono aver bisogno qui di usare un costrutto switch

 // ed elaborare il messaggio differentemente, a seconda del tipo del messaggio.

 

 if (dwMsgType == CMSG_HASHED)

    {

     printf("Il messaggio è un hashed message. Sto procedendo. \n");

    }

 else

    {

     HandleError("Tipo del messaggio errato");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere la dimensione del contenuto.

 

 if (CryptMsgGetParam(

            hMsg,                // Handle al messaggio.

            CMSG_CONTENT_PARAM,  // Tipo del parametro.

            0,                   // Indice.

            NULL,                // Indirizzo delle informazioni restituite.

            &cbDecoded))         // Dimensione delle informazioni restituite.

    {

     printf("LA lunghezza %d del messaggio è stata ottenuta. \n", cbDecoded);

    }

 else

    {

     HandleError("Decodifica di CMSG_CONTENT_PARAM fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Allocazione di memoria.

 

 if (pbDecoded = (BYTE *) malloc(cbDecoded))

    {

     printf("La memoria per il messaggio decodificato è stata allocata.\n");

    }

 else

    {

     HandleError("Allocazione della memoria di decodifica fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Copia del messaggio decodificato nel buffer appena allocato.

 

 if (CryptMsgGetParam(

            hMsg,                   // Handle al messaggio.

            CMSG_CONTENT_PARAM,     // Tipo del parametro.

            0,                      // Indice.

            pbDecoded,              // Indirizzo delle informazioni restituite.

            &cbDecoded))            // Dimensione delle informazioni restituite.

 

    {

     printf("Messaggio decodificato con successo \n");

     printf("Il messaggio decodificato è \n%s\n", (LPSTR)pbDecoded);

    }

 else

    {

     HandleError("Decodifica di CMSG_CONTENT_PARAM #2 fallita");

    }

 

 //-----------------------------------------------------------------------------

 // Verifica dell’hash.

 

 if (CryptMsgControl(

            hMsg,                        // Handle al messaggio.

            0,                           // Flags.

            CMSG_CTRL_VERIFY_HASH,       // Tipo di controllo.

            NULL))                       // Puntatore non usato.

    {

     printf("Verifica dell’hash completata con successo. \n");

     printf("I dati non sono stati interferiti.\n");

    }

 else

    {

     printf("Verifica dell’hash fallita. Qualcuno ha cambiato questo messaggio .\n");

    }

 

 printf("Programma di test completato senza errori. \n");

 

 //-----------------------------------------------------------------------------

 // Ripulitura

 

 if (pbEncodedBlob)

    free(pbEncodedBlob);

 if (pbDecoded)

    free(pbDecoded);

 

 CryptMsgClose(hMsg);

 

 // Rilascio del CSP.

 

 if (hCryptProv)

    CryptReleaseContext(hCryptProv,0);

} // Fine del main

 

//-----------------------------------------------------------------------------

//  Questo esempio usa la funzione HandleError, una semplice funzione di

//  gestione dell’errore, per stampare nel file di standard error un messaggio

//  di errore (stderr) file ed uscire dal programma.

//  Per la maggior parte delle applicazioni, si sostituisce questa funzione con

//  una che effettua dei report di errore più esaurienti.

 

void HandleError(char *s)

{

 fprintf(stderr,"Un errore è avvenuto eseguendo il programma. \n");

 fprintf(stderr,"%s\n",s);

 fprintf(stderr, "Error number %x.\n", GetLastError());

 fprintf(stderr, "Programma terminato. \n");

 exit(1);

} // Fine di HandleError

 

Funzioni semplificate

 

È stato fornito un gruppo di funzioni ad alto livello per semplificare ed abbreviare ampi quantitativi di codice necessari per svolgere i compiti usuali di manipolazione del messaggio. Queste funzioni sono chiamate funzioni semplificate per i messaggi. I nomi di tutte le funzioni semplificate per i messaggi contengono la parola "Message".

Le funzioni semplificate per i messaggi sono ad un livello più alto rispetto alle funzioni crittografiche di base o alle funzioni a basso livello per i messaggi. Esse contengono in una singola funzione molte funzioni crittografiche di base, funzioni a basso livello per i messaggi e funzioni per i certificati che svolgono un compito specifico in un modo specifico, proprio come cifrare un messaggio PKCS #7 o firmare un messaggio. Le funzioni semplificate per i messaggi forniscono un modo veloce di iniziare usando la CryptoAPI, per ridurre il numero di funzioni da invocare per svolgere il compito.

 

Gestione dei certificati con gli archivi dei certificati

 

In un certo periodo di tempo, i certificati si accumuleranno sul computer di un utente. Sono necessari, quindi, dei tool per gestire questi certificati. La CryptoAPI fornisce questi tool come funzioni di store, retrieve, cancellazione, elencazione (enumerazione) e verifica dei certificati. La CryptoAPI fornisce anche i mezzi per collegare i certificati ai messaggi.

Sono disponibili due principali categorie di funzioni: funzioni che gestiscono gli archivi dei certificati e funzioni che lavorano con i certificati, le CRL e le CTL all’interno di questi archivi. Le funzioni che gestiscono gli archivi dei certificati includono funzioni per lavorare con gli archivi logici o virtuali, archivi remoti, archivi esterni e archivi rilocabili.

I certificati, le CRL e le CTL possono essere mantenuti negli archivi dei certificati. Essi, per essere usati nei processi di autenticazione, possono essere recuperati dall'archivio dove persistono.

L'archivio dei certificati è fondamentale per tutte le funzionalità dei certificati. I certificati sono gestiti nell'archivio usando le funzioni con il prefisso “Cert”. Un archivio dei certificati tipico è una lista concatenata di certificati, come mostrato nel seguente diagramma.

 

 

L’illustrazione mostra che:

·        Ogni archivio dei certificati ha un puntatore al blocco del primo certificato dell'archivio.

·        Il blocco di ogni certificato include un puntatore ai dati del certificato ed un puntatore “next” al blocco del certificato successivo nell'archivio.

·        Il puntatore “next” dell’ultimo blocco è settato a NULL.

·        Il blocco dei dati di un certificato contiene il contesto, a sola lettura, del certificato e tutte le proprietà estese del certificato.

·        Il blocco dei dati di un certificato contiene un reference count che mantiene traccia del numero di puntatori, esistenti, al certificato.

I certificati in un archivio vengono normalmente mantenuti in alcuni tipi di memorie permanenti come un file del disco o il registro del sistema. Gli archivi dei certificati possono anche essere creati ed aperti rigorosamente in memoria. Un archivio in memoria fornisce una memorizzazione temporanea dei certificati per lavorare con i certificati che non necessitano di essere conservati.

Ogni utente ha un MY store personale dove i certificati dell’utente vengono memorizzati. Il MY store può stare in qualunque delle varie locazioni fisiche, inclusi il registro di un computer locale o remoto, un file del disco, un database, una smart card o un’altra locazione. Sebbene un certificato possa essere memorizzato nel MY store, questo archivio dovrebbe essere riservato ai certificati personali di un utente, cioè quei certificati usati per firmare e decifrare i messaggi dell’utente.

L’utilizzo dei certificati per l’autenticazione dipende dal fatto di possedere certificati emessi da emittenti fidate di certificati. I certificati delle emittenti fidate di certificati sono tipicamente conservati nel ROOT store, il quale persiste in una sottochiave del registro. Nel contesto della CryptoAPI, il ROOT store viene protetto e dei dialog speciali dell’interfaccia utente (UI) ricordano all’utente di porre, in quell'archivio, solo certificati fidati. In situazioni di reti d’impresa, i certificati devono essere copiati da un amministratore di sistema dal computer controllore del dominio ai ROOT store sui computer client. Questo processo consente a tutti i membri di un dominio di avere trust list simili.

Altri certificati possono essere memorizzati nell'archivio di sistema del CA o in un archivio file-based creato dall’utente.

 

Esempio: operazioni con gli archivi dei certificati

 

Il seguente programma mostra una serie di operazioni comuni sugli archivi dei certificati.

Questo programma illustra i seguenti compiti e funzioni della CryptoAPI:

·        Aprire e chiudere gli archivi presenti in memoria e nel sistema usando CertOpenStore e CertCloseStore.

·        Duplicare un archivio aperto usando CertDuplicateStore.

·        Trovare, negli archivi, certificati che vengano incontro ai criteri di ricerca usando CertFindCertificateInStore.

·        Creare un nuovo contesto del certificato dalla porzione codificata del certificato esistente usando CertCreateCertificateContext.

·        Aggiungere un certificato trovato ad un archivio in memoria usando CertAddCertificateContextToStore.

·        Aggiungere un link ad un certificato in un archivio usando CertAddCertificateLinkToStore.

·        Salvare l'archivio presente in memoria in un file sul disco.

·        Aprire e chiudere un archivio dei certificati file-based.

 

//------------------------------------------------------------------------------

// Questo programma crea un archivio dei certificati in memoria. Viene aperto e

// duplicato un archivio del sistema. Viene ricercato un certificato nell'archivio

// del sistema. Viene creato un nuovo certificato dalla porzione cifrata del

// certificate trovato. Il certificato trovato viene aggiunto all'archivio in

// memoria. Viene ricercato un secondo certificato dal MY store e viene aggiunto

// un link a quel certificate nell'archivio in memoria. Il certificato ed il link

// vengono quindi trovati nell'archivio in memoria e la memoria viene salvata sul

// disco. Tutti gli archivi ed i file vengono chiusi. Dopo, l'archivio nel file

// viene riaperto e viene svolta una ricerca del link del certificato. Il

// successo di questo programma dipende da se è disponibile un MY store. Quel-

// l'archivio deve includere un certificato con soggetto "Insert_cert_subject_name1",

// ed un secondo messaggio con soggetto "Insert_cert_subject_name2". I nomi dei

// soggetti devono essere cambiati con i nomi dei soggetti dei certificati

// conosciuti tramite il MY store.

 

#include <stdio.h>

#include <windows.h>

#include <wincrypt.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

 

void HandleError(char *s);

 

void main(void)

{

 //-----------------------------------------------------------------------------

 // Dichiarazione ed inizializzazione delle variabili.

 

 HCERTSTORE  hSystemStore;                // Handle all'archivio del sistema.

 HCERTSTORE  hMemoryStore;                // Un handle all'archivio in memoria.

 HCERTSTORE  hDuplicateStore;             // Handle ad un archivio da creare come

                                          // duplicato di un archivio aperto.

 PCCERT_CONTEXT  pDesiredCert = NULL;     // Setta a NULL per la prima chiamata

                                          // a CertFindCertificateInStore.

 PCCERT_CONTEXT  pCertContext;

 HANDLE  hStoreFileHandle ;               // Handle al file di output.

 LPCSTR  pszFileName = "TestStor.sto";    // Nome del file di output.

 

 //-----------------------------------------------------------------------------

 // Aprire un nuovo archivio dei certificate in memoria.

 

 if (hMemoryStore = CertOpenStore(

            CERT_STORE_PROV_MEMORY,       // Archivio in memoria.

            0,                            // Tipo di codifica.

                                          // Non usato per gli archivi in memoria.

            NULL,                         // Usa il provider di default.

            0,                            // No flags.

            NULL))                        // Non necessario.

    {

     printf("Ho aperto un archivio in memoria. \n");

    }

 else

  {

   HandleError("Errore nell’apertura di un archivio in memoria.");

  }

 

 //-----------------------------------------------------------------------------

 // Apertura di un MY store nel sistema usando CertOpenStore.

 

 if (hSystemStore = CertOpenStore(

            CERT_STORE_PROV_SYSTEM,  // L'archivio nel sistema deve essere un

                                     // archivio virtuale.

            0,                       // Tipo di codifica non necessario con

                                     // questo provider.

            NULL,                    // Accetta l’HCRYPTPROV di default.

            CERT_SYSTEM_STORE_CURRENT_USER,

                                     // Setta la locazione dell'archivio del

                                     // sistema nel registro.

            L"MY"))                  // Potrebbero essere usati altri archivi di

                                     // sistema predefiniti inclusi Trust, CA,

                                     // o Root.

    {

     printf("Apertura del MY store nel sistema. \n");

    }

 else

    {

     HandleError("Non posso aprire il MY store nel sistema.");

    }

 

 //-----------------------------------------------------------------------------

 // Creazione e duplicazione del MY store.

 

 if (hDuplicateStore = CertDuplicateStore(hSystemStore))

    {

     printf("Il MY store è stato duplicato.\n");

    }

 else

    {

     printf("Duplicazione del MY store fallita.\n.");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere un certificato che contiene la stringa "Insert_cert_subject_name1"

 // nel suo soggetto.

 

 if (pDesiredCert=CertFindCertificateInStore(

            hSystemStore,

            MY_ENCODING_TYPE,             // Utilizzo di X509_ASN_ENCODING.

            0,                            // Non sono necessari i dwFlags.

            CERT_FIND_SUBJECT_STR,        // Trova un certificato con il

                                          // soggetto che matcha la stringa

                                          // contenuta nel parametro successivo.

            L"Insert_cert_subject_name1", // La stringa Unicode da trovare nel

                                          // soggetto del certificato.

            NULL))                        // NULL per la prima chiamata alla

                                          // funzione. In tutte le chiamate

                                          // successive, è l’ultimo puntatore

                                          // restituito dalla funzione.

    {

     printf("Il certificato desiderato è stato trovato. \n");

    }

 else

    {

     HandleError("Non posso trovare il certificato desiderato.");

    }

 

 //-----------------------------------------------------------------------------

 // pDesiredCert è un puntatore ad un certificato con un soggetto che include la

 // stringa "Insert_cert_subject_name1", la stringa passata come parametro #5

 // alla function.

 

 //-----------------------------------------------------------------------------

 // Creare un nuovo certificato dalla parte codificata di un certificato

 // disponibile.

 

 if (pCertContext = CertCreateCertificateContext(

            MY_ENCODING_TYPE  ,           // Il tipo di codifica.

            pDesiredCert->pbCertEncoded,  // Il dato codificato dal certificato

                                          // trovato.

            pDesiredCert->cbCertEncoded)) // Lunghezza del dato codificato.

    {

     printf("È stato creato un nuovo certificato.\n");

    }

 else

    {

     HandleError("Non può essere creato un nuovo certificato.");

    }

 

 //-----------------------------------------------------------------------------

 // Aggiungere il certificato dal MY store al nuovo archivio in memoria.

 

 if (CertAddCertificateContextToStore(

            hMemoryStore,                 // Handle all'archivio.

            pDesiredCert,                 // Puntatore al certificato.

            CERT_STORE_ADD_USE_EXISTING,

            NULL))

    {

     printf("Certificato aggiunto all'archivio in memoria. \n");

    }

 else

    {

     HandleError("Non posso aggiungere il certificato all'archivio in memoria.");

    }

 

 //-----------------------------------------------------------------------------

 // Trovare un certificato differente nel MY store ed aggiungergli un link al-

 // l'archivio in memoria.

 

 //-----------------------------------------------------------------------------

 // Trovare il contesto del certificato appena aggiunto all'archivio in memoria.

 

 if (pDesiredCert=CertFindCertificateInStore(

            hSystemStore,

            MY_ENCODING_TYPE,             // Utilizzo di X509_ASN_ENCODING.

            0,                            // Non sono mecessari i dwFlags.

            CERT_FIND_SUBJECT_STR,        // Trovare un certificate con un

                                          // soggetto che matcha la stringa

                                          // nel parametro successivo.

            L"Insert_cert_subject_name2", // La stringa Unicode da trovare nel

                                          // soggetto di un certificato.

            NULL))                        // NULL per la prima chiamata alla

                                          // funzione. In tutte le chiamate

                                          // successive, è l’ultimo puntatore

                                          // restituito dalla funzione.

    {

     printf("Il secondo certificato è stato trovato. \n");

    }

 else

    {

     HandleError("Non posso trovare il secondo certificato.");

    }

 

 //-----------------------------------------------------------------------------

 // Aggiungere un link al secondo certificato del MY store al nuovo archivio in

 // memoria.

 

 if (CertAddCertificateLinkToStore(

            hMemoryStore,                 // Handle all'archivio.

            pDesiredCert,                 // Puntatore ad un certificato.

            CERT_STORE_ADD_USE_EXISTING,

            NULL))

    {

     printf("Link al certificato aggiunto all'archivio in memoria. \n");

    }

 else

    {

     HandleError("Non posso aggiungere il link al certificato all'archivio in memoria.");

    }

 

 //-----------------------------------------------------------------------------

 // Trovare il primo certificato nell'archivio in memoria.

 

 if (pDesiredCert=CertFindCertificateInStore(

            hMemoryStore,

            MY_ENCODING_TYPE,             // Utilizzo di X509_ASN_ENCODING.

            0,                            // Non sono mecessari i dwFlags.

            CERT_FIND_SUBJECT_STR,        // Trovare un certificate con un

                                          // soggetto che matcha la stringa

                                          // nel parametro successivo.

            L"Insert_cert_subject_name1", // La stringa Unicode da trovare nel

                                          // soggetto di un certificato.

            NULL))                        // NULL per la prima chiamata alla

                                          // funzione. In tutte le chiamate

                                          // successive, è l’ultimo puntatore

                                          // restituito dalla funzione.

    {

     printf("Il certificato desiderato è stato trovato nell'archivio in memoria. \n");

    }

 else

    {

     printf("Il certificato non è nell'archivio in memoria.\n");

    }

 

 //-----------------------------------------------------------------------------

 //  Trovare il link al certificato nell'archivio in memoria.

 

 if (pDesiredCert=CertFindCertificateInStore(

            hMemoryStore,

            MY_ENCODING_TYPE,             // Utilizzo di X509_ASN_ENCODING.

            0,                            // Non sono mecessari i dwFlags.

            CERT_FIND_SUBJECT_STR,        // Trovare un certificate con un

                                          // soggetto che matcha la stringa

                                          // nel parametro successivo.

            L"Insert_cert_subject_name1", // La stringa Unicode da trovare nel

                                          // soggetto di un certificato.

            NULL))                        // NULL per la prima chiamata alla

                                          // funzione. In tutte le chiamate

                                          // successive, è l’ultimo puntatore

                                          // restituito dalla funzione.

    {

     printf("Il link al certificato è stato trovato nell'archivio in memoria. \n");

    }

 else

    {

     printf("Il link al certificato non è stato trovato nell'archivio in memoria.\n");

    }

 

 //-----------------------------------------------------------------------------

 // Creare un file per salvare il nuovo archivio ed il certificato al suo interno.

 

 if (hStoreFileHandle = CreateFile(

            pszFileName,                  // Path del file.

            GENERIC_WRITE,                // Modalità d’accesso.

            0,                            // Share mode.

            NULL,                         // Sicurezza.

            CREATE_ALWAYS,                // Come creare il file.

            FILE_ATTRIBUTE_NORMAL,        // Attributi del file.

            NULL))                        // Template.

    {

     printf("Creato un nuovo file sul disco. \n");

    }

 else

    {

     HandleError("Non posso creare un file sul disco.");

    }

 

 

 //-----------------------------------------------------------------------------

 // hStoreFileHandle è l’handle al file di output.

 // Salvare l'archivio in memoria ed il suo certificato nel file di output.

 

 if (CertSaveStore(

            hMemoryStore,            // Handle all'archivio.

            0,                       // Tipo di codifica (non necessario qui).

            CERT_STORE_SAVE_AS_STORE,

            CERT_STORE_SAVE_TO_FILE,

            hStoreFileHandle,        // Questo è l’handle di un file del disco

                                     // aperto.

            0))                      // dwFlags. Nessun flags è necessario qui.

    {

     printf("Archivio in memoria salvato sul disco. \n");

    }

 else

    {

     HandleError("Non posso salvare l'archivio in memoria sul disco.");

    }

 

 //-----------------------------------------------------------------------------

 // Chiusura degli archivi e del file. Riapertura dell'archivio nel file, e test dei

 // suoi contenuti.

 

 if (hMemoryStore)

     CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);

 if (hSystemStore)

     CertCloseStore(hSystemStore, CERT_CLOSE_STORE_CHECK_FLAG);

 if (hStoreFileHandle)

     CloseHandle(hStoreFileHandle);

 

 printf("Tutti gli archivi ed i file sono stati chiusi. \n");

 

 //-----------------------------------------------------------------------------

 //  Riapertura dell'archivio nel file.

 

 if (hMemoryStore = CertOpenStore(

            CERT_STORE_PROV_FILENAME,     // Tipo del provider dell'archivio.

            MY_ENCODING_TYPE,             // Se necessario, usare il tipo di

                                          // codifica usuale.

            NULL,                         // Usare l’HCRYPTPROV di default.

            0,                            // Accettare il default per tutti i

                                          // dwFlags.

            L"TestStor.sto" ))            // Il nome di un file esistente sotto

                                          // forma di stringa Unicode.

    {

     printf("l'archivio nel file è stato riaperto. \n");

    }

 else

    {

     printf("l'archivio nel file non può essere riaperto. \n");

    }

 

 //-----------------------------------------------------------------------------

 //  Trovare il link al certificato nell'archivio nel file riaperto.

 

 if (pDesiredCert=CertFindCertificateInStore(

            hMemoryStore,

            MY_ENCODING_TYPE,             // Utilizzo di X509_ASN_ENCODING.

            0,                            // Non sono mecessari i dwFlags.

            CERT_FIND_SUBJECT_STR,        // Trovare un certificate con un

                                          // soggetto che matcha la stringa

                                          // nel parametro successivo.

            L"Insert_cert_subject_name1", // La stringa Unicode da trovare nel

                                          // soggetto di un certificato.

            NULL))                        // NULL per la prima chiamata alla

                                          // funzione. In tutte le chiamate

                                          // successive, è l’ultimo puntatore

                                          // restituito dalla funzione.

    {

     printf("Il link al certificato è stato trovato nell'archivio nel file. \n");

    }

 else

    {

     printf("Il link al certificato non era nell'archivio nel file.\n");

    }

 

 //-----------------------------------------------------------------------------

 // Pulitura finale della memoria.

 

 if (pDesiredCert)

     CertFreeCertificateContext(pDesiredCert);

 if (hMemoryStore)

     CertCloseStore(hMemoryStore, CERT_CLOSE_STORE_CHECK_FLAG);

 if (hSystemStore)

     CertCloseStore(hSystemStore, CERT_CLOSE_STORE_CHECK_FLAG);

 If (hStoreFileHandle)

     CloseHandle(hStoreFileHandle);

 printf("Tutti gli archivi ed i file sono stati chiusi. \n");

 return;

} // Fine del main

 

//-----------------------------------------------------------------------------

//  Questo esempio usa la funzione HandleError, una semplice funzione di

//  gestione dell’errore, per stampare nel file di standard error un messaggio

//  di errore (stderr) file ed uscire dal programma.

//  Per la maggior parte delle applicazioni, si sostituisce questa funzione con

//  una che effettua dei report di errore più esaurienti.

 

void HandleError(char *s)

{

 fprintf(stderr,"Un errore è avvenuto eseguendo il programma. \n");

 fprintf(stderr,"%s\n",s);

 fprintf(stderr, "Error number %x.\n", GetLastError());

 fprintf(stderr, "Programma terminato. \n");

 exit(1);

} // Fine di HandleError

 

Verifica della fiducia del certificato

 

Deve esserci fiducia fra il destinatario di un messaggio ed il firmatario del messaggio. Un metodo per stabilire questa fiducia è tramite un certificato, un documento elettronico che verifica che le entità o le persone sono chi sostengono di essere. Un certificato viene emesso ad un’entità da una terza parte che è fidata per entrambe le altre parti. Così, ogni destinatario di un messaggio firmato decide se l’emittente del certificato del firmatario è fidato. La CryptoAPI ha implementato una metodologia per permettere agli sviluppatori di applicazioni di creare applicazioni che verificano automaticamente i certificati tramite una lista predefinita di certificati fidati o root. Questa lista di entità fidate (chiamate soggetti) viene chiamata certificate trust list (CTL).

 

Esempi

  

Utilizzo di CryptEncryptMessage e CryptDecryptMessage

 

CryptEncryptMessage è l’unica funzione necessaria per svolgere tutti i compiti di cifratura di un messaggio. È necessaria un’inizializzazione  delle stutture dati. La seguente illustrazione mostra le relazioni fra i parametri delle funzioni che puntano alle strutture o agli array ed i loro dati inizializzati. Questo programma decifra anche il messaggio usando CryptDecryptMessage.

 

Per cifrare i dati:

·        Ottenere un puntatore al contenuto da cifrare.

·        Determinare la dimensione del contenuto da cifrare.

·        Acquisire un handle al CSP.

·        Aprire un archivio dei certificati.

·        Ottenere il certificato del destinatario.

·        Creare l’array del certificato del destinatario.

·        Inizializzare la struttura CRYPT_ALGORITHM_IDENTIFIER.

·        Inizializzare la struttura CRYPT_ENCRYPT_MESSAGE_PARA.

·        Chiamare CryptEncryptMessage per cifrare il contenuto e creare un enveloped message digitalmente.

 

#include <stdio.h>

#include <windows.h>

#include <wincrypt.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

 

void HandleError(char *s);

 

PCCERT_CONTEXT GetRecipientCert(HCERTSTORE hCertStore);

 

void ByteToStr(DWORD cb, void* pv, LPSTR sz);

 

BOOL DecryptMessage(BYTE *pbEncryptedBlob, DWORD cbEncryptedBlob,

                    HCRYPTPROV hCryptProv, HCERTSTORE hStoreHandle);

 

void main()

{

 //-----------------------------------------------------------------------------

 // Dichiarazione ed inizializzazione delle variabili. Ciò implica la

 // definizione di un puntatore al messaggio da cifrare. Questo codice crea un

 // messaggio e definisce un puntatore ad esso.

 

 BYTE* pbContent = (BYTE*) "Security is our business.";    // Messaggio.

 DWORD cbContent = strlen((char *)pbContent)+1;            // Dimensione del

// messaggio.

 HCRYPTPROV hCryptProv;                                    // Handle al CSP.

 HCERTSTORE hStoreHandle;

 PCCERT_CONTEXT pRecipientCert;

 PCCERT_CONTEXT RecipientCertArray[1];

 DWORD EncryptAlgSize;

 CRYPT_ALGORITHM_IDENTIFIER EncryptAlgorithm;

 CRYPT_ENCRYPT_MESSAGE_PARA EncryptParams;

 DWORD EncryptParamsSize;

 BYTE*    pbEncryptedBlob;

 DWORD    cbEncryptedBlob;

 

 //-----------------------------------------------------------------------------

 // Inizio dell’elaborazione.

 

 printf("About to begin with the message %s.\n",pbContent);

 printf("The message length is %d bytes. \n", cbContent);

 

 //-----------------------------------------------------------------------------

 // Ottenere un handle al CSP.

 

 if (CryptAcquireContext(

            &hCryptProv,      // Indirizzo dell’handle da restituire.

            NULL,             // Utilizza il nome di logon corrente dell’utente.

            NULL,             // Utilizza il provider di default.

            PROV_RSA_FULL,    // È necessaria sia la cifratura che la firma.

            NULL))            // No flags.

    {

     printf("A CSP has been acquired. \n");

    }

 else

    {

     HandleError("Cryptographic context could not be acquired.");

    }

 

 //-----------------------------------------------------------------------------

 // Aprire un archivio dei certificati di sistema.

 

 if (hStoreHandle = CertOpenSystemStore(hCryptProv, "MY"))

    {

     printf("The MY store is open. \n");

    }

 else

    {

     HandleError( "Error getting store handle.");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere un puntatore al certificato del destinatario chiamando

 // GetRecipientCert.

 

 if (pRecipientCert = GetRecipientCert(hStoreHandle))

    {

     printf("A recipient's certificate has been acquired. \n");

    }

 else

    {

     printf("No certificate with a CERT_KEY_CONTEXT_PROP_ID \N");

     printf("property and an AT_KEYEXCHANGE private key available. \n");

     printf("While the message could be encrypted, in this case, \n");

     printf("it could not be decrypted in this program. \n");

     printf("For more information, see the documentation for \n");

     printf("CrypteEncryptMessage and CryptDecryptMessage.\n\n");

     HandleError( "No Certificate with AT_KEYEXCHANGE key in store.");

    }

 

 //-----------------------------------------------------------------------------

 // Creare un RecipientCertArray.

 

 RecipientCertArray[0] = pRecipientCert;

 

 //-----------------------------------------------------------------------------

 // Inizializzare la algorithm identifier structure.

 

 EncryptAlgSize = sizeof(EncryptAlgorithm);

 

 //-----------------------------------------------------------------------------

 // Inizializzare la struttura a zero.

 

 memset(&EncryptAlgorithm, 0, EncryptAlgSize);

 

 //-----------------------------------------------------------------------------

 // Settare il membro necessario.

 

 EncryptAlgorithm.pszObjId = szOID_RSA_RC4; 

 

 //-----------------------------------------------------------------------------

 // Inizializzare la struttura CRYPT_ENCRYPT_MESSAGE_PARA.

 

 EncryptParamsSize = sizeof(EncryptParams);

 memset(&EncryptParams, 0, EncryptParamsSize);

 EncryptParams.cbSize =  EncryptParamsSize;

 EncryptParams.dwMsgEncodingType = MY_ENCODING_TYPE;

 EncryptParams.hCryptProv = hCryptProv;

 EncryptParams.ContentEncryptionAlgorithm = EncryptAlgorithm;

 //-----------------------------------------------------------------------------

 // Chiamare CryptEncryptMessage.

 

 if (CryptEncryptMessage(&EncryptParams, 1, RecipientCertArray, pbContent,

                         cbContent, NULL, &cbEncryptedBlob))

    {

     printf("The encrypted message is %d bytes. \n",cbEncryptedBlob);

    }

 else

    {

     HandleError( "Getting EncrypBlob size failed.");

    }

 

 //-----------------------------------------------------------------------------

 // Allocare memoria per il BLOB restituito.

 

 if (pbEncryptedBlob = (BYTE*)malloc(cbEncryptedBlob))

    {

     printf("Memory has been allocated for the encrypted BLOB. \n");

    }

 else

    {

     HandleError("Memory allocation error while encrypting.");

    }

 

 //-----------------------------------------------------------------------------

 // Chiamare ancora CryptEncryptMessage per cifrare il contenuto.

 

 if (CryptEncryptMessage(&EncryptParams, 1, RecipientCertArray, pbContent,

                         cbContent, pbEncryptedBlob, &cbEncryptedBlob))

    {

     printf( "Encryption succeeded. \n");

    }

 else

    {

     HandleError("Encryption failed.");

    }

 

 //-----------------------------------------------------------------------------

 // Chiamare la funzione DecryptMessage, il cui codice segue il main, per

 // decifrare il messaggio.

 

 if (DecryptMessage(pbEncryptedBlob, cbEncryptedBlob, hCryptProv, hStoreHandle))

    {

     printf("Decryption succeeded. \n");

    }

 else

    {

     printf("Decryption failed. \n");

    }

 

 //-----------------------------------------------------------------------------

 // Ripulire la memoria.

 

 CertFreeCertificateContext(pRecipientCert);

 if (CertCloseStore(hStoreHandle, CERT_CLOSE_STORE_CHECK_FLAG))

    {

     printf("The MY store was closed without incident. \n");

    }

 else

    {

     printf("Store closed after encryption -- \n"

            "but not all certificates or CRLs were freed. \n");

    }

 if (hCryptProv)

    {

     CryptReleaseContext(hCryptProv,0);

     printf("The CSP has been released. \n");

    }

 else

    {

     printf("CSP was NULL. \n");

    }

} // Fine del main.

 

//------------------------------------------------------------------------------

// Definizione della funzione DecryptMessage.

 

BOOL DecryptMessage(BYTE *pbEncryptedBlob, DWORD cbEncryptedBlob,

                    HCRYPTPROV hCryptProv, HCERTSTORE hStoreHandle)

 

//------------------------------------------------------------------------------

// Funzione per decifrare un messaggio cifrato usando CryptDecryptMessage. I

// suoi parametri sono pbEncryptedBlob, un messaggio cifrato; cbEncryptedBlob,

// la lunghezza del messaggio; hCryptProv, un CSP; e hStoreHandle, l’handle di

// un archivio dei certificati aperto.

 

{

 //-----------------------------------------------------------------------------

 // Dichiarazione ed inizializzazione delle variabili locali.

 

 DWORD cbDecryptedMessage;

 char* EncryptedString = new char[(cbEncryptedBlob * 2) +1];

 HCERTSTORE CertStoreArray[] = {hStoreHandle};

 CRYPT_DECRYPT_MESSAGE_PARA  DecryptParams;

 DWORD  DecryptParamsSize = sizeof(DecryptParams);

 BYTE*  pbDecryptedMessage;

 LPSTR  DecryptedString;

 BOOL   fReturn = TRUE;

 

 //-----------------------------------------------------------------------------

 // Ottenere un puntatore al messaggio cifrato, pbEncryptedBlob, e la sua

 // lunghezza, cbEncryptedBlob. In questo programma, questi sono passati come

 // parametro assieme ad un CSP ed ad un handle ad un archivio aperto.

 

 //-----------------------------------------------------------------------------

 // Chiamare una funzione, ByteToStr, per convertire il byte BLOB in formato

 // ASCII hexadecimal.

 

 ByteToStr(cbEncryptedBlob, pbEncryptedBlob, EncryptedString);

 

 //-----------------------------------------------------------------------------

 // Stampare la stringa convertita.

 

 printf("The encrypted string is: \n%s\n",EncryptedString);

 

 //-----------------------------------------------------------------------------

 // L’handle al MY store è stato passato come parametro.

 

 //-----------------------------------------------------------------------------

 // Creare un "CertStoreArray". Questo passo nella dichiarazione e nella

 // inizializzazione delle variabili locali, perché l’handle all'archivio è stato

 // passato alla funzione come parametro.

 

 //-----------------------------------------------------------------------------

 // Inizializzare la struttura CRYPT_DECRYPT_MESSAGE_PARA.

 

 memset(&DecryptParams, 0, DecryptParamsSize);

 DecryptParams.cbSize = DecryptParamsSize;

 DecryptParams.dwMsgAndCertEncodingType = MY_ENCODING_TYPE;

 DecryptParams.cCertStore = 1;

 DecryptParams.rghCertStore = CertStoreArray;

 

 //-----------------------------------------------------------------------------

 // Decifrare i dati del messaggio. Chiamare CryptDecryptMessage per ottenere la

 // dimensione dei dati restituiti.

 

 if (CryptDecryptMessage(&DecryptParams, pbEncryptedBlob, cbEncryptedBlob,

                         NULL, &cbDecryptedMessage, NULL))

    {

     printf("The size for the decrypted message is: %d.\n",cbDecryptedMessage);

    }

 else

    {

     HandleError( "Error getting decrypted message size");

    }

 

 //-----------------------------------------------------------------------------

 // Allocare memoria per i dati decifrati restituiti.

 

 if (pbDecryptedMessage = (BYTE*)malloc(cbDecryptedMessage))

    {

     printf("Memory has been allocated for the decrypted message. \n");

    }

 else

    {

     HandleError("Memory allocation error while decrypting");

    }

 

 //-----------------------------------------------------------------------------

 // Chiamare CryptDecryptMessage per decifrare i dati.

 

 if (CryptDecryptMessage(&DecryptParams, pbEncryptedBlob, cbEncryptedBlob,

                         pbDecryptedMessage, &cbDecryptedMessage, NULL))

    {

     DecryptedString = (LPSTR) pbDecryptedMessage;

     printf("Message Decrypted Successfully. \n");

     printf("The decrypted string is: %s\n",DecryptedString);

    }

 else

    {

     printf("Error decrypting the message \n");

     printf("Error code %x \n",GetLastError());

     fReturn = FALSE;

    }

 

 //-----------------------------------------------------------------------------

 // Ripulire la memoria.

 

 free(pbEncryptedBlob);

 free(pbDecryptedMessage);

 return fReturn;

}  // End of DecryptMessage.

 

 

 

//------------------------------------------------------------------------------

// Definizione della funzione ByteToStr.

 

void ByteToStr(DWORD cb, void* pv, LPSTR sz)

//------------------------------------------------------------------------------

// I parametri passati sono:

//    pv è l’array di BYTE da convertire.

//    cb è il numero di BYTE nell’array.

//    sz è un puntatore alla stringa da restituire.

{

 //-----------------------------------------------------------------------------

 //  Dichiarazione ed inizializzazione delle variabili locali.

 

 BYTE* pb = (BYTE*) pv;       // Puntatore locale ad un BYTE nell’array di BYTE.

 DWORD i;                     // Contatore locale del ciclo.

 int b;                       // Variabile locale.

 

 //-----------------------------------------------------------------------------

 //  Inizio ciclo di elaborazione.

 

 for (i = 0; i<cb; i++)

     {

      b = (*pb & 0xF0) >> 4;

      *sz++ = (b <= 9) ? b + '0' : (b - 10) + 'A';

      b = *pb & 0x0F;

      *sz++ = (b <= 9) ? b + '0' : (b - 10) + 'A';

      pb++;

     }

 *sz++ = 0;

} // Fine di ByteToStr.

 

//------------------------------------------------------------------------------

//  Questo esempio usa la funzione HandleError, una semplice funzione di

//  gestione dell’errore, per stampare nel file di standard error un messaggio

//  di errore (stderr) file ed uscire dal programma.

//  Per la maggior parte delle applicazioni, si sostituisce questa funzione con

//  una che effettua dei report di errore più esaurienti.

 

void HandleError(char *s)

{

 fprintf(stderr,"Un errore è avvenuto eseguendo il programma. \n");

 fprintf(stderr,"%s\n",s);

 fprintf(stderr, "Error number %x.\n", GetLastError());

 fprintf(stderr, "Programma terminato. \n");

 exit(1);

} // Fine di HandleError.

 

//------------------------------------------------------------------------------

// GetRecipientCert enumera i certificati di un archivio e trova il primo

// certificato che ha una chiave AT_EXCHANGE. Se viene trovato un certificato,

// viene restituito un puntatore a quel certificato. 

 

PCCERT_CONTEXT GetRecipientCert(HCERTSTORE hCertStore)

//------------------------------------------------------------------------------

// Il parametro passato è hCertStore, l’handle all'archivio in cui ricercare.

{

 //-----------------------------------------------------------------------------

 // Dichiarazione ed inizializzazione delle variabili locali.

 

 PCCERT_CONTEXT pCertContext = NULL;

 BOOL fMore = TRUE;

 DWORD dwSize = NULL;

 CRYPT_KEY_PROV_INFO* pKeyInfo = NULL;

 DWORD PropId = CERT_KEY_PROV_INFO_PROP_ID;

 

 

 

 //-----------------------------------------------------------------------------

 // Cercare il certificato nell'archivio finché non si raggiunge la fine o il

 // certificato con chiave AT_KEYEXCHANGE viene trovato.

 

 while (fMore && (pCertContext= CertFindCertificateInStore(

                  hCertStore, // Handle dell'archivio in cui si ricerca.

                  0,          // Tipo di codifica. Non usato in questa ricerca.

                  0,          // dwFindFlags. Criterio di ricerca speciale. Non

                              // usato in questa ricerca.

                  CERT_FIND_PROPERTY,

                              // Tipo di ricerca. Determina il tipo di ricerca

                              // da svolgere. In questo caso, si ricercano

                              // certificati che hanno proprietà estese

                              // specifiche.

                  &PropId,    // pvFindPara. Fornisce il valore specifico

                              // ricercato, qui è l’identiticatore di una

                              // proprietà estesa.

                  pCertContext)))

                              // pCertContext è NULL nella prima chiamata alla 

                              // funzione. Se la funzione viene chiamata in un

                              // loop, dopo la prima chiamata pCertContext

                              // dovrebbe essere il puntatore restituito dalla

                              // chiamata precedente.

       {

        //----------------------------------------------------------------------

        // Per semplicità, questo codice ricerca solo la prima occorrenza di una

        // chiave AT_KEYEXCHANGE. In molte situazioni, una ricerca dovrebbe a

        // essere effettuata anche per nome di soggetto specifico.

 

        //----------------------------------------------------------------------

        // Chiamare CertGetCertificateContextProperty una volta ottenuta la

        // dimensione della struttura restituita.

 

        if (!(CertGetCertificateContextProperty(

                   pCertContext,

                   CERT_KEY_PROV_INFO_PROP_ID,

                   NULL, &dwSize)))

           {

            HandleError("Error getting key property.");

           }

 

        //----------------------------------------------------------------------

        // Allocare memoria per la struttura restituita.

 

        if (pKeyInfo)

           free(pKeyInfo);

        if (!(pKeyInfo = (CRYPT_KEY_PROV_INFO*)malloc(dwSize)))

           {

            HandleError("Error allocating memory for pKeyInfo.");

           }

 

        //----------------------------------------------------------------------

        // Ottenere la key information structure.

 

        if (!(CertGetCertificateContextProperty(

                   pCertContext,

                   CERT_KEY_PROV_INFO_PROP_ID,

                   pKeyInfo,

                   &dwSize)))

           {

            HandleError("The second call to the function failed.");

           }

 

        //----------------------------------------------------------------------

        // Verificare se il membro dwKeySpec è una chiave di scambio.

 

        if (pKeyInfo->dwKeySpec == AT_KEYEXCHANGE)

           {

            fMore = FALSE;

           }

       } // Fine del ciclo while.

 

 if (pKeyInfo)

    free(pKeyInfo);

 return (pCertContext);

} // Fine di GetRecipientCert.

 

Cifratura di un file

 

Il programma richiede interattivamente il nome del file che contiene il testo in chiaro da cifrare ed il nome del file dove devono essere scritti i dati cifrati.

Il programma chiede all’utente di fornire il nome di un file di input ed uno di output. Chiede anche se deve essere usata una password per creare una chiave di sessione cifrata. Se viene usata una password nella cifratura dei dati, la stessa password deve essere usata nel programma che decifra il file.

Per cambiare le restrizioni sul controllo delle esportazioni, il CSP di default e la lunghezza della chiave di default possono essere cambiati in base alla versione del sistema operativo. L’importante è che sia la cifratura che la decifratura usino lo stesso CSP e che la lunghezza della chiave venga settata esplicitamente per assicurare l’interoperabilità su piattaforme differenti.

 

#include <stdio.h>

#include <windows.h>

#include <wincrypt.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

#define KEYLENGTH  0x00800000

 

void HandleError(char *s);

 

#define ENCRYPT_ALGORITHM CALG_RC4

#define ENCRYPT_BLOCK_SIZE 8

BOOL EncryptFile(

PCHAR szSource,

PCHAR szDestination,

PCHAR szPassword);

 

//------------------------------------------------------------------------------

// Inizio del main.

 

void main(void)

{

 PCHAR szSource;

 PCHAR szDestination;

 PCHAR szPassword;

 char  response;

 

 if (!(szSource =(char *)malloc(100)))

    HandleError("Allocazione della memoria fallita.");

 if (!(szDestination =(char *)malloc(100)))

    HandleError("Allocazione della memoria fallita.");

 if (!(szPassword =(char *)malloc(100)))

    HandleError("Allocazione della memoria fallita.");

 printf("Cifratura di un file. \n\n");

 printf("Inserire il nome del file da cifrare: ");

 scanf("%s",szSource);

 printf("Inserire il nome del file di output: ");

 scanf("%s",szDestination);

 printf("Usare una password per cifrare questo file? ( s/n ) ");

 getchar();

 scanf("%c",&response);

 if (response == 's')

    {

     printf("Inserire la password:");

     scanf("%s",szPassword);

    }

 else

    {

     printf("La chiave sarà generata senza usare una password. \n");

     free(szPassword);

     szPassword = NULL;

    }

 

 //-----------------------------------------------------------------------------

 // Chiama EnryptFile per effettuare la cifratura.

 

 if (EncryptFile(szSource, szDestination, szPassword))

    {

     printf("La cifratura del file %s ha avuto successo. \n", szSource);

     printf("I dati cifrati sono nel file %s.\n",szDestination);

    }

 else

    {

     HandleError("Errore nella cifratura del file!");

    }

} // Fine del main.

 

//------------------------------------------------------------------------------

// Codice per la funzione EncryptFile chiamata dal main.

 

static BOOL EncryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword)

//------------------------------------------------------------------------------

// I parametri passati sono:

// szSource, il nome dell’input, un file in chiaro.

// szDestination, il nome dell’output, un file cifrato che deve essere creato.

// szPassoword, o NULL se non viene usata una password o la stringa

// rappresentante la password.

{

 //-----------------------------------------------------------------------------

 //Dichiarazione ed inizializzazione delle variabili locali.

 

 FILE *hSource;

 FILE *hDestination;

 HCRYPTPROV hCryptProv;

 HCRYPTKEY hKey;

 HCRYPTKEY hXchgKey;

 HCRYPTHASH hHash;

 PBYTE pbKeyBlob;

 DWORD dwKeyBlobLen;

 PBYTE pbBuffer;

 DWORD dwBlockLen;

 DWORD dwBufferLen;

 DWORD dwCount;

 

 

 //-----------------------------------------------------------------------------

 // Apertura del file sorgente.

 if (hSource = fopen(szSource,"rb"))

    {

     printf("Il file sorgente in chiaro, %s, è stato aperto. \n", szSource);

    }

 else

    {

     HandleError("Errore nell’apertura del file sorgente in chiaro!");

    }

 

 //-----------------------------------------------------------------------------

 // Apertura del file di destinazione.

 

 

 if (hDestination = fopen(szDestination,"wb"))

    {

     printf("Il file di destinazione %s è stato aperto. \n", szDestination);

    }

 else

    {

     HandleError("Errore nell’apertura del file di destinazione del testo cifrato!");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere un handle al CSP di default.

 

 if (CryptAcquireContext(

            &hCryptProv,

            NULL,

            MS_ENHANCED_PROV,

            PROV_RSA_FULL,

            0))

    {

     printf("È stato acquisito un CSP. \n");

    }

 else

    {

     HandleError("Errore durante CryptAcquireContext!");

    }

 

 //-----------------------------------------------------------------------------

 // Creazione della chiave di sessione

 

 if (!szPassword)

    {

     //-------------------------------------------------------------------------

     // Nessuna password immessa.

     // Il file viene cifrato con una chiave di sessione casuale la quale viene

     // scritta in un file.

     

     //-------------------------------------------------------------------------

     // Creazione di una chiave di sessione casuale.

 

     if (CryptGenKey(

                hCryptProv,

                ENCRYPT_ALGORITHM,

                KEYLENGTH | CRYPT_EXPORTABLE,

                &hKey))

        {

         printf("Una chiave di sessione è stata creata. \n");

        }

     else

        {

         HandleError("Errore durante CryptGenKey. \n");

        }

 

     //-------------------------------------------------------------------------

     // Ottenere un handle alla exchange public key del decifrante.

 

     if (CryptGetUserKey(

                hCryptProv,

                AT_KEYEXCHANGE,

                &hXchgKey))

        {

         printf("The user public key has been retrieved. \n");

        }

     else

        {

         HandleError("La chiave pubblica dell’utente non è disponibile e/o non esiste.");

        }

 

     //-------------------------------------------------------------------------

     // Determinazione della dimensione del BLOB della chiave, ed allocazione della

     // memoria.

     

     if (CryptExportKey(

                hKey,

                hXchgKey,

                SIMPLEBLOB,

                0,

                NULL,

                &dwKeyBlobLen))

        {

         printf("Il BLOB della chiave è lunga %d byte. \n",dwKeyBlobLen);

        }

     else

        { 

         HandleError("Errore nel calcolare la lunghezza del BLOB della chiave! \n");

        }

     if (pbKeyBlob =(BYTE *)malloc(dwKeyBlobLen))

        {

         printf("È stata allocata memoria per il BLOB della chiave. \n");

        }

     else

        {

         HandleError("Memoria esaurita. \n");

        }

 

     //-------------------------------------------------------------------------

     // Cifratura ed esportazione della chiave di sessione in un BLOB della chiave

     // semplice.

     

     if (CryptExportKey(

                hKey,

                hXchgKey,

                SIMPLEBLOB,

                0,

                pbKeyBlob,

                &dwKeyBlobLen))

        {

         printf("La chiave è stata esportata. \n");

        }

     else

        {

         HandleError("Errore durante CryptExportKey!\n");

        }

 

     //-------------------------------------------------------------------------

     // Rilascio dell’handle alla exchange key.

 

     CryptDestroyKey(hXchgKey);

     hXchgKey = 0;

 

     //-------------------------------------------------------------------------

     // Scrittura della dimensione del BLOB della chiave nel file di destinazione.

     

     fwrite(&dwKeyBlobLen, sizeof(DWORD), 1, hDestination);

     if (ferror(hDestination))

        {

         HandleError("Error writing header.");

        }

     else

        {

         printf("A file header has been written. \n");

        }

 

     //-------------------------------------------------------------------------

     // Scrittura del BLOB della chiave nel file di destinazione.

    

     fwrite(pbKeyBlob, 1, dwKeyBlobLen, hDestination);

     if (ferror(hDestination))

        {

         HandleError("Errore nella scrittura dell’intestazione");

        }

     else

        {

         printf("Il BLOB della chiave è stata scritta nel file. \n");

        }

    }

 else

    {

     //-------------------------------------------------------------------------

     // Il file sarà cifrato con la chiave di sessione ricavata dalla password.

     // La chiave di sessione verrà ricreata quando il file sarà decifrato solo

     // se la password usata per creare la chiave è disponibile.

 

     //-------------------------------------------------------------------------

     // Creazione di un oggetto hash.

 

     if (CryptCreateHash(

                hCryptProv,

                CALG_MD5,

                0,

                0,

                &hHash))

        {

         printf("È stato creato un oggetto hash. \n");

        }

     else

        {

         HandleError("Errore durante CryptCreateHash!\n");

        } 

 

     //-------------------------------------------------------------------------

     // Hashing della password.

 

     if (CryptHashData(

                hHash,

                (BYTE *)szPassword,

                strlen(szPassword),

                0))

        {

         printf("La password è stata aggiunta all’hash. \n");

        }

     else

        {

         HandleError("Errore durante CryptHashData. \n");

        }

 

     //-------------------------------------------------------------------------

     // Derivazione di una chiave di sessione da un oggetto hash.

 

     if (CryptDeriveKey(

                hCryptProv,

                ENCRYPT_ALGORITHM,

                hHash,

                KEYLENGTH,

                &hKey))

        {

         printf("È stata derivata una chiave di cifratura dall’hash della password. \n");

        }

     else

        {

         HandleError("Errore durante CryptDeriveKey!\n");

        }

 

     //-------------------------------------------------------------------------

     // Distruzione dell’oggetto hash.

 

     CryptoDestroyHash(hHash);

     hHash=0;

    }

 

 //-----------------------------------------------------------------------------

 // La chiave di sessione è pronta. Se non è stata derivata da una password,

 // la chiave di sessione cifrata con la chiave privata del cifrante sarà

 // scritta nel file di destinazione.

 

 //-----------------------------------------------------------------------------

 // Determinazione del numero di byte da cifrare ogni volta. Questo deve

 // essere un multiplo di ENCRYPT_BLOCK_SIZE. ENCRYPT_BLOCK_SIZE è stato

 // con una istruzione di #define.

 

 DwBlockLen = 1000 – 1000 % ENCRYPT_BLOCK_SIZE;

 

 //-----------------------------------------------------------------------------

 // Determinazione della dimensione del blocco. Se viene usato un cifrario a

 // blocchi, deve essere allocato spazio per un blocco extra.

 

 if (ENCRYPT_BLOCK_SIZE > 1)

    dwBufferLen = dwBlockLen + ENCRYPT_BLOCK_SIZE;

 else

    dwBufferLen = dwBlockLen;

 

 //-----------------------------------------------------------------------------

 // Allocazione della memoria.

 

 if (pbBuffer = (BYTE *)malloc(dwBufferLen))

    {

     printf("È stata allocata memoria per il buffer. \n");

    }

 else

    {

     HandleError("Memoria esaurita. \n");

    }

 

 //-----------------------------------------------------------------------------

 // In un ciclo do-while, cifratura del file sorgente e scrittura nel file di

 // destinazione.

 

 do

   {

 

    //--------------------------------------------------------------------------

    // Lettura di dwBlockLen byte dal file sorgente.

 

    dwCount = fread(pbBuffer, 1, dwBlockLen, hSource);

    if (ferror(hSource))

       {

        HandleError("Errore nella lettura del testo in chiaro!\n");

       }

 

    //--------------------------------------------------------------------------

    // Cifratura dei dati.

     

    if (!CryptEncrypt(

               hKey,

               0,

               feof(hSource),

               0,

               pbBuffer,

               &dwCount,

               dwBufferLen))

       {

        HandleError("Errore durante CryptEncrypt. \n");

       }

 

    //--------------------------------------------------------------------------

    // Scrittura dei dati nel file di destinazione.

 

    fwrite(pbBuffer, 1, dwCount, hDestination);

    if (ferror(hDestination))

       {

        HandleError("Errore nella scrittura del testo cifrato.");

       }

   }

 while (!feof(hSource));

 

 //-----------------------------------------------------------------------------

 // Il ciclo termina quando l’ultimo blocco del file sorgente è stato letto,

 // cifrato e scritto nel file di destinazione.

 

 //-----------------------------------------------------------------------------

 // Chiusura del file.

 

 if (hSource)

    fclose(hSource);

 if (hDestination)

    fclose(hDestination);

 

 //-----------------------------------------------------------------------------

 // Rilascio della memoria.

 

 if (pbBuffer)

    free(pbBuffer);

 

 //-----------------------------------------------------------------------------

 // Distruzione della chiave di sessione.

 

 if (hKey)

    CryptDestroyKey(hKey);

 

 //-----------------------------------------------------------------------------

 // Rilascio dell’handle alla exchange key.

 

 

if (hXchgKey)

    CryptDestroyKey(hXchgKey);

 

 //-----------------------------------------------------------------------------

 // Distruzione dell’oggetto hash.

 

 if (hHash)

    CryptDestroyHash(hHash);

 

 //-----------------------------------------------------------------------------

 // Rilascio dell’handle al CSP.

 

 if (hCryptProv)

    CryptReleaseContext(hCryptProv, 0);

 return(true);

} // Fine di Encryptfile.

 

//-----------------------------------------------------------------------------

//  Questo esempio usa la funzione HandleError, una semplice funzione di

//  gestione dell’errore, per stampare nel file di standard error un messaggio

//  di errore (stderr) file ed uscire dal programma.

//  Per la maggior parte delle applicazioni, si sostituisce questa funzione con

//  una che effettua dei report di errore più esaurienti.

 

void HandleError(char *s)

{

 fprintf(stderr,"Un errore è avvenuto eseguendo il programma. \n");

 fprintf(stderr,"%s\n",s);

 fprintf(stderr, "Error number %x.\n", GetLastError());

 fprintf(stderr, "Programma terminato. \n");

 exit(1);

} // Fine di HandleError.

 

Decifratura di un file

 

Il programma richiede all’utente il nome del file cifrato ed il nome del file in cui i dati decifrati devono essere scritti. Il file con i dati cifrati deve esistere. Il programma crea o sovrascrive un file di output.

Il programma richiede anche una stringa usata come password. Se una password è stata usata per creare la chiave di sessione di cifratura, la stessa password deve essere immessa per creare la chiave di sessione di decifratura.

Per cambiare le restrizioni sul controllo delle esportazioni, il CSP di default e la lunghezza della chiave di default possono essere cambiati in base alla versione del sistema operativo. È importante che sia la cifratura che la decifratura usino lo stesso CSP e che la lunghezza della chiave venga esplicitamente settata per assicurare l’interoperabilità su differenti piattaforme.

 

#include <stdio.h>

#include <windows.h>

#include <wincrypt.h>

#define MY_ENCODING_TYPE  (PKCS_7_ASN_ENCODING | X509_ASN_ENCODING)

#define KEYLENGTH  0x00800000

 

void HandleError(char *s);

 

#define ENCRYPT_ALGORITHM CALG_RC4

#define ENCRYPT_BLOCK_SIZE 8

BOOL DecryptFile(

PCHAR szSource,

PCHAR szDestination,

PCHAR szPassword);

 

void main(void)

{

 PCHAR szSource;

 PCHAR szDestination;

 PCHAR szPassword;

 char  response;

 

 if (!(szSource=(char *)malloc(100)))

    HandleError("Allocazione della memoria fallita.");

 if (!(szDestination=(char *)malloc(100)))

    HandleError("Allocazione della memoria fallita.");

 if (!(szPassword=(char *)malloc(100)))

    HandleError("Allocazione della memoria fallita.");

 printf("Decifratura di un file. \n\n");

 printf("Inserire il nome del file da decifrare: ");

 scanf("%s",szSource);

 printf("Inserire il nome del file di output: ");

 scanf("%s",szDestination);

 printf("È stata usata una password per cifrare questo file? ( s/n ) ");

 getchar();

 scanf("%c",&response);

 if (response == 's')

    {

     printf("Inserire la password:");

     scanf("%s",szPassword);

    }

 else

    {

     printf("La chiave sarà generata senza usare una password. \n");

     free(szPassword);

     szPassword = NULL;

    }

 

 if (!DecryptFile(szSource, szDestination, szPassword))

    {

     printf("\nErrore nella decifratura del file. \n");

    }

 else

    {

     printf("\nLa decifratura del file %s ha avuto successo. \n", szSource);

     printf("Il file decifrato è %s .\n",szDestination);

    }

} // Fine del main.

 

//------------------------------------------------------------------------------

// Definizione della funzione Decryptfile.

 

 

static BOOL DecryptFile(PCHAR szSource, PCHAR szDestination, PCHAR szPassword)

{

 //-----------------------------------------------------------------------------

 //Dichiarazione ed inizializzazione delle variabili locali.

 

 FILE *hSource;

 FILE *hDestination;

 HCRYPTPROV hCryptProv;

 HCRYPTKEY hKey;

 HCRYPTHASH hHash;

 PBYTE pbKeyBlob = NULL;

 DWORD dwKeyBlobLen;

 PBYTE pbBuffer;

 DWORD dwBlockLen;

 DWORD dwBufferLen;

 DWORD dwCount;

 BOOL status = FALSE;

 

 //-----------------------------------------------------------------------------

 // Apertura del file sorgente.

 

 if (!(hSource = fopen(szSource,"rb")))

    {

     HandleError("Errore nell’apertura del file contenente il testo cifrato!");

    }

 

 //-----------------------------------------------------------------------------

 // Apertura del file di destinazione.

 

 if (!(hDestination = fopen(szDestination,"wb")))

    {

     HandleError("Errore nell’apertura del file contenente il testo in chiaro!");

    }

 

 //-----------------------------------------------------------------------------

 // Ottenere un handle al CSP di default.

 

 if (!CryptAcquireContext(

            &hCryptProv,

            NULL,

            MS_ENHANCED_PROV,

            PROV_RSA_FULL,

            0))

    {

     HandleError("Errore durante CryptAcquireContext!");

    }

 

 //-----------------------------------------------------------------------------

 // Verifica dell’esistenza di una password.

 

 if (!szPassword)

    {

     //-------------------------------------------------------------------------

     // Decifratura del file con la chiave di sessione salvata.

 

     //-------------------------------------------------------------------------

     // Legge la lunghezza di Key BLOB dal file sorgente, e alloca memoria.

 

     fread(&dwKeyBlobLen, sizeof(DWORD), 1, hSource);

     if (ferror(hSource) || feof(hSource))

        {

         HandleError("Errore leggendo l’intestazione del file!");

        }

     if (!(pbKeyBlob = (BYTE *)malloc(dwKeyBlobLen)))

        {

         HandleError("Errore nell’allocazione della memoria.");

        }

 

     //-------------------------------------------------------------------------

     // Lettura del BLOB della chiave dal file sorgente.

 

     fread(pbKeyBlob, 1, dwKeyBlobLen, hSource);

     if (ferror(hSource) || feof(hSource))

        {

         HandleError("Errore nella lettura dell’intestazione del file!\n");

        }

 

     //-------------------------------------------------------------------------

     // Importazione del BLOB della chiave nel CSP.

 

     if (!CryptImportKey(

                hCryptProv,

                pbKeyBlob,

                dwKeyBlobLen,

                0,

                0,

                &hKey))

        {

         HandleError("Errore durante CryptImportKey!");

        }

    }

 else

    {

     //-------------------------------------------------------------------------

     // Decifratura del file con la chiave di sessione derivata dalla password.

 

     //-------------------------------------------------------------------------

     // Creazione di un oggetto hash.

    

     if (!CryptCreateHash(

                hCryptProv,

                CALG_MD5,

                0,

                0,

                &hHash))

        {

         HandleError("Errore durante CryptCreateHash!");

        }

 

     //--------------------------------------------------------------------

     // Hash in the password data.

 

     if (!CryptHashData(

                hHash,

                (BYTE *)szPassword,

                strlen(szPassword),

                0))

        {

         HandleError("Errore durante CryptHashData!");

        }

 

     //-------------------------------------------------------------------------

     // Derivazione di una chiave di sessione dall’oggetto hash.

 

 

     if (!CryptDeriveKey(

                hCryptProv,

                ENCRYPT_ALGORITHM,

                hHash,

                KEYLENGTH,

                &hKey))

        {

         HandleError("Errore durante CryptDeriveKey!");

        }

 

     //-------------------------------------------------------------------------

     // Distruzione dell’oggetto hash.

 

     CryptDestroyHash(hHash);

     hHash = 0;

    }

 //-----------------------------------------------------------------------------

 // La chiave di decifratura è ora disponibile, o è stata importata da un

 // BLOB della chiave letto dal file sorgente o è stata creata usando la password.

 // Questo punto del programma non viene raggiunto se la chiave di decifratura

 // non è disponibile.

 

 //-----------------------------------------------------------------------------

 // Determinazione del numero di byte da decifrare ogni volta. Questo deve

 // essere un multiplo di ENCRYPT_BLOCK_SIZE.

 

 dwBlockLen = 1000 - 1000 % ENCRYPT_BLOCK_SIZE;

 dwBufferLen = dwBlockLen;

 

 //-----------------------------------------------------------------------------

 // Allocazione della memoria.

 

 if (!(pbBuffer = (BYTE *)malloc(dwBufferLen)))

    {

     HandleError("Memoria esaurita!\n");

    }

 

 //-----------------------------------------------------------------------------

 //  Decifratura del file sorgente e scrittura nel file di destinazione.

 

 do

   {

    //--------------------------------------------------------------------------

    // Lettura di dwBlockLen byte dal file sorgente.

 

    dwCount = fread(pbBuffer, 1, dwBlockLen, hSource);

    if (ferror(hSource))

       {

        HandleError("Errore nella lettura del testo cifrato!");

       }

 

    //--------------------------------------------------------------------------

    // Decifratura dei dati.

 

    if (!CryptDecrypt(

               hKey,

               0,

               feof(hSource),

               0,

               pbBuffer,

               &dwCount))

       {

        HandleError("Errore durante CryptDecrypt!");

       }

 

    //--------------------------------------------------------------------------

    // Scrittura dei dati nel file di destinazione.

 

    fwrite(pbBuffer, 1, dwCount, hDestination);

    if (ferror(hDestination))

       {

        HandleError("Errore nella scrittura del testo in chiaro!");

       }

   }

 while(!feof(hSource));

 status = TRUE;

 

 //-----------------------------------------------------------------------------

 // Chiusura dei file.

 

 if (hSource)

    fclose(hSource);

 if (hDestination)

    fclose(hDestination);

 

 //-----------------------------------------------------------------------------

 // Rilascio della memoria.

 

 if (pbKeyBlob)

    free(pbKeyBlob);

 if (pbBuffer)

    free(pbBuffer);

 

 //-----------------------------------------------------------------------------

 // Distruzione della chiave di sessione.

 

 if (hKey)

    CryptDestroyKey(hKey);

 

 //-----------------------------------------------------------------------------

 // Distuzione dell’oggetto hash.

 

 if (hHash)

    CryptDestroyHash(hHash);

 

 //--------------------------------------------------------------------

 // Rilascio dell’handle al CSP.

 

 if (hCryptProv)

    CryptReleaseContext(hCryptProv, 0);

 return status;

} // Fine di Decryptfile.

 

//-----------------------------------------------------------------------------

//  Questo esempio usa la funzione HandleError, una semplice funzione di

//  gestione dell’errore, per stampare nel file di standard error un messaggio

//  di errore (stderr) file ed uscire dal programma.

//  Per la maggior parte delle applicazioni, si sostituisce questa funzione con

//  una che effettua dei report di errore più esaurienti.

 

void HandleError(char *s)

{

 fprintf(stderr,"Un errore è avvenuto eseguendo il programma. \n");

 fprintf(stderr,"%s\n",s);

 fprintf(stderr, "Error number %x.\n", GetLastError());

 fprintf(stderr, "Programma terminato. \n");

 exit(1);

} // Fine di HandleError.