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 nell’algoritmo 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à:

  • La robustezza di un algoritmo di cifratura dipende direttamente dalla lunghezza della chiave, perché chiavi lunghe implicano uno spazio di possibili chiavi molto ampio.
  •  

    Algoritmo a chiave simmetrica (o a chiave segreta):
    c’è un’unica 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 l’altra per decifrare (e viceversa)
    ® una segreta, l’altra 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 l’integrità del messaggio (senza cifrarlo)

  • Proprietà fondamentale: l’algoritmo di calcolo deve assicurare che sia "estremamente improbabile" che due messaggi diversi possano condurre alla medesima impronta.

    Come funziona: il destinatario, calcolando l’impronta 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 l’autenticità e la non ripudiabilità (senza cifrarlo).

  • Può essere usata sia con algoritmi a chiave segreta sia, più spesso, con algoritmi a chiave pubblica.

    Come funziona: il destinatario, applicando al messaggio e alla firma ricevuti l’algoritmo di verifica con la chiave (pubblica) del presunto mittente, può verificare l’autenticità 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 all’esportazione 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 dell’IAIK fornisce invece una gamma più ampia di algoritmi, modalità e schemi di padding.

     

    Esempi

    Esempio 1   calcolo e verifica di un digest di un messaggio
    Esempio 2   verifica di un digest di un messaggio
    Esempio 3   calcolo e verifica della signature di un messaggio
    Esempio 4   cifratura e decifratura di un messaggio con DES
    Riassunto algoritmi

    Esempio 1
    Calcolo dell’impronta 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 dell’impronta 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 rete

    try {

    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 file

    byte[] 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 CIFRATO

      xxx = des.doFinal(data);
      printBytes("xxx", xxx);

      // FASE 3 (a): REINIZIALIZZAZIONE DEL Cipher

      des.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);
    }

     }

    }