Elementi di Crittografia -
Package JCE
Emilio Petrangeli, Mauro Pagano
Prof.ssa Rota, Merola
Sommario
●
Introduzione Crittografia
●
Cenni di Crittografia
●
Introduzione JCE
●
JCE
●
Principali classi JCE
●
Hash coding
●
JCE in pratica
●
Esempio con Applicazione
Introduzione
Con l`avvento di internet e delle reti di comunicazione in
genere, sono sorte una serie di problematiche legate alla
sicurezza: riservatezza e certificazione.
Riservatezza: l’obiettivo, durante la fase di trasferimento dati
da un punto ad un altro, e` rendere possibile la comprensione
unicamente a mittente e destinatario, verificando eventuali
intercettazioni.
La criptazione serve proprio a rendere i dati incomprensibili
all’esterno!
Certificazione: risolve il problema dell’identificazione CERTA
del mittente.
●
Introduzione
Questa tesina illustra l’implementazione in java
di tecniche per la risoluzione di tali
problematiche, senza, tuttavia, prendere in
considerazione tutti gli aspetti legati alla
sicurezza di sistema- argomenti relativi
all’amministrazione di rete- piuttosto
concentrandosi sulla programmazione.
Cenni Crittografia
Crittografia: processo che trasforma, per mezzo di sofisticati
algoritmi, una sequenza di byte con senso logico (messaggio)
in un’altra del tutto incomprensibile.
La trasformazione avviene attraverso una chiave: solo chi la
possiede potrà criptare e decriptare il messaggio.
Cenni Crittografia
Gli algoritmi più semplici sono quelli a chiave
simmetrica: con una sola chiave permettono
di criptare e decriptare il messagio
Cenni Crittografia
Gli algoritmi a chiave asimmetrica, più sicuri, ma computazionalmente più pesanti, generano
codici più ingombranti. Per questo motivo spesso, quando si necessita di leggerezza e
velocità, si preferisce utilizzare una tecnica mista: il mittente critta il messaggio con una chiave
simmetrica, procede a crittare la stessa chiave simmetrica con la chiave asimmetrica pubblica
del destinatario, ed invia poi messaggio e chiave entrambi crittati. Il ricevente, con la sua
chiave privata, decritterà prima la chiave simmetrica e poi con tale chiave il messaggio stesso
Attualmente questo meccanismo risulta essere piuttosto sicuro. Benché nessuno ne abbia
dimostrato l’impossibilità, ad oggi non risultano esserci state violazioni degne di nota.
Introduzione JCE
Dopo una rapida panoramica sulla
crittografia, vediamo come sia possibile
implementare tali tecniche in Java.
API di Sicurezza
●
●
●
Le API di sicurezza, sono organizzate all’interno del
package (e relativi subpackage) java.security.
Attraverso esse lo sviluppatore ha la possibilità di
inserire funzionalità sia di alto che di basso livello.
Tale package è organizzato secondo un’architettura
provider based, ciò permette di avere più
implementazioni che operano tra loro.e
Java Criptlogy Extension(JCE)
●
●
●
La Java Criptlogy Extension (JCE):
Fornisce un framework per il crittaggio, il decrittaggio e
la generazione di chiavi, permettendo lo scambio di
chiavi.
Supporta cifrari simmetrici (DES, RC2, IDEA),
asimmentrici (RSA), di flusso (RC4) e algoritmi MAC
(Message Authentication Code).
La progettazione di JCE ruota intorno a due fondamentali
principi:
●
Indipendenza dagli algoritmi ed estensibilità
●
Interoperabilità e indipendenza dall'implementazione
Java Criptlogy Extension(JCE)
●
Indipendenza dagli algoritmi:è ottenuta definendo
una serie di cryptographics engines e una serie di classi
che forniscono le funzionalità di tali motori.
●
Indipendenza dall’implementazione: si ottiene come
conseguenza dell’utilizzo di un’architettura provider-based.
●
Interoperabilità delle implementazioni: permette ad
un provider di utilizzare gli oggetti creati da un altro, rendendo
possibile l’utilizzo di una chiave, creata con un provider
specifico, con tutti gli altri installati o la verifica della chiave
generata da un provider su un altro.
Java Criptlogy Extension(JCE)
Note:
●
●
●
Non tutti i provider forniscono i medesimi servizi; un
unico provider potrebbe non offrire tutte le funzioni di
cui si necessita.
La fase d’installazione di nuovi provider può avvernire
sia staticamente che dinamicamente.
La molteplicità di provider installati permette la
definizione di un ordine di preferenza secondo il quale
interrogare i provider per la ricerca del servizio
necessario.
Engine class
●
Cpytographic service: è in grado di fornire una
funzionalità, genera o distribuisce chiavi e parametri
necessari per una determinata transazione o crea oggetti
che incapsulano le chiavi in modo sicuro.
Esempio:
In JDK2, è presente un engine class per il calcolo del
digest di un certo messaggio, per la generazione di
coppie di chiavi, per la firma digitale e creazione del
keystore.
●
Per ogni engine class, esite una serie di sottoclassi che
rappresentano il servizio implementato secondo un
determinato algoritmo.
Principali Classi: Il Cifrario
●
●
●
●
E’ la classe più importante del framework;
Fornisce l’oggetto che implementa un cifrario, utilizzato
per crittare e decrittare;
E` acceduta come singleton attraverso il metodo
getInstance che restituisce un'istanza del cifrario;
Al momento dell'invocazione del metodo, è necessario
specificare la tipologia di algoritmo che si vuole fare
implementare al cifrario.
Principali Classi: Il Cifrario
Es:
Cipher c1 = Cipher.getInstance("DES");
Principali Classi: Il Cifrario
Una volta ottenuto il cifrario questo va inizializzato
secondo il compito che dovra' svolgere, questi 4 modi
sono:
●
ENCRYPT_MODE
●
DECRYPT_MODE
●
WRAP_MODE
●
UNWRAP_MODE
Principali Classi: Il Cifrario
Una volta che disponiamo del cifrario inizializzato,
possiamo crittare o decrittare dati invocando il metodo
doFinal.
E' possibile crittare in un solo passo o in molti passi, utile
a seconda di cosa va crittato.
●
Per il wrapping e unwrapping delle chiavi utilizziamo i
metodi wrap e unwrap forniti sempre dall'oggetto cifrario.
●
Principali Classi: Flusso Cifrato
In JCE viene introdotto il concetto di Flusso Cifrato
●
●
Flusso Cifrato: ottenuto combinando un InputStream (o
OutputStream) con l'oggetto Cipher.
CipherInputStream o CipherOutputStream: altro non
sono che delle classi Filter*putStream che crittano (o
decrittano) cio' che passa attraverso questo filtro.
Principali Classi: KeyGenerator
Il generatore di chiavi KeyGenerator
●
●
●
KeyGenerator: ottenuto anch'esso attraverso il metodo
getInstance accompagnato dal tipo di algoritmo da
utilizzare.
Il KeyGenerator anch'esso come il cifrario va
inizializzato, questa inizializzazione puo' essere
dipendente o no da un algoritmo.
Una volta inizializzato possiamo ottenere la chiave
invocando il metodo generateKey().
Principali Classi: SealedObject
La classe SealedObject
SealedObject: la classe che da la possibilita' di crittare gli
oggetti.
●
Definendo un oggetto che implementa l'interfaccia
Serializable e' poi possibile crittare la sua versione
“serializzata”.
●
Dall'altra parte e' possibile decrittare l'oggetto , deserializzarlo ed ottenere la versione originale.
●
Principali Classi: SealedObject
Esempio:
Cipher c = Cipher.getInstance("DES");
c.init(Cipher.ENCRYPT_MODE, sKey);
SealedObject so = new SealedObject("This is a secret",
c);
Principali Classi: Key Agreement
●
●
●
Key Agreement: allo stesso modo va richiesto
attraverso il metoto getInstance (passando come
parametro il tipo di algoritmo da utilizzare) e
inizializzato.
Una volta che l'oggetto e' pronto possiamo eseguire le
varie fasi usando in metodo doPhase a cui passiamo la
chiave ed un valore booleano che indica se la fase che
andra' eseguita sara' l'ultima o no.
Una volta terminato e' possibile generare la chiave
usando il metodo generateSecret (algoritmo).
Hash coding
I vari processi basati su crittografia fanno uso di una trasformazione tanto
semplice quanto fondamentale, ovvero quella della codifica hash.
Tale trasformazione opera su uno stream di dati (detti in chiaro) e
restituisce un codice identificativo di tale stringa.
Tale trasformazione infatti deve sottostare alle seguenti regole:
Una funzione hash è deterministica: lo stesso documento deve generare
sempre lo stesso codice.
●
Un codice hash deve essere uniformemente distribuito su tutto il
dominio disponibile.
●
Deve essere estremamente difficile il processo inverso decodifica:
anche se non è possibile dimostrare l’impossibilità del processo inverso di
decodifica, tale trasformazione deve essere il più difficile possibile.
●
Hash coding
●
●
●
La probabilità che due documenti producano lo stesso codice hash
deve essere prossima a zero: anche in questo caso non si può avere
la certezza matematica, e ci si deve affidare alla statistica. Questo è
comunque è un requisito fondamentale .
Alta dipendenza dalle condizioni iniziali: piccole modifiche del
messaggio devono portare a grosse differenze nel codice risultante.
Il codice non deve dare nessuna informazione sul messaggio
originale: per garantire una maggiore sicurezza, il codice non deve
contenere nessun riferimento diretto relativo al messaggio di origine.
Hash coding
Produrre buoni algoritmi di hashing è sicuramente un compito che è
bene lasciare agli esperti, affidandosi a quello che si trova già
disponibile.
Il JCE offre a tal proposito la classe java.secuirity.MessageDigest,
una classe astratta che serve come riferimento ad un generico
algoritmo di codifica hash. Le sotto-classi concrete (come ad
esempio java.security.MessageDigestSPI) implementano un
particolare algoritmo.
Nota:
Nella tabella “Java 1.1 ed algoritmi di Hash” , presente on-line, sono elencati gli
algoritmi disponibili con il JCE, e le loro caratteristiche principali
Hash coding
Vediamo come si puo ottenere il codice hasch data una
qualsiasi origine di byte:
●
Ricavare un istanza per un particolare algoritmo
●
Leggere i dati
●
Passare i dati al metodo update
●
Invocare il metodo digest() per ottenere il codice
Hash coding
L’esempio mostra come ottenere un codice hash di una particolare
pagina web (passando l’URL come parametro a linea di comando).
L’algoritmo utilizzato è l’SHA-1
import java.net.*;
import java.io.*;
import java.security.*;
import java.math.*;
public class URLDigest {
public static void main(String[] args) {
for (int i = 0; i < args.length; i++) {
try {
URL u = new URL(args[i]);
printDigest(u.openStream());
}
catch (MalformedURLException e) {
System.err.println(args[i] + " is not a URL");
}
catch (Exception e) {
System.err.println(e);
}
}
}
CONTINUA.....
Hash coding
public static void printDigest(InputStream in)
throws IOException, NoSuchAlgorithmException {
MessageDigest sha = MessageDigest.getInstance("SHA");
byte[] data = new byte[128];
while (true) {
int bytesRead = in.read(data);
if (bytesRead < 0) break;
sha.update(data, 0, bytesRead);
}
byte[] result = sha.digest();
for (int i = 0; i < result.length; i++) {
System.out.print(result[i] + " ");
}
System.out.println();
System.out.println(new BigInteger(result));
}
}
JCE in Pratica
Dopo una panoramica sulla “Teoria” del
package JCE vediamo ora come dotare
una applicazione di un meccanismo di
crittazione a chiave pubblica.
JCE in Pratica
La prima cosa che dobbiamo fare è generare la coppia di chiavi
necessarie avvalendoci delle classi Key, KeyPair, e
KeyPairGenerator.
KeyPairGenerator è un generatore di chiavi: tale classe deve essere
istanziata con il nome di un particolare algoritmo (useremo in
questo caso il DSA), e con un numero casuale.
Tale numero deve essere effettivamente il più casuale possibile:
spesso si utilizzano valori derivanti dalle pause che l’utente compie
fra la pressione di un tasto e l’altro, oppure monitorando i
movimenti del mouse.
JCE in Pratica
MessageDigest md = MessageDigest.getInstance("SHA");
String string = url + user + (new Date()).toString();
md.update(string.getBytes());
KeyPairGenerator keygen = KeyPairGenerator.getInstance("DSA");
keygen.initialize(512, new SecureRandom(md.digest()));
KeyPair pair = keygen.generateKeyPair();
ps.println(" Public-key: " + pair.getPublic().getEncoded()+ "
Private-key: " + pair.getPrivate().getEncoded()));
JCE in Pratica
L’utilizzo della classe SecureRandom offre maggiori sicurezze per
quanto riguarda la reale casualità del numero generato. Ora che
abbiamo generato la chiave, possiamo creare il motore di
crittazione:
Cipher myCipher = Cipher.newInstance("PKA");
myCipher.initEncrypt(key);
Byte[] data;
myCipher.cript(data);
JCE in Pratica
A questo punto possiamo crittare i dati (procedimento effettuato a
blocchi di byte per maggior sicurezza) semplicemente con la
seguente istruzione
CipherOutputStream cos = new CipherOutputStream(new
FileOutputStream("name", myCipher));
La classe CipherOutputStream (e l’equivalente CipherInputStream)
permettono di inviare dati direttamente in uno stream in forma
crittata. Può essere associata ad uno stream di compressione, da
un lato, e ad uno di scrittura su file, dall’altro.
JCE in Pratica
Crittografia.java:
Una semplice applicazione da console, che
passatogli come parametro una stringa/frase
genera una chiave, cripta questa
stringa/frase con la chiave creata, poi la
decripta.
JCE in Pratica
Codice:
public class Criptare{
public static void main(String[] args){
String stringa=new String("");
for (int i=0;i< args.length;i++){
//Leggo la stringa in input
stringa+=args[i];
}
System.out.println("La frase da Criptare/Decripta da criptare e' "+stringa);
try {
vai(stringa);
}
}
catch (Exception e) {
System.err.println(e);
}
CONTINUA......
//Chiamo il metodo per criptare la
stringa
JCE in Pratica
public static void vai(String stringa) throws IOException,
NoSuchAlgorithmException,NoSuchPaddingException,InvalidKeyException,IllegalBlockSizeException,BadPadd
ingException {
KeyGenerator keygen = KeyGenerator.getInstance("DES");
//Creo il generatore di chiavi
SecretKey key = keygen.generateKey();
//Creo la chiave
System.out.println("La chiave generata e': " + new String( key.getEncoded()));
Cipher cip = Cipher.getInstance("DES/ECB/PKCS5Padding");
criptazione con
cip.init(Cipher.ENCRYPT_MODE, key);
byte[] stringa_byte = stringa.getBytes();
byte[] risultato = cip.doFinal(stringa_byte);
//Creo il motore di
la chiave creata
//Inizializzo il motore
//Trasformo la stringa in una
sequenza di byte
//Cripto la sequenza di byte
System.out.println("La frase criptata e': "+new String(risultato));
cip.init(Cipher.DECRYPT_MODE, key);
motore, ma
Descriptare
byte[] decriptato = cip.doFinal(risultato);
Bytes
System.out.println(new String(decriptato));
dalla
bytes ottenuti
}
}
//Inizializzo di nuovo il
ora per
//Decripto la sequenza di
//Stampo la stringa derivata
decodificazaione dei
prima