Passwort in Konfigurationsdateien verschlüsseln? [geschlossen]

130

Ich habe ein Programm, das Serverinformationen aus einer Konfigurationsdatei liest und das Kennwort in dieser Konfiguration verschlüsseln möchte, das von meinem Programm gelesen und entschlüsselt werden kann.

Anforderungen:

  • Verschlüsseln Sie das Klartextkennwort, das in der Datei gespeichert werden soll
  • Entschlüsseln Sie das verschlüsselte Passwort, das aus der Datei meines Programms eingelesen wurde

Irgendwelche Empfehlungen, wie ich das machen würde? Ich dachte daran, meinen eigenen Algorithmus zu schreiben, aber ich denke, er wäre furchtbar unsicher.

Petey B.
quelle

Antworten:

172

Eine einfache Möglichkeit hierfür ist die Verwendung der kennwortbasierten Verschlüsselung in Java. Auf diese Weise können Sie einen Text mit einem Kennwort verschlüsseln und entschlüsseln.

Dies bedeutet im Grunde, einen javax.crypto.Cipherwith-Algorithmus zu initialisieren "AES/CBC/PKCS5Padding"und einen Schlüssel javax.crypto.SecretKeyFactorymit dem "PBKDF2WithHmacSHA512"Algorithmus abzurufen.

Hier ist ein Codebeispiel (aktualisiert, um die weniger sichere MD5-basierte Variante zu ersetzen):

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

Ein Problem bleibt bestehen: Wo sollten Sie das Kennwort speichern, mit dem Sie die Kennwörter verschlüsseln? Sie können es in der Quelldatei speichern und verschleiern, aber es ist nicht allzu schwer, es wieder zu finden. Alternativ können Sie es als Systemeigenschaft angeben, wenn Sie den Java-Prozess starten ( -DpropertyProtectionPassword=...).

Das gleiche Problem bleibt bestehen, wenn Sie den KeyStore verwenden, der ebenfalls durch ein Kennwort geschützt ist. Grundsätzlich benötigen Sie irgendwo ein Hauptkennwort, und es ist ziemlich schwer zu schützen.

Johannes Brodwall
quelle
3
Vielen Dank für das Codebeispiel, es ist ziemlich genau so, wie ich es gemacht habe. In Bezug auf das Passwort, das die Passwörter schützt, auf die ich bei demselben Problem gestoßen bin, habe ich mich vorerst für die Methode der Verschleierung entschieden, aber noch keine akzeptable Lösung gefunden, danke für Ihre Vorschläge.
Petey B
7
"Alternativ können Sie es als Systemeigenschaft angeben, wenn Sie den Java-Prozess starten (-DpropertyProtectionPassword = ...)." Beachten Sie, dass dies das Extrahieren des Kennworts mit "ps fax" unter (GNU / Linux) / UNIX ermöglichen würde.
Ztyx
7
@Ben Es ist üblich, in Base64 zu codieren, damit Sie den resultierenden Wert in einer Textdatei oder einer auf Zeichenfolgen basierenden Datenbankspalte oder ähnlichem speichern können.
RB.
4
@ V.7 nein. MD5 ist absolut nicht sicher für Passwort-Hashing und wurde nie für diesen Zweck entwickelt. Verwenden Sie es niemals dafür. In diesen Tagen ist Argon2 am besten. Siehe owasp.org/index.php/Password_Storage_Cheat_Sheet und paragonie.com/blog/2016/02/how-safely-store-password-in-2016
Kimball Robinson
3
Viel besser auf diese Weise. Natürlich wäre ein sicheres zufälliges Salz und eine Iterationszahl von (konservativ, niedrig) 40K besser, aber zumindest haben Sie diese Dinge in den Kommentaren angegeben, und PBKDF2 und AES / CBC sind definitiv Verbesserungen. Ich finde es großartig, wie Sie damit umgegangen sind, indem Sie die Antwort aktualisiert haben. Ich werde die Warnung entfernen. Stimmen Sie Ihren Kommentar ab, damit die Leute nicht überrascht sind, den aktualisierten Code zu finden (sie können sich die Änderungen ansehen, um den alten Code zu finden, nehme ich an). Könnte eine gute Idee sein, auch Ihre alten Kommentare zu bereinigen.
Maarten Bodewes
20

Ja, schreiben Sie definitiv keinen eigenen Algorithmus. Java hat viele Kryptographie-APIs.

Wenn das Betriebssystem, auf dem Sie installieren, über einen Keystore verfügt, können Sie damit Ihre Kryptoschlüssel speichern, die Sie zum Ver- und Entschlüsseln der vertraulichen Daten in Ihrer Konfiguration oder in anderen Dateien benötigen.

JeeBee
quelle
4
+1 für die Verwendung eines KeyStore! Es ist nichts weiter als eine Verschleierung, wenn Sie den Schlüssel in der Jar-Datei speichern.
Neunseitiger
2
Wenn das Kennwort nur nicht im Klartext gespeichert werden muss, sind die Keystores zu viel des Guten.
Thorbjørn Ravn Andersen
20

Schauen Sie sich jasypt an , eine Bibliothek, die mit minimalem Aufwand grundlegende Verschlüsselungsfunktionen bietet.

Kaitsu
quelle
16

Ich denke, dass der beste Ansatz darin besteht, sicherzustellen, dass Ihre Konfigurationsdatei (die Ihr Passwort enthält) nur einem bestimmten Benutzerkonto zugänglich ist . Beispielsweise haben Sie möglicherweise einen anwendungsspezifischen Benutzer, appuserfür den nur vertrauenswürdige Personen das Kennwort haben (und für den siesu tun).

Auf diese Weise entsteht kein lästiger Kryptografie-Overhead und Sie haben immer noch ein sicheres Passwort.

BEARBEITEN: Ich gehe davon aus, dass Sie Ihre Anwendungskonfiguration nicht außerhalb einer vertrauenswürdigen Umgebung exportieren (was angesichts der Frage nicht sicher ist, ob dies sinnvoll wäre).

oxbow_lakes
quelle
4

Um die Probleme des Hauptkennworts zu lösen - der beste Ansatz besteht darin, das Kennwort nirgendwo zu speichern. Die Anwendung sollte Kennwörter für sich selbst verschlüsseln - damit nur sie entschlüsselt werden können. Wenn ich also eine .config-Datei verwenden würde, würde ich Folgendes tun : mySettings.config :

encryptTheseKeys = secretKey, anotherSecret

secretKey = unprotectedPasswordThatIputHere

anotherSecret = anotherPass

someKey = unprotectedSettingIdontCareAbout

Also würde ich die Schlüssel einlesen, die in den encryptTheseKeys erwähnt sind, das Brodwalls-Beispiel von oben auf sie anwenden und sie mit einer Art Markierung (sagen wir Krypta :) in die Datei zurückschreiben , damit die Anwendung weiß, dass sie es nicht tut Auch hier würde die Ausgabe folgendermaßen aussehen:

encryptTheseKeys = secretKey, anotherSecret

secretKey = crypt: ii4jfj304fjhfj934fouh938

anotherSecret = crypt: jd48jofh48h

someKey = unprotectedSettingIdontCareAbout

Bewahren Sie die Originale an einem sicheren Ort auf ...

user1007231
quelle
2
Ja, das ist von vor 3 Jahren. Um den Hauptschlüssel zu vermeiden, habe ich RSA-Schlüssel verwendet, die von unserer internen Zertifizierungsstelle ausgegeben wurden. Der Zugriff auf den privaten Schlüssel wird durch Verschlüsselung mit einem Fingerabdruck der Maschinenhardware geschützt.
Petey B
Ich verstehe, klingt ziemlich solide. nett.
user1007231
@ user1007231 - Aufbewahrungsort - "Bewahren Sie die Originale an einem sicheren Ort auf ..."?
Nanosoft
@PeteyB - Nicht verstanden? Können Sie mich auf einige Links verweisen, die mich aufklären können? Danke
Nanosoft
@nanosoft - Holen Sie sich einen "Aegis Secure Key USB" und speichern Sie in einem Textdokument dort oder auf Papier in Ihrer Brieftasche
user1007231
4

Der große Punkt und der Elefant im Raum und all das ist, dass, wenn Ihre Anwendung das Passwort erhalten kann, ein Hacker mit Zugriff auf die Box auch darauf zugreifen kann!

Die einzige Möglichkeit, dies zu umgehen, besteht darin, dass die Anwendung mithilfe der Standardeingabe auf der Konsole nach dem "Hauptkennwort" fragt und dieses dann zum Entschlüsseln der in der Datei gespeicherten Kennwörter verwendet. Dies macht es natürlich unmöglich, die Anwendung beim Booten unbeaufsichtigt zusammen mit dem Betriebssystem zu starten.

Selbst mit diesem Grad an Ärger könnte ein Hacker, wenn er Root-Zugriff erhält (oder sogar nur als Benutzer, der Ihre Anwendung ausführt), den Speicher entleeren und dort das Kennwort finden.

Die Sache, die sichergestellt werden muss, ist, nicht dem gesamten Unternehmen Zugriff auf den Produktionsserver (und damit auf die Passwörter) zu gewähren und sicherzustellen, dass es unmöglich ist, diese Box zu knacken!

stolsvik
quelle
Die wirkliche Lösung besteht darin, Ihren privaten Schlüssel an einem anderen Ort zu speichern, beispielsweise auf einer Karte oder einem HSM: en.wikipedia.org/wiki/Hardware_security_module
atom88
0

Je nachdem, wie sicher Sie die Konfigurationsdateien benötigen oder wie zuverlässig Ihre Anwendung ist, ist http://activemq.apache.org/encrypted-passwords.html möglicherweise eine gute Lösung für Sie.

Wenn Sie keine Angst vor der Entschlüsselung des Kennworts haben und die Konfiguration mithilfe einer Bean zum Speichern des Kennwortschlüssels sehr einfach sein kann. Wenn Sie jedoch mehr Sicherheit benötigen, können Sie eine Umgebungsvariable mit dem Geheimnis festlegen und nach dem Start entfernen. Dabei müssen Sie sich Sorgen machen, dass die Anwendung / der Server ausfällt und die Anwendung nicht automatisch neu gestartet wird.

CPrescott
quelle
Die Verwendung eines HSM ist der ideale Weg: en.wikipedia.org/wiki/Hardware_security_module
atom88
-8

Wenn Sie Java 8 verwenden, kann die Verwendung des internen Base64-Codierers und -Decodierers durch Ersetzen vermieden werden

return new BASE64Encoder().encode(bytes);

mit

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

und

return new BASE64Decoder().decodeBuffer(property);

mit

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

Beachten Sie, dass diese Lösung Ihre Daten nicht schützt, da die Methoden zum Entschlüsseln am selben Ort gespeichert sind. Es macht es nur schwieriger zu brechen. Hauptsächlich wird vermieden, es zu drucken und versehentlich allen zu zeigen.

Antonio Raposo
quelle
26
Base64 ist keine Verschlüsselung.
Jwilleke
1
Base64 ist keine Verschlüsselung und es ist das schlechteste Beispiel, das Sie liefern können ... viele Leute glauben, dass base64 ein Verschlüsselungsalgorithmus ist, also ist es besser, sie nicht zu verwirren ...
robob
Beachten Sie, dass die decode () -Methode oben in der Quelldatei die eigentliche Verschlüsselung durchführt. Diese Base64-Codierung und -Decodierung wird jedoch benötigt, um die Bytes aus einer Zeichenfolge zu konvertieren, um dieser Funktion etwas zu übergeben, das sie verwenden kann (ein Byte-Array-Byte [])
atom88