|
Vero progresso quando i vantaggi di una nuova tecnologia diventano per tutti
|
|
|
|
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à:
- 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 “”. - 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. - 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. - 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
- L’utente sceglie una password.
- La JCE ne calcola l’impronta (hash).
- Sulla base dell'impronta JCE genera una chiave per un algoritmo simmetrico.
- Andremo a creare un cifrario.
- Col cifrario verrà codificato il testo in chiaro.
Decodifica
- L’utente sceglie una password.
- JCE controlla se l'impronta corrisponde a quella inserita durante la codifica.
- 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: |
 |

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