Università degli Studi di Bologna IIª Facoltà di Ingegneria - Cesena Anno Accademico 2010/2011 OUTLINE Java Security Extensions(J2SE) Prof. Enrico Denti Dipartimento di Elettronica, Informatica e Sistemistica enrico.denti@unibo.it Telefono 051.20.93015 2 JAVA SECURITY EXTENSIONS (JSE) PIATTAFORMA JAVA PER LA SICUREZZA Situazione fino al JDK 1.3 Architettura per la sicurezza (JCA) parte del JDK Estensioni per la sicurezza (JSE) fornite a parte (soprattutto per problemi di esportazione dagli USA) Java Web Start fornito a parte Java 2 Security Platform (J2SE 1.4) Java Authentication and Authorization Service (JAAS) Java 2 Security Architecture Novità JDK 1.4 Situazione dal JDK 1.4 (e 1.5) Estensioni JSE fornite insieme al JDK standard Java Web Start fornito insieme al JDK standard Altri miglioramenti: adattamento della gestione dei permessi al modello JAAS gestione dinamica delle politiche di sicurezza miglioramento della sicurezza nella gestione delle applet Java Secure Socket Extension (JSSE) Java Cryptography Extension (JCE) package java.security Java Cryptography Architecture (JCA) Java Generic Security Service (JGSS) Java Certification Path (CertPath) JAVA CRYPTOGRAPHY ARCHITECTURE (JCA) & JAVA CRYPTOGRAPHY EXTENSION (JCE) Il supporto crittografico in Java è articolato su due livelli: la Java Cryptography Architecture (JCA), che definisce la cornice generale per la crittografia: definisce le classi astratte per la gran parte delle funzionalità ma implementa solo alcune funzionalità specifiche in particolare il calcolo di impronte digitali e firme digitali di messaggi la Java Cryptography Extension (JCE), che definisce le API complete e implementa tutte le altre funzionalità in particolare, quelle di cifratura e decifratura di messaggi con algoritmi a piacere JAVA CRYPTOGRAPHY ARCHITECTURE (JCA) Due principi indipendenza dall implementazione e interoperabilità estendibilità e indipendenza degli algoritmi che si traducono in due tipologie di classi: classi astratte "engine" che dichiarano le funzionalità di un certo algoritmo crittografico classi concrete "provider" che implementano un insieme di funzionalità entro un Cryptographic Service Provider (CSP) Molteplicità di provider Più provider possono coesistere ed interoperare il provider di default si chiamasun e fa parte del JRE nuovi provider possono essere installati anche dinamicamente un'applicazione può sia richiedere una implementazione qualsiasi di un algoritmo, sia richiedere quella di un dato provider
PRINCIPALI CLASSI "ENGINE" Packagejava.security Key definisce le funzionalità condivise da chiavi opache KeySpec definisce una chiave di tipo trasparente KeyFactory rende una chiave opaca o trasparente KeyPairGenerator genera una coppia di chiavi asimmetriche AlgorithmParameters gestisce i parametri di un algoritmo AlgorithmParameterGenerator genera i parametri di un algoritmo MessageDigest calcola l hash (message digest) di un messaggio SecureRandom genera numeri casuali o pseudo-casuali Signature appone o verifica la firma digitale di un messaggio CertificateFactory crea e revoca certificati di chiavi pubbliche KeyStore gestisce il database (keystore) di chiavi e certificati NB: opaco e trasparente non sono sinonimi di cifrato e in chiaro. opaco = non si può accedere a ciò che costituisce la chiave direttamente, ma solo tramite i metodi dell'interfaccia Key; trasparente = non opaco. CHIAVI: INTERFACCE E CLASSI KeyFactory (JCA) + SecretKeyFactory (JCE) Key INTERFACCE DERIVATE DHPrivateKey DHPublicKey DSAPrivateKey DSAPublicKey SecretKey PBEKey PrivateKey PublicKey RSAMultiPrimePrivateCrtKey RSAPrivateCrtKey RSAPrivateKey RSAPublicKey PublicKey generatepublic (KeySpec keyspec) PrivateKey generateprivate(keyspec keyspec) SecreteKey generatesecret (KeySpec keyspec) KeySpec CLASSI IMPLEMENTATIVE DHPrivateKeySpec DHPublicKeySpec DSAPrivateKeySpec DSAPublicKeySpec SecretKeySpec PBEKeySpec RSAPrivateKeySpec RSAPublicKeySpec EncodedKeySpec DESedeKeySpec DESKeySpec KeySpec getkeyspec(key key, Class keyspec) COSA IMPLEMENTA: gli algoritmi di message digest MD5 e SHA-1 l algoritmo di firma digitale DSA (Digital Signature Algorithm) e i relativi enti di supporto specifici per DSA: KeyFactory e KeyPairGenerator AlgorithmParameters e AlgorithmParameterGenerator e inoltre: IL PROVIDERsun l algoritmo di generazione casuale SHA1PRNG (algoritmo proprietario, raccomandazione standard IEEE P1363) unacertificatefactory per certificati X.509 e per Certificate Revocation List una implementazione del KeyStore proprietario JKS Oltre al provider sun ne vengono forniti (JDK 1.4) altri due che estendono l'insieme delle funzionalità disponibili: rsajca SunJCE ALTRI PROVIDER PREDEFINITI in particolare verso l algoritmo di firma digitale RSA solo per firma e verifica svariati algoritmi di cifratura tra cui: DES, TripleDES, Blowfish (e relativi generatori di chiavi) Diffie-Hellman per negoziazione di chiavi INSTALLAZIONE DI NUOVI PROVIDER Se le funzionalità offerte dal provider Sun non bastano, si possono installare altri provider, sia staticamente sia dinamicamente. 1. INSTALLAZIONE STATICA porre l archivio JAR del nuovo provider nella cartella relativa alle estensioni del JRE, $JAVA_HOME/jre/lib/ext aggiungere al file delle specifiche di sicurezza java.security (contenuto in $JAVA_HOME\jre\lib\security) la riga security.provider.n =ProviderName dove N rappresenta la priorità di quel provider; la macchina virtuale Java sceglie i provider seguendo tale ordine. 2. INSTALLAZIONE DINAMICA non ci sono passi preliminari da compiere. INSTALLAZIONE STATICA: ESEMPIO I provider predefiniti sono elencati nelle prime 5 righe del file $JAVA_HOME/jre/lib/security/java.security security.provider.1=sun.security.provider.sun security.provider.2=com.sun.net.ssl.internal.ssl.provider security.provider.3=com.sun.rsajca.provider security.provider.4=com.sun.crypto.provider.sunjce security.provider.5=sun.security.jgss.sunprovider security.provider.6=org.bouncycastle.jce.provider.bouncycastleprovider In questo esempio, la sesta riga installa staticamente il provider BouncyCastle (www.bouncycastle.com), il cui file JAR dev'essere posto nella directory $JAVA_HOME/jre/lib/ext
INSTALLAZIONE DINAMICA: ESEMPIO L'installazione dinamica si svolge da programma Java, utilizzando le apposite API della classesecurity: static int addprovider(provider provider) static Provider getprovider(string name) static Provider[] getproviders() static Set getalgorithms(string servicename) static int insertproviderat(provider provider, int position) Ad esempio, per installare il provider IAIK (scaricabile da http://jce.iaik.tugraz.at/products/01_jce/index.php): import javax.crypto.*; import iaik.security.provider.*; IAIK provider = new IAIK(); Security.addProvider(provider); LE JAVA SECURITY EXTENSIONS Le estensioni JCE e JSSE implementano due provider che estendono le funzionalità del provider di base: SunJCE SunJSSE crittografia a livello locale crittografia a livello di rete Altri package forniscono funzionalità di sicurezza aggiuntive, complementari alla JCA: JAAS servizi di autenticazione, autorizzazione e amministrazione CertPath gestione di catene di certificati (certification paths) JGSS meccanismi di comunicazione generici e specifici (Kerberos v5) JCA & JSE: SCHEMA D'USO Tutte le classi engine della JCA e della JSE si usano secondo il pattern "Factory", ossia: non si istanziano oggetti direttamente, tramite new si usa invece un metodo statico "di fabbrica", che nasconde e incapsula la fase di creazione esplicita: getistance(string nomeistanza) Questo metodo può così restituire formalmente delle interfacce, separando completamente l'uso (espresso dalle interfacce) dagli aspetti implementativi (legati alle classi utilizzate internamente). ESEMPI: KeyGenerator kg = KeyGenerator.getInstance("TripleDES"); KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); Signature sig = Signature.getInstance("MD5WithRSA"); CertificateFactory cf = CertificateFactory.getInstance("X.509"); JCE: CARATTERISTICHE La Java Cryptography Extension (JCE) fornisce una implementazione completa delle funzionalità di cifratura e decifratura dichiarate dalla JCA Supporta cifrari simmetrici (a blocchi e a flusso), cifrari asimmetrici e cifrari con password, unitamente ai meccanismi di MAC (Message Authentication Code) e generazione / negoziazione di chiavi; tutti gli algoritmi sono applicabili su dati, stream di I/O e oggetti serializzabili. Il package principalejavax.crypto include fra le altre: Cipher (cifratura e decifratura di dati con uno specifico algoritmo) CipherInputStream /CipherOutputStream (canale sicuro, ossia un Cipher + un InputStream o un OutputStream) KeyGenerator (generazione di chiavi per algoritmi simmetrici e per lo scambio Diffie-Hellmann DH) Mac (Message Authentication Code MAC) MODALITÀ e PADDING (RIEMPIMENTI) La MODALITÀ definisce come il cifrario debba applicare l'algoritmo di cifratura: può essere a blocchi o a flusso. Casi tipici sono: ECB [Elettronic Code Book]: uno stesso blocco di testo, anche se ripetuto, dà sempre luogo allo stesso blocco di testo cifrato CBC [Cipher Block Chaining]: ogni blocco cifrato dipende sia dal relativo blocco in chiaro, sia da tutti i blocchi precedenti, sia da un opportuno vettore di inizializzazione. Il PADDING stabilisce come completare un blocco che non raggiunge la dimensione fissa prestabilita. PKCS#5 [Public Key Criptography Standard n 5] è lo schema di riempimento più usato per la cifratura simmetrica: ai byte che mancano per riempire un blocco si assegna un valore pari al numero di byte mancanti. h e l l o 3 3 3 t e s t 4 4 4 4 8 8 8 8 8 8 8 8 c i p h e r 2 2 ESEMPIO CON 3DES: inizializzazione import java.security.*; import javax.crypto.*; Istanzia un generatore di public class TripleDES { chiavi di tipo TripleDES public static void main (String[] args) { Con il provider BouncyCastle si String text = "Hello world!"; userebbe la stringa "DESede" if (args.length == 1) text = args[0]; KeyGenerator keygen = KeyGenerator.getInstance("TripleDES"); keygen.init(168); Key key = keygen.generatekey(); Cipher cipher = Cipher.getInstance("TripleDES/ECB/PKCS5Padding"); Inizializza il generatore con la lunghezza in bit della chiave Per TripleDES, è sempre 168 Generazione della chiave Crea il cifrario, indicando: Tipo di chiave [TripleDES] Modalità [ECB] Padding [PKCS5Padding]
ESEMPIO CON 3DES: cifratura. cipher.init(cipher.encrypt_mode, key); byte[] plaintext = text.getbytes("utf8"); System.out.println("\nPlain text: "); for (int i=0;i<plaintext.length;i++) System.out.print(plaintext[i]+" "); Inizializza il cifrario per cifrare Converte una stringa in un array di byte specificando la codifica. ESEMPIO CON 3DES: decifratura cipher.init(cipher.decrypt_mode, key); byte[] decryptedtext = cipher.dofinal(ciphertext); String output = new String(decryptedText,"UTF8"); System.out.println("\nDecrypted Text: "+output);. Inizializza il cifrario per decifrare (usiamo la stessa chiave) Decifra i dati byte[] ciphertext = cipher.dofinal(plaintext); System.out.println("\nCipher text: "); for(int i=0;i<ciphertext.length;i++) System.out.print(ciphertext[i]+" ");. Cifra i dati COMPILAZIONE ED ESECUZIONE: C:> javac TripleDES.java C:> java TripleDES testoinchiaro Supponiamo: "Hello world!" NB: prima di cifrare chiamandodofinal, si possono eventualmente aggiungere altri dati invocando il metodoupdate(byte[]); anche in tal caso occorre ricordarsi di specificare la codifica. Plain text: 72 101 108 108 111 32 119 111 114 108 100 33 Cipher text: 23 28-14 60-14 45-6 23 12 53 71 58 123 90-108 -57 Decrypted Text: Hello world! ESEMPIO CON 3DES: COMPLETAMENTO Per funzionare, il codice precedente dev'essere completato con la gestione delle eccezioni: catch (NoSuchAlgorithmException e1) { System.out.println("Algoritmo non supportato"); catch (InvalidAlgorithmParameterException e2) { System.out.println("Parametro non valido"); catch (NoSuchProviderException e2) { System.out.println("Algoritmo non supportato dal provider"); catch (NoSuchPaddingException e3) { System.out.println("Padding non supportato"); catch (BadPaddingException e4) { System.out.println("Padding non riuscito"); catch (InvalidKeyException e5) { System.out.println("Chiave non valida"); catch (IllegalBlockSizeException e6) { System.out.println("Dimensione blocco non corretta"); catch (UnsupportedEncodingException e7) { System.out.println("Codifica non supportata"); CAMBIARE ALGORITMO: ESEMPI Cambiare algoritmo significa solo creare un diverso cifrario. Ad esempio, volendo utilizzare Blowfish anziché 3DES: KeyGenerator keygen = KeyGenerator.getInstance("Blowfish"); keygen.init(128); Cipher c = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); Analogamente per l'algoritmo Rijndael, che opera in modalità CBC: Cipher c = Cipher.getInstance("Rijndael/CBC/PKCS5Padding"); byte[] initvect = new byte[16]; 16 byte casuali necessari SecureRandom random = new SecureRandom(); per la generazione del random.nextbytes(initvect); seme IvParameterSpec spec = new IvParameterSpec(initVect); cipher.init(cipher.encrypt_mode, key, spec); Inizializza il cifrario passando anche il semespec CIFRATURA CON PASSWORD (PBE) Password-Based Encryption (PBE): caratteristiche password di un utente medio = circa 6 caratteri 48 bit contro le chiavi di 448 bit di 3DES e Blowfish password tipiche = parole con un qualche significato spazio delle chiavi ancora più limitato soggette ad attacchi con dizionario Due difese contemporanee: ing = aggiunta di un insieme di bit casuali alla password in modo ampliare lo spazio delle chiavi Conteggi di ripetizione = chiave del cifrario PBE separata rispetto alla password: la chiave PBE è ottenuta ripetendo molte volte un'operazione sulla password base. in chiaro Password CIFRARIO CON PASSWORD + SALT CIFRARIO PBE DATI CIFRATI cifrato DATI CIFRATI cifrato cifrato cifrato Password CIFRARIO PBE in chiaro
CIFRATURA CON PBE: ESEMPIO import java.security.*; import javax.crypto.*; import java.util.*; public class ProvaPBE { private final int ITERATIONS = 1000; import java.security.spec.*; import javax.crypto.spec.*; public static String encrypt(char[] password, String plaintext) { Random random = new Random(); byte[] salt = new byte[8]; random.nextbytes(salt); PBEKeySpec keyspec = new PBEKeySpec(password); Crea la chiave PBE SecretKeyFactory keyfactory = data la password SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyfactory.generatesecret(keyspec); PBEParameterSpec paramspec = new PBEParameterSpec(salt, ITERATIONS); Incapsula salt e numero di iterazioni Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); cipher.init(cipher.encrypt_mode, key, paramspec); byte[] ciphertext = cipher.dofinal(plaintext.getbytes("utf8")); return new String(salt) + new String(ciphertext); DECIFRATURA CON PBE: ESEMPIO public static String decrypt(char[] password, String input) { byte[] salt = input.substring(0,8).getbytes("utf8"); byte[] ciphertext = input.substring( 8 ).getbytes("utf8"); Separa il salt dal testo cifrato e li converte in array di byte PBEKeySpec keyspec = new PBEKeySpec(password); SecretKeyFactory keyfactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyfactory.generatesecret(keyspec); PBEParameterSpec paramspec = Tutto come prima new PBEParameterSpec(salt, ITERATIONS); Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); cipher.init(cipher.decrypt_mode, key, paramspec); byte[] plaintextarray = cipher.dofinal(ciphertext); return new String(plaintextArray ); a parte ovviamente il DECRYPT MODE e lo scambio di ruoli fra testo in chiaro e testo cifrato MEMORIZZAZIONE SICURA DELLA CHIAVE PROBLEMA: DOVE memorizzare la chiave? su floppy disc o smart card? scomodo (servono mezzi fisici esterni) su disco fisso? comodo.. ma la sicurezza..? è opportuno cifrare la chiave, ad esempio con PBE non è male poi proteggere ulteriormente la chiave, ad esempio impostando i permessi di accesso al file Cifratura: byte[] keybytes = key.getencoded(); cipher.init(cipher.encrypt_mode, password, paramspec); byte[] encryptedkeybytes = cipher.dofinal(keybytes); Decifratura: cipher.init(cipher.decrypt_mode, password, paramspec); byte[] keybytes = cipher.dofinal(encryptedkeybytes); SecretKeySpec key = new SecretKeySpec(keyBytes, "Blowfish"); Alcuni provider forniscono un mezzo più comodo per cifrare la chiave, evitando la conversione da byte[] akey Si incapsula una chiave segreta configurando un cifrario PBE in WRAP_MODE (anzichéencrypt_mode) e poi usandowrap anzichédofinal. Analogamente si recupera la chiave operando sul cifrario in UNWRAP_MODE (anzichédecrypt_mode) e poi usandounwrap anzichédofinal. Cifratura: INCAPSULAMENTO ED ESTRAZIONE DELLA CHIAVE cipher.init(cipher.wrap_mode, password, paramspec); byte[] encryptedkeybytes = cipher.wrap(key); Decifratura: cipher.init(cipher.unwrap_mode, password, paramspec); Key key = cipher.unwrap(encryptedkeybytes, "Blowfish", Cipher.SECRET_KEY); CipherInputStream ecipheroutputstream realizzano il concetto di canale sicuro combinando automaticamente uninputstream o unoutputstream con uncipher incaricato di gestire cifratura e decifratura. Creazione stream cifrati: STREAM CIFRATI CipherOuputStream cipheroutput = new CipherOutputStream(new FileOutputStream(cipherFileName), cipher); CipherInputStream cipherinput = new CipherInputStream( new FileInputStream( cipherfilename), cipher); Uso (trasparente) di stream cifrati: int ch = 0; Un Inputstream qualsiasi Ogni carattere scritto viene automaticamente cifrato while ((ch = input.read())!=-1) { cipheroutput.write(ch); cipheroutput.close(); OGGETTI SIGILLATI Gli oggetti sigillati (SealedObject) sono oggetti cifrati che incapsulano il cifrario Sono utili per memorizzare e trasportare una versione cifrata di un oggetto (serializzabile) import java.io.*; import javax.crypto.*; import java.security.*; public class SealedObjectExample { public static void main (String[ ] args) { String secretmessage = "Ci vediamo domani alle 15"; KeyGenerator keygenerator = KeyGenerator.getInstance("Blowfish"); Key key = keygenerator.generatekey(); Cipher cipher = Cipher.getInstance("Blowfish/ECB/PKCS5Padding"); cipher.init(cipher.encrypt_mode, key); SealedObject so = new SealedObject(secretMessage, cipher); String decryptedmessage = (String) so.getobject(key); System.out.println("Messaggio decifrato: " + decryptedmessage);
CIFRATURA ASIMMETRICA FIRMA DIGITALE: GENERAZIONE e INVIO Algoritmo di impronta CIFRATURA ASIMMETRICA FIRMA DIGITALE: RICEZIONE e VERIFICA Algoritmo di impronta Messaggio da firmare Impronta del messaggio Messaggio ricevuto Impronta del messaggio Chiave privata del mittente Chiave pubblica del mittente UGUALI? Impronta del messaggio Lato Mittente Algoritmo di firma Firma digitale da inviare al destinatario insieme al messaggio Firma digitale ricevuta dal mittente Algoritmo di verifica della firma Lato Destinatario RSA vs DSA: CONFRONTO Firmare con RSA significa cifrare con la chiave privata decifrare con la pubblica Questa operazione non nasconde i dati (che sono decifrabili con la chiave pubblica) ma prova l identità del firmatario L'algoritmo DSA (Digital Signature Algorithm) è analogo a RSA per la firma, ma non può essere usato per cifrare DSA è più veloce a generare le firme, RSA a verificarle poiché una firma viene convalidata più spesso di quanto venga generata, RSA è solitamente più veloce APPOSIZIONE DI UNA FIRMA DIGITALE import java.security.signature; import java.security.signatureexception; KeyPairGenerator keypairgen = KeyPairGenerator.getInstance("RSA"); keypairgen.initialize(1024); KeyPair keypair = keypairgen.genkeypair(); byte[] msg = "Non esistono più le mezze stagioni".getbytes("utf8"); Signature sig = Signature.getInstance("MD5WithRSA"); sig.initsign(keypair.getprivate()); sig.update(msg); byte[] signaturebytes = sig.sign(); Per firmare occorre un oggetto Signature PROCEDURA DI FIRMA: si inizializza l'oggetto Signature con la chiave PRIVATA del mittente gli si passano mediante update i dati da firmare si calcola la firma invocando sign che restituisce i byte costituenti la firma E CORRISPONDENTE VERIFICA import java.security.signature; import java.security.signatureexception; sig.initverify( keypair.getpublic() ); sig.update(msg); boolean verified = false; try { verified = sig.verify(signaturebytes); catch (SignatureException se) { System.out.println("Formato non valido"); Anche per verificare la fiorma occorre un oggetto Signature PROCEDURA DI VERIFICA: si inizializza l'oggetto Signature con la chiave PUBBLICA del mittente gli si passano mediante update i dati da VERIFICARE si verifica la firma invocandoverify che restituisce un boolean CIFRATURA ASIMMETRICA vs. CIFRATURA SIMMETRICA DIFFERENZE RISPETTO AL CASO SIMMETRICO MODALITÀ: nei cifrari asimmetrici, si usa quasi sempre ECB PADDING: nei cifrari asimmetrici non si usa più PKCS#5, ma bensì (con RSA) PKCS#1 e OAEP (Optimal Asymmetric Encryption Padding) COSA SI CIFRA: tipicamente solo la (corta) chiave di sessione da usarsi da lì in poi con un algoritmo simmetrico. COSA OCCORRE solo per firmare digitalmente un messaggio e verificarlo: basta il provider JCE standard per cifratura e decifratura: un provider che supporti tutto RSA BouncyCastle o IAIK
CIFRATURA ASIMMETRICA con RSA import javax.crypto.*; import java.security.*; KeyPairGenerator keypairgen = KeyPairGenerator.getInstance("RSA"); keypairgen.initialize(1024); KeyPair keypair = keypairgen.genkeypair(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(cipher.encrypt_mode, keypair.getpublic() ); Tipicamente il messaggio da cifrare sarà la chiave di sessione byte[] messagebytes = "il mio messaggio".getbytes("utf8"); byte[] ciphertext = cipher.dofinal(messagebytes); cipher.init(cipher.decrypt_mode, keypair.getprivate() ); byte[] decryptedmessagebytes = cipher.dofinal(ciphertext); System.out.println( new String(decryptedMessageBytes,"UTF8") ); CIFRATURA ASIMMETRICA con RSA DI UNA CHIAVE (simmetrica) DI SESSIONE KeyPairGenerator keypairgen = KeyPairGenerator.getInstance("RSA"); keypairgen.initialize(1024); KeyPair keypair = keypairgen.genkeypair(); Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding"); cipher.init(cipher.encrypt_mode, keypair.getpublic() ); KeyGenerator sessionkeygen = KeyGenerator.getInstance("Blowfish"); sessionkeygen.init(128); Key blowfishkey = sessionkeygen.generatekey(); byte[] blowfishkeybytes = blowfishkey.getencoded(); byte[] ciphertext = cipher.dofinal(blowfishkeybytes); cipher.init(cipher.decrypt_mode, keypair.getprivate() ); byte[] decryptedkeybytes = cipher.dofinal(ciphertext); SecretKey reconstructedblowfishkey = new SecretKeySpec(decryptedKeyBytes,"Blowfish");