Documentazione Contatti      
Documentazione > Tutorial > Cifrare dati in Java, parte 3: Cifratura Simmetrica
Hide
Best Practices
EJB
Frameworks
Howto
J2EE
J2ME and Wireless
J2SE
JSP e Servlet
Java Application Server
Java IDE/Tools
Java Media
Java Security
Java Sys Admin
Java e XML
Java e SQL
OpenSource Java
Patterns
Repository
Tesi
UML
Web Services
Slide
White Paper di jws.it
project management
Eventi
Groovy



Sconto del 15% al JaxItalia


Henry Ford
Vero progresso quando i vantaggi di una nuova tecnologia diventano per tutti


OpenOffice & MySql - Gestione dei Report



  Visualizza Commenti (0) Aggiungi Commento    
 
Cifrare dati in Java, parte 3: Cifratura Simmetrica
By Enrico Cesaretti
23 settembre 2008
Valutazione Acquisita: 80

  Cifrare dati in Java, parte 3: Cifratura Simmetrica
Program La classe javax.cipher.Cipher
Program SALT
Program INITIAL_VECTOR

Come detto nel primo articolo sull'architettura, la cifratura simmetrica usa la stessa chiave per codificare e per decodificare.
Il vantaggio è la velocità di codifica, quindi la si può utilizzare per codificare una grande quantità di dati.

Una chiave simmetrica, per essere sicura, deve avere una profondità di almeno  128 bit.

Dopo aver installato correttamente e testato l’implementazione del nostro provider, saremo in grado di implementare un semplice codice che preso un dato in chiaro lo codifica e poi lo decodifica.

Articoli correlati:
Cifrare dati in Java, parte 1: Architettura

Cifrare dati in Java, parte 2: Installazione Ambiente  

Cifrare i dati in Java, parte4: Cifratura Asimmetrica



La classe javax.cipher.Cipher top
La classe fulcro del package per la criptazione è javax.cipher.Cipher che serve a creare un cifrario per codificare e decodificare. Tale classe viene inizializzata mediante un metodo factory(Cipher.getInstance(…)). Chiamando tale metodo è necessario specificare:
  • La tipologia di algoritmo in uso(DESede,RSA ecc…).
  • La modalità di codifica per il cifrario. Esistono quattro modalità:

  1. ECB
    Ogni carattere codificato risulterà sempre corrispondente alla stessa serie di simboli. Ad esempio se codifico per 3 volte il testo “testo di prova” otterrò sempre lo stesso testo cifrato “”.
  2. CBC
    Ogni blocco avrà un sistema di codifica diverso calcolato dal valore del blocco precedente. Dal momento che non esistono ripetizioni è praticamente impossibile decifrare il risultato senza conoscere la chiave utilizzata.
  3. CFB
    È un CBC specializzato per codificare blocchi di byte di piccole dimensioni, utilissimo per rendere sicure le conversazioni in chat dove vengono inviate stringhe di piccole dimensioni.
  4. OFB
    Una versione di CFB che salvaguarda la perdita dei dati, se durante una chat si perde un bit, anche il testo in chiaro risulterà mancante di un solo bit. Con le altre modalità va perso tutto il blocco.
  • Il padding( Modalità di scrittura della chiave).

Ecco un esempio di inizializzazione del cifrario:

Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Pdding");

All’atto dell’inizializzazione andremo a specificare la chiave da utilizzare che deve essere compatibile con l’algoritmo in uso ed il tipo di operazione(Cipher.ENCRYPT_MODE oppure Cipher.DECRYPT_MODE).

KeyGenerator keygen = KeyGenerator.getInstance("DESede");
keygen.init(168);
Key miaChiave = kg.generateKey();


Quindi inizializziamo:

cipher.init(Cipher.ENCRYPT_MODE, miaChiave);


A questo punto è possibile ottenere una serie di byte in chiaro e cifrarli grazie al metodo Cipher.doFinal().

Byte[] plaintext = "testo in chiaro".getBytes();
byte[] ciphertext = cipher.doFinal(plaintext);
Per la decifrazione del testo sarà necessario inizializzare nuovamente il cifrario in modalità Cipher.DECRYPT_MODE:
cipher.init(Cipher.DECRYPT_MODE, miaChiave);
byte[] decripted = cipher.doFinal(ciphertext);


Ecco l’esempio completo in cui viene fatta codifica e decodifica usando l’algoritmo DESede:

package it.enrico.jce.simmetric.desede;

package it.enrico.jce.simmetric.desede;

import javax.crypto.*;
import java.security.*;

public class MainDESedeECB
{
    public static void main(String args[])throws Exception
    {
        System.out.println("Generazione chiave in corso...");
        
        //GENERA UNA CHIAVE DESede
        KeyGenerator keygen = KeyGenerator.getInstance("DESede");
        keygen.init(168);
        Key chiave = keygen.generateKey();
       
        System.out.println("Chiave generata: "+chiave);
       
        //OTTIENE UN CIFRARIO DESede/ECB/PKCS5Padding IN ENCRYPT_MODE
        Cipher cipher = Cipher.getInstance("DESede/ECB/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, chiave);
       
        byte[] plaintext = args[0].getBytes("UTF8");
       
        System.out.println("Testo in chiaro: "+args[0]);
       
        System.out.print("Byte in chiaro: ");
        for(int i=0; i<plaintext.length; i++)
        {
            System.out.print(plaintext[i]+" ");   
        }System.out.println();
       
        //CODIFICA IL TESTO
        byte[] ciphertext = cipher.doFinal(plaintext);
       
        System.out.println("Testo codificato: "+ciphertext);
        System.out.print("Byte codificati: ");
        for(int i=0; i<ciphertext.length; i++)
        {
            System.out.print(ciphertext[i]+" ");   
        }System.out.println();
       
        //MODIFICA IL CIFRARIO IN DECRYPT_MODE
        cipher.init(Cipher.DECRYPT_MODE, chiave);
       
        //DECODIFICA IL TESTO
        byte[] decripted = cipher.doFinal(ciphertext);
       
        String output = new String(decripted, "UTF8");
       
        System.out.println("Testo decodificato: " + output);
    }   
}


Il programma in questione ha però degli evidenti limiti. Infatti la chiave “chiave” è usa e getta, una volta terminato il processo viene persa, per poter riutilizzare tale chiave esistono dei metodi per persisterne i byte in uno storage. Ovviamente la persistenza dei dati relativi ad una chiave deve essere a sua volta protetta in qualche modo, così viene utilizzata una cifratura basata su password PBE (Password-Based Encryption), dove la chiave viene memorizzata cifrata mediante una password conosciuta soltanto dall’utente che la genera, in un luogo sicuro come la mente. Ecco illustrati in breve i passi per lavorare con tale tecnologia:

Codifica
  1. L’utente sceglie una password.
  2. La JCE ne calcola l’impronta (hash).
  3. Sulla base dell'impronta JCE genera una chiave per un algoritmo simmetrico.
  4. Andremo a creare un cifrario.
  5. Col cifrario verrà codificato il testo in chiaro.

Decodifica
  1. L’utente sceglie una password.
  2. JCE controlla se l'impronta corrisponde a quella inserita durante la codifica.
  3. Se l’impronta è corretta verrà ricreata la chiave e decodificato il dato.

Attenzione, ad ogni impronta corrisponde una chiave. Dal momento che l’impronta è univoca per ogni password può esistere un ipotetico “buco” di sicurezza, per risolvere le chiavi un malintenzionato potrebbe effettuare un brute force usando delle chiavi (parole) conosciute presenti in un dizionario. A tale proposito viene utilizzata una tecnologia di ripetizione dei byte per il completamento dei blocchi detta SALT o INITIAL_VECTOR che modifica la chiave memorizzata su disco con dei byte casuali.


 

SALT top
Il salt è una serie di 8 byte casuali che viene aggiunta all'inizio della password prima di calcolarne l’impronta. Il salt poi verrà aggiungo all’impronta risultante:






INITIAL_VECTOR top

Un altro metodo di sicurezza per memorizzare le password è l’INITIAL VECTOR che consiste nel ricalcolare più volte l'impronta della password. Se impostiamo 10000 ripetizioni, verrà calcolata l'impronta della password in maniera ricorsiva per 10000 volte.

Vediamo un esempio che illustra come creare una chiave “Rijndael”(triple des), ne applica il salt e la memorizza sul FS:

private static final String FILE_PATH = "it/enrico/jce/simmetric/rijndael/";
private static String KEY_FILENAME = "rijndael.key";
public class PBETest
{
   public static void createKey(char password[])throws Exception
    {
        System.out.println("Generazione chiave Rijndael in corso...");
        KeyGenerator keygenerator = KeyGenerator.getInstance("Rijndael");
        keygenerator.init(256);
        javax.crypto.SecretKey chiaveInChiaro = keygenerator.generateKey();
        System.out.println("Chiave Rijndael generata!");
        
        //GENERAZIONE DEL SALT
        byte salt[] = new byte[8];
        SecureRandom securerandom = new SecureRandom();
        securerandom.nextBytes(salt);
        PBEKeySpec pbekeyspec = new PBEKeySpec(password);
       
        //MODIFICA LA CHIAVE AGGIUNGENDO IL PBE(password)
        SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
        javax.crypto.SecretKey PBESecretkey = secretkeyfactory.generateSecret(pbekeyspec);
        PBEParameterSpec pbeparameterspec = new PBEParameterSpec(salt, ITERATIONS);
       
        //UTILIZZA UN CIFRARIO PBEWithSHAAndTwofish-CBC IN ENCRYPT_MODE
        Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
        cipher.init(Cipher.ENCRYPT_MODE, PBESecretkey, pbeparameterspec);
       
        //APPLICA LA PASSWORD ALLA CHIAVE CIFRATA
        byte chiaveCifrata[] = cipher.doFinal(chiaveInChiaro.getEncoded());
       
        //SCRIVE CHIAVE E SALT
        FileOutputStream fileoutputstream = new FileOutputStream(FILE_PATH + KEY_FILENAME);
        fileoutputstream.write(salt);
        fileoutputstream.write(chiaveCifrata);
        fileoutputstream.close();
       
        System.out.println("salt: "+new String(salt) );
        System.out.println("chiave: "+new String(chiaveCifrata) );
   }

//...CONTINUA

}

Per caricare una chiave precedentemente salvata è necessario applicare la decifrazione con PBE al contrario.
Ecco un metodo che carica la chiave sottraendone il salt:

   private static Key loadKey(char password[])throws Exception
    {
        FileInputStream fileinputstream = new FileInputStream(FILE_PATH + KEY_FILENAME);
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        for(int i = 0; (i = fileinputstream.read()) != -1;)
        {
            baos.write(i);
        }
               
        fileinputstream.close();
       
        //LEGGE LA CHIAVE CON IL SALT
        byte saltEChiave[] = baos.toByteArray();
        baos.close();
       
        //ESTRAE IL SALT
        byte salt[] = new byte[8];
        System.arraycopy(saltEChiave, 0, salt, 0, 8);
        int j = saltEChiave.length - 8;
       
        //LEGGE LA ChiAVE CIFRATO
        byte chiaveCifrata[] = new byte[j];
        System.arraycopy(saltEChiave, 8, chiaveCifrata, 0, j);
       
        System.out.println("salt: "+new String(salt) );
        System.out.println("chiave: "+new String(chiaveCifrata) );
       
        //APPLICA LA PASSWORD ED OTTIENE LA CHIAVE IN CHIARO
        PBEKeySpec pbekeyspec = new PBEKeySpec(password);
        SecretKeyFactory secretkeyfactory = SecretKeyFactory.getInstance("PBEWithSHAAndTwofish-CBC");
        javax.crypto.SecretKey secretkey = secretkeyfactory.generateSecret(pbekeyspec);
        PBEParameterSpec pbeparameterspec = new PBEParameterSpec(salt, ITERATIONS);
       
        //UTILIZZA UN CIFRARIO PBEWithSHAAndTwofish-CBC IN DECRYPT_MODE
        Cipher cipher = Cipher.getInstance("PBEWithSHAAndTwofish-CBC");
        cipher.init(Cipher.DECRYPT_MODE, secretkey, pbeparameterspec);
       
        //APPLICA LA PASSWORD ED OTTIENE LA CHIAVE
        byte chiaveInChiaro[] = cipher.doFinal(chiaveCifrata);
        SecretKeySpec secretkeyspec = new SecretKeySpec(chiaveInChiaro, "Rijndael");
        return secretkeyspec;
   }

Una volta ottenuta la chiave sarà possibile usarla per cifrare o decifrare un dato.
Ecco due metodi usati per queste funzioni:

(Questo metodo carica la chiave del FS e la usa insieme ad un INITIAL_VECTOR per codificare e persistere il dato sul FS)
   private static void encrypt(char password[], String fileInput, String fileOutput)
        throws Exception
    {
        System.out.println("Caricamento chiave...");
       
        //CARICA LA CHIAVE CON PASSWORD
        Key key = loadKey(password);
        System.out.println("Chiave caricata!");
       
        //CREA UN LETTORE Rijndael/CBC/PKCS5Padding
        Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
        System.out.println("Inizializzazione SecureRandom...");
       
        //GENERA UN INITIAL VECTOR
        SecureRandom securerandom = new SecureRandom();
        byte iv[] = new byte[16];
        securerandom.nextBytes(iv);
       
        FileInputStream fis = new FileInputStream(FILE_PATH + fileInput);
        FileOutputStream fos = new FileOutputStream(FILE_PATH + fileOutput);
       
        //SCRIVE L'INITIAL VECTOR NEL FILE DA CIFRARE
        fos.write(iv);
       
        IvParameterSpec ivparameterspec = new IvParameterSpec(iv);
        System.out.println("Inizializzazione Cifrario...");
       
        //INIZIALIZZA UN CIFRARIO CON L'INITIAL VECTOR CASUALE IN  ENCRYPT_MODE
        cipher.init(Cipher.ENCRYPT_MODE, key, ivparameterspec);
       
        //CODIFICA IL FILE CON UN FILTRO CipherOutputStream
        CipherOutputStream cipheroutputstream = new CipherOutputStream(fos, cipher);
        System.out.println("Cifratura in corso...");
       
        for(int i = 0; (i = fis.read()) != -1;)
        {
            cipheroutputstream.write(i);
        }

        fis.close();
        cipheroutputstream.close();
   }


(Questo metodo carica la chiave del FS e la usa insieme ad un INITIAL_VECTOR per leggere e decodificare il dato cifrato dal FS)

   private static void decrypt(char[] password, String fileInput, String fileOutput)
        throws Exception
    {
        System.out.println("Caricamento chiave...");
       
        //CARICA LA CHIAVE
        Key key = loadKey(password);
        System.out.println("Chiave caricata!");
       
        //CREA UN LETTORE Rijndael/CBC/PKCS5Padding
        Cipher cipher = Cipher.getInstance("Rijndael/CBC/PKCS5Padding");
       
        FileInputStream fis = new FileInputStream(FILE_PATH + fileInput);
        FileOutputStream fos = new FileOutputStream(FILE_PATH + fileOutput);
       
        //LEGGE L'INITIAL VECTOR
        byte iv[] = new byte[16];
        fis.read(iv);
        IvParameterSpec ivparameterspec = new IvParameterSpec(iv);
        System.out.println("Inizializzazione Cifrario...");
       
        //INIZIALIZZA UN CIFRARIO CON L'INITIAL VECTOR CASUALE IN  DECRYPT_MODE
        cipher.init(Cipher.DECRYPT_MODE, key, ivparameterspec);
        CipherInputStream cis = new CipherInputStream(fis, cipher);
        System.out.println("Decifrazione file...");
       
        //DECIFRA IL FILE E LO SCRIVE SU UN FILE IN CHIARO
        for(int i = 0; (i = cis.read()) != -1;)
        {
            fos.write(i);
        }
        cis.close();
        fos.close();
   }

Bene, adesso siamo pronti ad affrontare nel prossimo articolo la cifratura asimmetrica.

 

Articoli correlati:
Cifrare dati in Java, parte 1: Architettura
Cifrare dati in Java, parte 2: Installazione Ambiente
Cifrare i dati in Java, parte4: Cifratura Asimmetrica






JavaPortal è ideato da:    
K-Tech Logo










LICENZA



Eccetto dove diversamente specificato, i contenuti di questo sito sono rilasciati sotto licenza Creative Commons

Sitemap  © 2002-2004 Copyright Information. Privacy . Today is sabato 19 giugno 2010