2015-07-06 47 views
5

Ho un'applicazione che deve memorizzare alcune password segrete in un file di configurazione come database e password/dettagli ftp. Mi sono guardato intorno e ho trovato molte soluzioni di crittografia/decrittografia usando AES, ma non riesco a capire come farlo funzionare senza cambiare la chiave. Ciò significa che posso crittografare e decifrare (usando la stessa SecretKey), ma per mantenere la persistenza attraverso i riavvii ecc. Non riesco a far sembrare la SecretKey uguale. L'esempio seguente mostra i miei metodi operativi:Codifica e decrittografia Java AES con segreto statico

String secret = Encryptor.encrpytString("This is secret"); 
String test = Encryptor.decrpytString(secret); 
System.out.println(test); //This is secret is printed 

Fin qui tutto bene. Comunque se lo eseguo una volta potrei ottenere il valore di '2Vhht/L80UlQ184S3rlAWw ==' come mio segreto, la prossima volta che è 'MeC4zCf9S5wUUKAu8rvpCQ ==', presumibilmente la chiave sta cambiando. Suppongo di applicare una logica controintuitiva al problema e apprezzerei se qualcuno potesse far luce su a) cosa sto facendo male, o b) una soluzione che mi permettesse di memorizzare le informazioni della password crittografate e recuperabile con le informazioni fornite.

I miei metodi sono i seguenti:

private static final String salt = "SaltySalt"; 

private static byte [] ivBytes = null; 

private static byte[] getSaltBytes() throws Exception { 
    return salt.getBytes("UTF-8"); 
} 

private static char[] getMasterPassword() { 
    return "SuperSecretPassword".toCharArray(); 
} 

private static byte[] getIvBytes() throws Exception { 
    if (ivBytes == null) { 
     //I don't have the parameters, so I'll generate a dummy encryption to create them 
     encrpytString("test"); 
    } 
    return ivBytes; 
} 

public static String encrpytString (String input) throws Exception { 
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256); 
    SecretKey secretKey = factory.generateSecret(spec); 
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); 
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
    cipher.init(Cipher.ENCRYPT_MODE, secret); 
    ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); 
    byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8")); 
    return DatatypeConverter.printBase64Binary(encryptedTextBytes);   
} 

public static String decrpytString (String input) throws Exception { 
    byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(input); 
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256); 
    SecretKey secretKey = factory.generateSecret(spec); 
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); 
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(getIvBytes())); 
    byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes); 
    return new String(decryptedTextBytes); 
} 

Grazie per l'aiuto!

risposta

2

OK, sembra che ho trovato la risposta alla mia domanda. Ho ottenuto le mie informazioni da this Stackoverflow post. Da quello che ho capito, l'IV (vettore di inizializzazione) viene utilizzato per aggiungere entropia nel processo di crittografia. Ogni volta che crei un nuovo codice, Java crea un IV leggermente diverso. Ci sono quindi due soluzioni:

  1. utente fisso IV, o
  2. Conservare il IV insieme ai dati crittografati.

Da quello che ho letto, l'opzione 1 non è una buona pratica; quindi l'opzione 2 lo è. Capisco che dovrebbe essere possibile semplicemente aggiungere la IV alla stringa crittografata (poiché il segreto è ancora necessario) e quindi la IV può essere ricostruita quando arriva il momento di decifrare.

Ecco la soluzione completa quasi. Sto ancora ricevendo alcuni errori di padding sulla decrittazione (vedi il mio commento). Non ho tempo da perdere ora, quindi come misura temporanea provo immediatamente a decodificare una stringa crittografata e continuo a provare (iterando) finché non funziona. Sembra avere una percentuale di successo del 50% + Non sto crittografando abbastanza spesso per essere un problema di prestazioni. Sarebbe bello se qualcuno potesse suggerire una soluzione (solo per completezza).

private static final String salt = "SaltySalt"; 

private static final int IV_LENGTH = 16; 

private static byte[] getSaltBytes() throws Exception { 
    return salt.getBytes("UTF-8"); 
} 

private static char[] getMasterPassword() { 
    return "SuperSecretPassword".toCharArray(); 
} 

public static String encrpytString (String input) throws Exception { 
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256); 
    SecretKey secretKey = factory.generateSecret(spec); 
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); 
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
    cipher.init(Cipher.ENCRYPT_MODE, secret); 
    byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); 
    byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8")); 
    byte[] finalByteArray = new byte[ivBytes.length + encryptedTextBytes.length]; 
    System.arraycopy(ivBytes, 0, finalByteArray, 0, ivBytes.length); 
    System.arraycopy(encryptedTextBytes, 0, finalByteArray, ivBytes.length, encryptedTextBytes.length); 
    return DatatypeConverter.printBase64Binary(finalByteArray);   
} 

public static String decrpytString (String input) throws Exception { 
    if (input.length() <= IV_LENGTH) { 
     throw new Exception("The input string is not long enough to contain the initialisation bytes and data."); 
    } 
    byte[] byteArray = DatatypeConverter.parseBase64Binary(input); 
    byte[] ivBytes = new byte[IV_LENGTH]; 
    System.arraycopy(byteArray, 0, ivBytes, 0, 16); 
    byte[] encryptedTextBytes = new byte[byteArray.length - ivBytes.length]; 
    System.arraycopy(byteArray, IV_LENGTH, encryptedTextBytes, 0, encryptedTextBytes.length); 
    SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); 
    PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256); 
    SecretKey secretKey = factory.generateSecret(spec); 
    SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); 
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); 
    cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes)); 
    byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes); 
    return new String(decryptedTextBytes); 
} 
+1

Questo è esattamente quello che ho in un'interfaccia di integrazione di terze parti. Campo dati di input XML Base64toBytes, byte 0..15 sono valore IV, decrittografano byte 16..n con una chiave segreta. – Whome

+2

Per AES/CBC/PKCS7Padding, la maggior parte delle persone sceglie anteporre IV al testo cifrato. Quindi, su decifratura, taglia i primi 16 byte dal testo cifrato, sono IV, inizia il cifrario e poi doFinal con il resto. Questo ha alcune proprietà interessanti, in particolare le implementazioni che non seguono questa convenzione, ma iniziano solo con un IV e scartano il primo blocco ancora in grado di decifrare il testo. – wallenborn

+0

Grazie per la conferma ragazzi! – Aaron

0

Utilizzare un vettore di inizializzazione statico, ad es. uno zero IV:

cipher.init(Cipher.ENCRYPT_MODE, secret, new IvParameterSpec(new byte[16])); 

cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(new byte[16])); 

Dal momento che stai memorizzare le password che probabilmente desidera utilizzare un IV casuale e/o sale casuale e memorizzarli con il testo cifrato in modo che le stesse password non crittografare allo stesso testo cifrato.

+1

Concordato utilizzando un iv statico farà il trucco, ma anche d'accordo che non è una buona pratica se non salendo esternamente il testo prima di memorizzarlo esso. Memorizzare la IV insieme al messaggio crittografato sembra essere il via. – Aaron