2015-07-06 54 views
5

Mam aplikację, która musi przechowywać pewne tajne hasła w pliku konfiguracyjnym, takim jak bazy danych i hasła ftp/szczegóły. Rozejrzałem się i znalazłem wiele rozwiązań szyfrowania/odszyfrowywania za pomocą AES, ale nie mogę się domyślić, jak to zrobić bez zmiany klucza. Oznacza to, że mogę szyfrować i odszyfrowywać (używając tego samego klucza SecretKey), ale w celu zachowania trwałości podczas restartów itp. Nie mogę sprawić, że SecretKey pozostanie taki sam. Poniższy przykład pokazuje moje metody pracy:Szyfrowanie i deszyfrowanie AES Java z statycznym sekretem

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

Jak dotąd tak dobrze. Jednak jeśli uruchomię go raz, mogę uzyskać wartość "2Vhht/L80UlQ184S3rlAWw ==" jako mój sekret, następnym razem będzie to "MeC4zCf9S5wUUKAu8rvpCQ ==", więc prawdopodobnie klucz się zmienia. Zakładam, że stosuję jakąś logikę przeciw-intuicyjną do problemu i doceniłbym, gdyby ktoś mógł rzucić trochę światła na a) co robię źle, lub b) rozwiązanie, które pozwoliłoby mi przechowywać zaszyfrowane informacje o haśle i można je odzyskać za pomocą dostarczonych informacji.

Moje metody są następujące:

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); 
} 

Dzięki za pomoc!

Odpowiedz

2

OK, wygląda na to, że znalazłem odpowiedź na moje pytanie. Zebrałem moje informacje z this Stackoverflow post. Z tego co rozumiem, IV (wektor inicjalizacyjny) służy do dodawania entropii do procesu szyfrowania. Za każdym razem, gdy tworzysz nowy szyfr, Java tworzy nieco inny IV. Istnieją zatem dwa rozwiązania:

  1. użytkownika stałą IV lub
  2. Przechowywać IV wraz z zaszyfrowanych danych.

Z tego, co przeczytałem, opcja 1 nie jest bardzo dobrą praktyką; więc opcja 2 jest. Rozumiem, że powinno być możliwe dołączenie IV do zaszyfrowanego ciągu (ponieważ sekret jest nadal wymagany), a zatem IV może zostać zrekonstruowany, gdy przychodzi czas na odszyfrowanie.

Oto kompletne rozwiązanie prawie. Nadal dostaję błędy wyściółki podczas odszyfrowywania (zobacz mój komentarz). Nie mam teraz czasu, aby na to wydać, więc jako środek tymczasowy od razu próbuję odszyfrować zaszyfrowany ciąg i próbować dalej (iterować), dopóki nie zadziała. Wygląda na to, że ma około 50% wskaźnika trafień + Nie szyfruję wystarczająco często, aby mogło to być problemem wydajności. Byłoby miło, gdyby ktoś mógł zaproponować poprawkę (tylko ze względu na kompletność).

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

To jest dokładnie to, co mam w trzecim interfejsie integracyjnym. Pole danych wejściowych XML Base64toBytes, bajty 0..15 to wartość IV, odszyfruj bajty 16..n za pomocą tajnego klucza. – Whome

+2

W przypadku AES/CBC/PKCS7Padding większość osób wybiera opcję IV do tekstu zaszyfrowanego. Odszyfrując pierwsze 16 bajtów z tekstu zaszyfrowanego, są to IV, zainicjuj szyfr, a następnie wykonaj z resztą. Ma kilka fajnych właściwości, w szczególności te implementacje, które nie są zgodne z tą konwencją, ale zaczynają się od 0 IV i odrzucają, że pierwszy blok nadal może odszyfrować tekst. – wallenborn

+0

Dzięki za potwierdzenie! – Aaron

0

Użyj statycznego wektora inicjującego, np. zero IV:

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

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

Skoro przechowywania haseł prawdopodobnie chcesz użyć losowej IV i/lub losowe sól i przechowywać je z tekstem szyfrującej więc te same hasła nie są szyfrowane do tego samego zaszyfrowanego tekstu.

+1

Uzgodnione za pomocą statycznego iv zrobią lewę, ale także zgodzę się, że to nie jest dobra praktyka, chyba że zewnętrznie solenie tekst przed przechowywaniem to. Przechowywanie IV wraz z zaszyfrowaną wiadomością wydaje się być najlepszym rozwiązaniem. – Aaron