JCA o Java Cryptography Architecture è il framework java per la crittografia che fa parte della API di sicurezza di Java, ed è stato introdotto nei pacchetti java.security
e java.crypto
. JCA fornisce le principali funzionalità di crittografia quali: firma digitale, message digest (hash), certificati e convalida dei certificati, crittografia (simmetrica/blocco asimmetrico/cifrari a flusso), generazione e gestione di chiavi, generazione di numeri casuali, e molto altro.
Nel presente articolo sono descritte le funzionalità di base per la criptazione e decriptazione, sia simmetrica che asimmetrica.
Generazione di Chiavi Crittografiche
La chiave crittografica è una informazione che è utilizzata in un algoritmo di criptazione e decriptazione per generare un testo cifrato da uno in chiaro. Possono avere lunghezza variabile in funzione dell’algoritmo utilizzato ed in generale si parla di chiavi simmetriche o asimmetriche. JCA offre le funzionalità per la generazione di entrambe le tipologie di chiavi.
La classe KeyGenerator espone le funzionalità per la generazione di chiavi simmetriche. Per istanziarla possiamo invocare il metodo getInstance()
specificando l’algoritmo che si intende utilizzare. Si noti che questa modalità di istanziazione di oggetti in JCA è molto comune perché il framework è progettato in modo da demandare l’implementazione degli algoritmi a provider esterni registrati. La stringa di input specifica l’implementazione (ovvero algoritmo) richiesta e se non presente il metodo restituisce l’eccezione NoSuchAlgorithmException.
Il codice seguente generare una chiave simmetrica.
1 2 3 4 5 6 7 8 9 |
private static final String SYM_ALGORITHM = "AES"; private static final Integer SYM_KEY_SIZE = 128; public static Key generateSymmetricKey() throws NoSuchAlgorithmException { KeyGenerator generator = KeyGenerator.getInstance( SYM_ALGORITHM ); generator.init( SYM_KEY_SIZE ); SecretKey key = generator.generateKey(); return key; } |
- AES (128)
- DES (56)
- DESede (168)
- HmacSHA1
- HmacSHA256
Per la generazione di una coppia di chiavi asimmetriche (pubblica e privata) la classe del framework da utilizzare è KeyPairGenerator
, come mostrato nel codice seguente. La coppia di chiavi è restituita nel bean KeyPair
caratterizzata da due sole proprietà in sola lettura: publicKey
e privateKey
.
1 2 3 4 5 6 7 8 |
private static final String ASYM_ALGORITHM = "RSA"; private static final Integer ASYM_KEY_SIZE = 1024; public static KeyPair generateAsymmetricKey() throws NoSuchAlgorithmException { KeyPairGenerator generator = KeyPairGenerator.getInstance( ASYM_ALGORITHM ); generator.initialize( ASYM_KEY_SIZE ); return generator.generateKeyPair(); } |
- DiffieHellman (1024)
- DSA (1024)
- RSA (1024, 2048)
Crittografia Simmetrica
La crittografia simmetrica o crittografia a chiave privata indica un metodo di cifratura molto semplice in cui la chiave utilizzata per cifrare il testo in chiaro è la stessa utilizzata per decifrarlo. La figura seguente mostra il processo appena descritto.
La classe che espone i servizi per la crittazione e decrittazione è java.crypto.Cipher
. Come già visto per ottenerne una istanza è necessario invocare il metodo getInstance() passando una stringa (transformation) che indica il set di operazioni che devono essere eseguite sull’input per ottenere l’output richiesto. Tale stringa ha il formato “algorithm/mode/padding” dove:
- algorithm indica l’algoritmo di crittazione desiderato;
- mode indica il metodo di crittazione che può essere a Blocchi di Cifre o a Flusso di Cifre;
- padding nel caso di cifratura a blocchi indica l’algoritmo da utilizzare per “riempire” il blocco finale.
Il codice seguente mostra un esempio di cifratura simmetrica con algoritmo di cifratura a blocchi AES in modalità CBC. Il metodo CBC combina il blocco corrente con l’esito della crittazione del blocco precedente, quindi necessita di un vettore di inizializzazione per cifrare il primo blocco.
1 2 3 4 5 6 7 |
public static byte [] encrypt( Key key, byte[] iv, byte[] plaintext ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); cipher.init( Cipher.ENCRYPT_MODE, key, new IvParameterSpec( iv ) ); return cipher.doFinal( plaintext ); } |
L’oggetto Cipher
può essere utilizzato sia per criptare che per decriptare. La selezione della modalità operativa si effettua passando il parametro mode
al metodo init()
, che nel nostro esempio assume il valore ENCRYPT_MODE
. A tale metodo passiamo anche la chiave da utilizzare per l’operazione ed il vettore di inizializzazione.
Per eseguire l’operazione inversa di decriptazione il codice java sarà simile al precedente ma l’operazione specificata nel metodo init()
dovrà essere DECRYPT_MODE
.
1 2 3 4 5 6 7 |
public static byte [] decrypt( Key key, byte[] iv, byte[] ciphertext ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException { Cipher cipher = Cipher.getInstance( "AES/CBC/PKCS5Padding" ); cipher.init( Cipher.DECRYPT_MODE, key, new IvParameterSpec( iv ) ); return cipher.doFinal( ciphertext ); } |
Si noti che con tale algoritmo la sorgente e la destinazione oltre a doversi scambiare in modo sicuro la chiave simmetrica, devono anche conoscere il vettore di inizializzazione.
Per completezza si riporta anche il codice per la generazione del vettore di inizializzazione, in cui si utilizza la classe SecureRandom
che utilizza algoritmi per la generazione di numeri casuali non facilmente predicibile.
1 2 3 4 5 6 |
public static byte [] generateInitVector() { SecureRandom random = new SecureRandom(); byte [] iv = new byte [ SYM_KEY_SIZE / 8 ]; random.nextBytes( iv ); return iv; } |
Crittografia Asimmetrica
La crittografia asimmetrica, conosciuta anche come crittografia a chiave pubblica/privata o solamente crittografia a chiave pubblica, è un metodo di criptazione in cui si utilizza una coppia di chiavi diverse, una pubblica ed una privata, in grado di funzionare solo l’una con l’altra. La chiave privata è utilizzabile solo dal legittimo possessore, mentre quella pubblica è resa nota a chiunque. In pratica, ciò che viene cifrato da una chiave può essere decifrato solo con l’altra chiave della coppia e viceversa.
Sebbene più sicura, in quanto elimina il problema della condivisione della chiave tra mittente e destinatario, risulta computazionalmente più onerosa rispetto ad un algoritmo a chiave simmetrica. Per questo motivo è generalmente utilizzato per cifrare solamente pochi blocchi di dati, come ad esempio la una chiave simmetrica. E’ poi la chiave simmetrica(detta anche chiave di sessione) che viene utilizzata per la criptazione del testo, come mostrato nell’immagine seguente.
Il codice java per le operazione di cifratura e decifratura è mostrato di seguito.
1 2 3 4 5 6 7 |
public static byte [] encrypt( PublicKey key, byte[] plaintext ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance( "RSA/ECB/NoPadding" ); cipher.init( Cipher.ENCRYPT_MODE, key ); return cipher.doFinal( plaintext ); } |
1 2 3 4 5 6 7 |
public static byte [] decrypt( PrivateKey key, byte[] ciphertext ) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException { Cipher cipher = Cipher.getInstance( key.getAlgorithm() + "/ECB/NoPadding" ); cipher.init( Cipher.DECRYPT_MODE, key ); return cipher.doFinal( ciphertext ); } |
Si noti che, oltre alla tipologia della chiave, nei due metodo non è presente il vettore di inizializzazione.
Base 64 Encoding
Tutti i metodi di cifratura e decifratura che abbiamo visto ricevono in input e restituiscono in output un array di byte. Per poter persistere o trasferire tale array attraverso un media che tratta dati testuali è necessario convertirlo in Base 64. Questo per garantire che il dato rimanga intatto durante il trasporto. Il codice per eseguire il coding e l’encoding in Base 64 è il seguente:
1 2 3 4 5 6 7 8 9 10 |
private static final BASE64Encoder encoder = new BASE64Encoder(); private static final BASE64Decoder decoder = new BASE64Decoder(); public static String base64Encode( byte[] array ) { return encoder.encode( array ); } public static byte[] base64Decode( String buffer ) throws Exception { return decoder.decodeBuffer( buffer ); } |
Trasmissione del Messaggio
In conclusione inserendo i metodi descritti sopra in una classe CryptoHelper
le operazioni di criptazione e decriptazione avvengono nel modo seguente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
public static void main(String[] args) throws Exception { String msg = "Questo è il testo da proteggere"; // Cifratura con chiave simmetrica Key key = CryptoHelper.generateSymmetricKey(); byte[] iv = CryptoHelper.generateInitVector(); String encripted = CryptoHelper.base64Encode( CryptoHelper.encrypt( key, iv, msg.getBytes() ) ); System.out.println( encripted ); // ----------------------- // Invio messaggio cifrato // ----------------------- // Decifratura con chiave simmetrica String decripted = new String( CryptoHelper.decrypt( key, iv, CryptoHelper.base64Decode( encripted ) ) ); System.out.println( decripted ); // Cifratura con chiave simmetrica KeyPair keyPair = CryptoHelper.generateAsymmetricKey(); encripted = CryptoHelper.base64Encode( CryptoHelper.encrypt( keyPair.getPublic(), iv, msg.getBytes() ) ); System.out.println( encripted ); // ----------------------- // Invio messaggio cifrato // ----------------------- // Decifratura con chiave asimmetrica decripted = new String( CryptoHelper.decrypt( keyPair.getPrivate(), iv, CryptoHelper.base64Decode( encripted ) ) ); System.out.println( decripted ); } |
1 2 3 4 5 6 |
A410wtwGxNwNLeJtPIWwlu2PMzOpLg7kMzgzLzbkMU1kgb1NBu/Ngj2aLMhlQX4m Questo è il testo da proteggere Zoa3u/ndsN9NZhBolBby8IbKM5IKZeB4mWCl3Ltoja9II4WzRhpXipoZU5jBCBEDMnlRArLJg2in 0c8/HeaWRg93aDQvNdOwq5BUNFVaAilnIkmFLBdq6baLk9OGXDebWBbMDktI0fKHaqJAsZKrwEEP +AfJmwIt9umc8+nyPGw= Questo è il testo da proteggere |
Codice Sorgente
La classe è disponibile qui CryptoHelper.java