Zaszyfrować hasło w plikach konfiguracyjnych? [Zamknięte]

131

Mam program, który odczytuje informacje o serwerze z pliku konfiguracyjnego i chciałby zaszyfrować hasło w tej konfiguracji, które może zostać odczytane przez mój program i odszyfrowane.

Wymagania:

  • Zaszyfruj hasło w postaci zwykłego tekstu, które ma być zapisane w pliku
  • Odszyfruj zaszyfrowane hasło odczytane z pliku z mojego programu

Jakieś zalecenia, jak bym to zrobił? Myślałem o napisaniu własnego algorytmu, ale czuję, że byłby to strasznie niepewny.

Petey B.
źródło
Uwielbiam, jak wiele najlepszych pytań i odpowiedzi dotyczących stackoverflow to te, które według niego nie spełniają jego wytycznych (np. Pytania z prośbą o rekomendacje). lol ....
Vahid Pazirandeh

Odpowiedzi:

173

Prostym sposobem na to jest użycie szyfrowania opartego na hasłach w Javie. Pozwala to zaszyfrować i odszyfrować tekst za pomocą hasła.

Zasadniczo oznacza to zainicjowanie javax.crypto.Cipheralgorytmu "AES/CBC/PKCS5Padding"i uzyskanie klucza javax.crypto.SecretKeyFactoryz "PBKDF2WithHmacSHA512"algorytmu.

Oto przykład kodu (zaktualizowany, aby zastąpić mniej bezpieczny wariant oparty na MD5):

import java.io.IOException;
import java.io.UnsupportedEncodingException;
import java.security.AlgorithmParameters;
import java.security.GeneralSecurityException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import java.util.Base64;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

public class ProtectedConfigFile {

    public static void main(String[] args) throws Exception {
        String password = System.getProperty("password");
        if (password == null) {
            throw new IllegalArgumentException("Run with -Dpassword=<password>");
        }

        // The salt (probably) can be stored along with the encrypted data
        byte[] salt = new String("12345678").getBytes();

        // Decreasing this speeds down startup time and can be useful during testing, but it also makes it easier for brute force attackers
        int iterationCount = 40000;
        // Other values give me java.security.InvalidKeyException: Illegal key size or default parameters
        int keyLength = 128;
        SecretKeySpec key = createSecretKey(password.toCharArray(),
                salt, iterationCount, keyLength);

        String originalPassword = "secret";
        System.out.println("Original password: " + originalPassword);
        String encryptedPassword = encrypt(originalPassword, key);
        System.out.println("Encrypted password: " + encryptedPassword);
        String decryptedPassword = decrypt(encryptedPassword, key);
        System.out.println("Decrypted password: " + decryptedPassword);
    }

    private static SecretKeySpec createSecretKey(char[] password, byte[] salt, int iterationCount, int keyLength) throws NoSuchAlgorithmException, InvalidKeySpecException {
        SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA512");
        PBEKeySpec keySpec = new PBEKeySpec(password, salt, iterationCount, keyLength);
        SecretKey keyTmp = keyFactory.generateSecret(keySpec);
        return new SecretKeySpec(keyTmp.getEncoded(), "AES");
    }

    private static String encrypt(String property, SecretKeySpec key) throws GeneralSecurityException, UnsupportedEncodingException {
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.ENCRYPT_MODE, key);
        AlgorithmParameters parameters = pbeCipher.getParameters();
        IvParameterSpec ivParameterSpec = parameters.getParameterSpec(IvParameterSpec.class);
        byte[] cryptoText = pbeCipher.doFinal(property.getBytes("UTF-8"));
        byte[] iv = ivParameterSpec.getIV();
        return base64Encode(iv) + ":" + base64Encode(cryptoText);
    }

    private static String base64Encode(byte[] bytes) {
        return Base64.getEncoder().encodeToString(bytes);
    }

    private static String decrypt(String string, SecretKeySpec key) throws GeneralSecurityException, IOException {
        String iv = string.split(":")[0];
        String property = string.split(":")[1];
        Cipher pbeCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        pbeCipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(base64Decode(iv)));
        return new String(pbeCipher.doFinal(base64Decode(property)), "UTF-8");
    }

    private static byte[] base64Decode(String property) throws IOException {
        return Base64.getDecoder().decode(property);
    }
}

Pozostaje jeden problem: gdzie należy przechowywać hasło, którego używasz do szyfrowania haseł? Możesz przechowywać go w pliku źródłowym i zaciemniać, ale nie jest trudno go znaleźć ponownie. Alternatywnie możesz podać ją jako właściwość systemową podczas uruchamiania procesu Java ( -DpropertyProtectionPassword=...).

Ten sam problem pozostaje, jeśli używasz KeyStore, który również jest chroniony hasłem. Zasadniczo będziesz musiał gdzieś mieć jedno hasło główne, które jest dość trudne do zabezpieczenia.

Johannes Brodwall
źródło
3
Dzięki za przykładowy kod, tak właśnie skończyłem. Jeśli chodzi o hasło, które chroni hasła, które napotkałem na ten sam problem, na razie skorzystałem z metody zaciemniania tego, ale nie znalazłem jeszcze akceptowalnego rozwiązania, dziękuję za sugestie.
Petey B
8
„Alternatywnie możesz podać ją jako właściwość systemową podczas uruchamiania procesu Java (-DpropertyProtectionPassword = ...)”. Zauważ, że umożliwiłoby to wyodrębnienie hasła za pomocą "ps fax" w (GNU / Linux) / UNIX.
Ztyx
7
@Ben Powszechną praktyką jest kodowanie do Base64, aby umożliwić przechowywanie wynikowej wartości w pliku tekstowym lub kolumnie bazy danych opartej na ciągach lub podobnej.
RB.
4
@ V.7 nope. MD5 nie jest absolutnie bezpieczna dla haszowania haseł i nigdy nie została zaprojektowana do tego celu. Nigdy go do tego nie używaj. Obecnie Argon2 jest najlepszy. Zobacz owasp.org/index.php/Password_Storage_Cheat_Sheet i paragonie.com/blog/2016/02/how-safely-store-password-in-2016
Kimball Robinson
3
O wiele lepiej w ten sposób. Oczywiście bezpieczna losowa sól i liczba iteracji (konserwatywna, niska wartość) wynosząca 40K byłaby przyjemniejsza, ale przynajmniej wskazałeś te rzeczy w komentarzach, a PBKDF2 i AES / CBC to zdecydowane ulepszenia. Myślę, że to wspaniale, jak sobie z tym poradziłeś, aktualizując odpowiedź; Usunę ostrzeżenie. Zagłosowano na Twój komentarz, aby ludzie nie byli zaskoczeni znalezieniem zaktualizowanego kodu (przypuszczam, że mogą spojrzeć na zmiany, aby znaleźć stary kod). Dobrym pomysłem może być również wyczyszczenie starych komentarzy.
Maarten Bodewes
20

Tak, zdecydowanie nie pisz własnego algorytmu. Java ma wiele interfejsów API kryptografii.

Jeśli system operacyjny, na którym instalujesz, ma magazyn kluczy, możesz go użyć do przechowywania kluczy kryptograficznych, które będą potrzebne do zaszyfrowania i odszyfrowania poufnych danych w konfiguracji lub innych plikach.

JeeBee
źródło
4
+1 za korzystanie z KeyStore! To nic innego jak zaciemnienie, jeśli przechowujesz klucz w pliku Jar.
dziewięciostronne
2
Jeśli wszystko, czego potrzeba, to nie przechowywać hasła w postaci zwykłego tekstu, to magazyny kluczy są przesadzone.
Thorbjørn Ravn Andersen
20

Sprawdź jasypt , czyli bibliotekę oferującą podstawowe możliwości szyfrowania przy minimalnym wysiłku.

Kaitsu
źródło
16

Myślę, że najlepszym rozwiązaniem jest upewnienie się, że plik konfiguracyjny (zawierający hasło) jest dostępny tylko dla określonego konta użytkownika . Na przykład możesz mieć użytkownika specyficznego dla aplikacji, appuserdo którego tylko zaufane osoby mają hasło (i do którego mogą su).

W ten sposób nie ma irytującego narzutu kryptograficznego, a nadal masz bezpieczne hasło.

EDYCJA: Zakładam, że nie eksportujesz konfiguracji aplikacji poza zaufane środowisko (co nie jestem pewien, czy miałoby to jakiś sens, biorąc pod uwagę pytanie)

oxbow_lakes
źródło
5

Najważniejsze jest to, że słoń w pokoju i to wszystko polega na tym, że jeśli twoja aplikacja może zdobyć hasło, to haker z dostępem do skrzynki również może je zdobyć!

Jedynym sposobem obejścia tego jest to, że aplikacja pyta o „hasło główne” na konsoli przy użyciu standardowego wejścia, a następnie używa go do odszyfrowania haseł przechowywanych w pliku. Oczywiście to całkowicie uniemożliwia uruchomienie aplikacji bez nadzoru wraz z systemem operacyjnym podczas uruchamiania.

Jednak nawet przy takim poziomie irytacji, jeśli hakerowi uda się uzyskać uprawnienia administratora (lub nawet dostęp jako użytkownik uruchamiający aplikację), może zrzucić pamięć i znaleźć tam hasło.

Chodzi o to, aby nie pozwolić całej firmie na dostęp do serwera produkcyjnego (a tym samym do haseł) i upewnić się, że nie da się złamać tego pudełka!

stolsvik
źródło
1
Prawdziwym rozwiązaniem jest przechowywanie klucza prywatnego w innym miejscu, na przykład na karcie lub HSM: en.wikipedia.org/wiki/Hardware_security_module
atom88
4

Cóż, aby rozwiązać problemy z hasłem głównym - najlepszym podejściem jest nie przechowywanie hasła nigdzie, aplikacja powinna szyfrować hasła dla siebie - tak, aby tylko ona mogła je odszyfrować. Więc gdybym używał pliku .config, zrobiłbym co następuje, mySettings.config :

encryptTheseKeys = secretKey, anotherSecret

secretKey = unprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = unprotectedSettingIdontCareAbout

więc wczytałbym klucze, które są wymienione w encryptTheseKeys, zastosowałem na nich przykład Brodwalls z góry i zapisałem je z powrotem do pliku z jakimś znacznikiem (powiedzmy crypt :), aby aplikacja wiedziała, że ​​tego nie robi znowu wynik będzie wyglądał następująco:

encryptTheseKeys = secretKey, anotherSecret

secretKey = crypt: ii4jfj304fjhfj934fouh938

anotherSecret = crypt: jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

Tylko pamiętaj, aby przechowywać oryginały w swoim własnym, bezpiecznym miejscu ...

user1007231
źródło
2
Tak, to jest sprzed 3 lat. Aby uniknąć klucza głównego, ostatecznie użyłem kluczy RSA wydanych z naszego wewnętrznego urzędu certyfikacji. Dostęp do klucza prywatnego jest chroniony poprzez szyfrowanie poprzez odcisk palca sprzętu maszyny.
Petey B
Rozumiem, brzmi całkiem solidnie. ładny.
user1007231
@ user1007231 - Gdzie przechowywać - „Tylko pamiętaj, aby przechowywać oryginały w swoim własnym, bezpiecznym miejscu ...”?
nanosoft
@PeteyB - Nie zrozumiałeś? Czy możesz wskazać mi linki, które mogą mnie oświecić. Dzięki
nanosoft
@nanosoft - Zdobądź „Aegis Secure Key USB” i zapisz w dokumencie tekstowym tam lub na papierze w portfelu
user1007231
0

W zależności od tego, jak bezpieczne są potrzebne pliki konfiguracyjne lub jak niezawodna jest Twoja aplikacja, http://activemq.apache.org/encrypted-passwords.html może być dla Ciebie dobrym rozwiązaniem.

Jeśli nie boisz się odszyfrowania hasła i skonfigurowanie go przy użyciu fasoli do przechowywania klucza hasła może być naprawdę proste. Jeśli jednak potrzebujesz większego bezpieczeństwa, możesz ustawić zmienną środowiskową z sekretem i usunąć ją po uruchomieniu. W związku z tym musisz się martwić, że aplikacja / serwer przestanie działać, a nie aplikacja, która nie zostanie automatycznie uruchomiona ponownie.

CPrescott
źródło
używanie HSM to idealny sposób: en.wikipedia.org/wiki/Hardware_security_module
atom88,
-9

Jeśli korzystasz z języka Java 8, użycie wewnętrznego kodera i dekodera Base64 można uniknąć, zastępując pliki

return new BASE64Encoder().encode(bytes);

z

return Base64.getEncoder().encodeToString(bytes);

i

return new BASE64Decoder().decodeBuffer(property);

z

return Base64.getDecoder().decode(property);

Należy pamiętać, że to rozwiązanie nie chroni danych, ponieważ metody odszyfrowywania są przechowywane w tym samym miejscu. To tylko utrudnia złamanie. Przede wszystkim unika drukowania i pokazywania wszystkim przez pomyłkę.

Antonio Raposo
źródło
26
Base64 nie jest szyfrowaniem.
jwilleke
1
Base64 nie jest szyfrowaniem i jest to najgorszy przykład, jaki możesz podać ... wiele osób uważa, że ​​base64 jest algorytmem szyfrowania, więc lepiej ich nie mylić ...
robob
zwróć uwagę, że metoda decode () w górnej części pliku źródłowego wykonuje faktyczne szyfrowanie. Jednak to kodowanie i dekodowanie base64 jest potrzebne do konwersji z ciągu bajtów w celu przekazania tej funkcji czegoś, czego może użyć (tablica bajtów bajt [])
atom88