L'ARCHITETTURA CRITTOGRAFICA DI JAVA
Nomenclatura e Riassunto dei Concetti Fondamentali
Messaggio in chiaro: il messaggio originale (leggibile da tutti)
Messaggio cifrato: il messaggio modificato per nasconderne il contenuto.
Algoritmo di cifratura: il metodo con il quale, dato un messaggio in chiaro, si produce il corrispondente messaggio cifrato.
Chiave di cifratura: quel valore introdotto nellalgoritmo di cifratura per "personalizzarlo" volta per volta (a parità di messaggio in chiaro, il messaggio cifrato ottenuto risulta completamente diverso cambiando la chiave).
Proprietà fondamentale: dal messaggio cifrato deve essere (praticamente) impossibile risalire al messaggio in chiaro.
Per garantire tale proprietà:
Algoritmo a
chiave simmetrica (o a chiave segreta):
cè ununica chiave che serve sia per cifrare
che per decifrare.
Algoritmo a
chiave asimmetrica (o a chiave pubblica):
ci sono due chiavi: se una viene usata per cifrare,
occorre laltra per decifrare (e viceversa) ®
una segreta, laltra pubblica.
IMPRONTA (DIGEST) di un messaggio: è una stringa di dimensione fissa calcolata a partire dal messaggio originale, ma indipendente dalla dimensione dello stesso.
Scopo: garantire lintegrità del messaggio (senza cifrarlo)
Come funziona: il destinatario, calcolando limpronta del messaggio ricevuto e confrontandola con quella ricevuta, può sapere in modo certo se il messaggio è stato alterato.
FIRMA (SIGNATURE) di un messaggio: è una stringa (spesso di dimensione fissa) calcolata a partire dal messaggio originale e da una chiave.
Scopo: firmare il messaggio, in modo da garantirne lautenticità e la non ripudiabilità (senza cifrarlo).
Come funziona: il destinatario, applicando al messaggio e alla firma ricevuti lalgoritmo di verifica con la chiave (pubblica) del presunto mittente, può verificare lautenticità della firma del messaggio.
Java Cryptography Architecture (JCA)
Due principi:
Due tipi di classi:
Caratteristiche:
AD ESEMPIO:
MessageDigest, Signature e KeyPairGenerator sono tre diverse engine class, che definiscono rispettivamente le funzionalità di digest (impronta digitale), signature (firma digitale), e di generatore di coppie di chiavi, secondo uno degli algoritmi a chiave pubblica disponibili.
La struttura "a livelli" della JCA
La struttura della JCA è a due livelli:
ATTENZIONE: a causa delle limitazioni allesportazione di tecnologia crittografica fuori dagli Stati Uniti, la JCE della Sun non è esportabile.
Gli utenti europei devono quindi utilizzare un diverso provider scritto fuori dagli Stati Uniti, come IAIK-IJCE della IAIK (http://jcewww.iaik.tu-graz.ac.at/).
Funzionalità fornite dalla JCA "base"
La JCA base fornisce le seguenti classi:
ALGORITMI SUPPORTATI:
JCA vs. JCE
Le classi fornite dalla JCA consentono di:
usando coppie di chiavi pubblica/privata.
Le classi fornite dalla JCA non consentono di:
con algoritmi a piacere.
FUNZIONALITÀ DEFINITE DALLA JCE
La JCE definisce le API standard per la cifratura / decifratura:
con:
Ogni provider può implementare solo alcune delle API definite dalla JCE. In particolare il provider standard SUN (jdk 1.1.x) fornisce:
Il provider dellIAIK fornisce invece una gamma più ampia di algoritmi, modalità e schemi di padding.
Esempi
Esempio 1
Calcolo dellimpronta digitale di un messaggio
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
class Esempio1 {
public static void main(String args[]){
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-1");
//SHA = Secure Hash Algorithm
} catch (NoSuchAlgorithmException e) {
System.out.println("Algoritmo non supportato");
}byte[] messaggio = {10,20,30,40,50};
byte[] impronta = sha.digest(messaggio);
System.out.println("Impronta:");
for (int i=0; i<impronta.length; i++)
System.out.print("" + impronta[i]
+ "\t");
System.out.println(""); }
}
Esempio 2
Verifica dellimpronta digitale di un messaggio
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.io.IOException;
import java.io.BufferedReader;
import java.io.InputStreamReader;
class Esempio2 {
public static void main(String args[]){
System.out.println("Verifica del digest"+ + "(impronta digitale) di un messaggio");
byte[] impronta1 = new byte[20];
BufferedReader in = new BufferedReader(
new InputStreamReader(System.in));System.out.println("Inserire l'impronta da verificare (un numero per riga):");
// nella realtà sarà letta da un file
// o scaricata via retetry {
for(int i=0; i<20; i++) {
String buf = in.readLine();
impronta1[i] = Byte.parseByte(buf);
}}
catch (IOException e) {
System.out.println("Lettura da input fallita");
}System.out.println("Impronta letta:");
for (int i=0; i<impronta.length; i++)
System.out.print("" + improntaData[i]
+ "\t");
System.out.println("");// ------- parte identica a Esempio0
MessageDigest sha = null;
try {
sha = MessageDigest.getInstance("SHA-1");
}
catch (NoSuchAlgorithmException e) {
System.out.println("Algoritmo richiesto non supportato");
}byte[] messaggio = {10,20,30,40,50};
// nella realtà sarà letto da filebyte[] impronta2 = sha.digest(messaggio);
// fine parte identica a Esempio0// verifica impronta
if (MessageDigest.isEqual(impronta1,
impronta2))
System.out.println("MSG INTEGRO");
else
System.out.println("MSG ALTERATO!");
}
}
Esempio 3
Calcolo (e verifica) della firma digitale di un messaggio
Si procede in due fasi (tre se si include la verifica).
Fase 1 (generazione della coppia di chiavi)
Fase 2 (generazione della firma)
Fase 3 (verifica della firma)
import java.security.KeyPairGenerator;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.Signature;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.security.SignatureException;
class Esempio3 {
public static void main(String args[]){
// FASE 1: GENERAZIONE DELLA COPPIA DI CHIAVI
KeyPairGenerator gen = null;
try {
gen = KeyPairGenerator.getInstance("DSA");
// DSA = Digital Signature Algorithm
} catch (NoSuchAlgorithmException e1) {
System.out.println("Algoritmo non supportato");
System.exit(1);
}gen.initialize(1024, new SecureRandom( ));
// chiavi modulo 1024 bit, seme definito da Java
// (si potrebbe passare un seme definito dal-
// l'utente sotto forma di array di bytes)KeyPair kp = gen.generateKeyPair();
PrivateKey kpriv = kp.getPrivate();
PublicKey kpub = kp.getPublic();// FASE 2: GENERAZIONE DELLA FIRMA DIGITALE
Signature s = null;
try {
s = Signature.getInstance("DSA");
}
catch (NoSuchAlgorithmException e2) {
System.out.println("Algoritmo non supportato");
System.exit(2);
}try {
s.initSign(kpriv);
// .. inizializz. con la chiave privata
} catch (InvalidKeyException e3) {
System.out.println("Chiave privata non valida");
System.exit(3);
}byte[] data = {10,20,30,40,50,60,70,80,90};
byte[] xxx = null;try {
s.update(data);
xxx = s.sign();
// xxx contiene la versione firmata di data
} catch (SignatureException e4) {
System.out.println("Firma non valida");
System.exit(4);
}System.out.println("Dati: " + data);
System.out.println("Xxx: " + xxx);// FASE 3: VERIFICA DELLA FIRMA DIGITALE
try {
s.initVerify(kpub);
// inserisce la chiave di decifratura
} catch (InvalidKeyException e5) {
System.out.println("Chiave pubblica non valida");
System.exit(5);
}
boolean res = false;try {
s.update(data);
res = s.verify(xxx); // verifica la firma
} catch (SignatureException e6) {
System.out.println("Firma non valida");
System.exit(6);
}
if (res)
System.out.println("Verifica positiva.");
else
System.out.println("Verifica NEGATIVA!!");
}
}
CIFRATURA DI MESSAGGI CON LA IAIK-JCE
Per cifrare o decifrare un messaggio le classi fondamentali sono Cipher (o CipherStream) e KeyGenerator, da utilizzare come segue:
Il metodo doFinal()
Per decifrare il risultato così ottenuto occorre re-inizializzare l'oggetto Cipher specificando il modo DECRYPT e la chiave di decifratura, quindi richiamarare nuovamente doFinal().
Esempio 4
Cifratura (e successiva decifratura) di un messaggio con DES
Fase 0 (creazione e installazione del provider IAIK)
Fase 1 (generazione della chiave segreta)
Fase 2 (creazione del Cipher e del messaggio cifrato)
Fase 3 (decifratura del messaggio e verifica del risultato)
import java.security.Security;
import java.security.SecureRandom;
import java.security.NoSuchAlgorithmException;
import java.security.InvalidKeyException;
import java.security.NoSuchProviderException;
import java.security.Key;
import javax.crypto.*;
import iaik.security.provider.*;
class Esempio4 {
static void printBytes(String name, byte[] b){
System.out.print(name + ": ");
for (int i=0; i<b.length; i++)
System.out.print(b[i] + " ");
System.out.println("");
System.out.println("-----------------------");
}
public static void main(String args[]){
// FASE 0: INSTALLAZIONE DEL PROVIDER IAIK
IAIK provider = new IAIK();
Security.addProvider(provider);// FASE 1 (a): CREAZIONE DEL GENERATORE CHIAVI
KeyGenerator gen = null;
try {
gen = KeyGenerator.getInstance("DES","IAIK");
} catch (NoSuchAlgorithmException e1) {
System.out.println("Algoritmo non supportato");
System.exit(1);
}
catch (NoSuchProviderException e1) {
System.out.println("Provider non supportato");
System.exit(1);
}// FASE 1 (b): GENERAZIONE DELLA CHIAVE SEGRETA
gen.init(new SecureRandom());
Key k = gen.generateKey();
printBytes("chiave", k.getEncoded());// il messaggio in chiaro (e due buffer)
byte[] data = {10,20,30,40,50,60,70,80};
printBytes("Dati", data);
byte[] xxx = null, newdata = null;// FASE 2 (a): CREAZIONE E INIZIALIZZ. DEL Cipher
Cipher des = null;
try {
des = Cipher.getInstance("DES","IAIK");
des.init(Cipher.ENCRYPT_MODE, k);
// Sun: des.initEncrypt(k);
System.out.println("Cipher pronto per cifrare");
// FASE 2 (b): CREAZIONE DEL MESSAGGIO CIFRATOxxx = des.doFinal(data);
printBytes("xxx", xxx);
// FASE 3 (a): REINIZIALIZZAZIONE DEL Cipherdes.init(Cipher.DECRYPT_MODE, k);
// Sun: des.initDecrypt(k);
System.out.println("Cipher pronto a decifrare");// FASE 3 (b): DECIFRATURA DEL MESSAGGIO
newdata = des.doFinal(xxx);
printBytes("NewData", newdata);// FASE 3 (c): VERIFICA DEL RISULTATO
boolean res = true;
if (data.length != newdata.length) res = false;
else
for (int j=0; j<data.length; j++)
if (data[j] != newdata[j]) {
res = false; break;
}
if (res) System.out.println("Verifica positiva");
else System.out.println("Verifica NEGATIVA!!");} // chiude il try iniziale
catch (NoSuchProviderException e2) {
System.out.println("Provider non supportato");
System.exit(2);
}
catch (NoSuchAlgorithmException e2) {
System.out.println("Algoritmo non supportato");
System.exit(2);
}
catch (NoSuchPaddingException e2) {
System.out.println("Padding non supportato");
System.exit(2);
}
catch (BadPaddingException e3) {
System.out.println("Bad Padding");
System.exit(3);
}
catch (InvalidKeyException e3) {
System.out.println("Chiave non valida");
System.exit(3);
}
catch (IllegalBlockSizeException ex) {
System.out.println("Dimensione blocco illegale");
System.exit(3);
}
}
}