Angenommen, ich versuche, von einer RESTful-API zu ziehen, die Basisauthentifizierung / Basiszertifikate verwendet. Was wäre der beste Weg, um diesen Benutzernamen und dieses Kennwort in meinem Programm zu speichern? Im Moment sitzt es nur im Klartext da.
UsernamePasswordCredentials creds = new UsernamePasswordCredentials("myName@myserver","myPassword1234");
Gibt es eine sicherheitsorientiertere Möglichkeit?
Vielen Dank
java
security
authentication
A_Elric
quelle
quelle
Antworten:
Wichtige Notiz:
Wenn Sie das Authentifizierungssystem als Ganzes entwerfen, sollten Sie keine Kennwörter speichern, auch wenn diese verschlüsselt sind. Sie speichern einen Hash und prüfen, ob die bei der Anmeldung angegebenen Kennwörter mit demselben Hash übereinstimmen. Auf diese Weise wird durch eine Sicherheitsverletzung in Ihrer Datenbank vermieden, dass die Kennwörter Ihrer Benutzer offengelegt werden.
Nach alledem, mit einer inneren zu äußeren Denkweise, sind hier einige Schritte, um Ihren Prozess zu schützen:
Im ersten Schritt sollten Sie Ihre Passwortbehandlung von
String
auf änderncharacter array
.Der Grund dafür ist, dass a
String
einimmutable
Objekt ist und seine Daten daher nicht sofort bereinigt werden, selbst wenn das Objekt auf gesetzt istnull
. Die Daten werden stattdessen für die Speicherbereinigung festgelegt. Dies führt zu Sicherheitsproblemen, da böswillige Programme möglicherweise vor der Bereinigung Zugriff auf dieseString
(Kennwort-) Daten erhalten.Dies ist der Hauptgrund, warum die JPasswordField-
getText()
Methode von Swing veraltet ist und warumgetPassword()
Zeichenarrays verwendet werden .Der zweite Schritt besteht darin, Ihre Anmeldeinformationen zu verschlüsseln und sie während des Authentifizierungsprozesses nur vorübergehend zu entschlüsseln. Oder um sie serverseitig zu hashen, speichern Sie diesen Hash und "vergessen" Sie das ursprüngliche Passwort.
Dies stellt ähnlich wie im ersten Schritt sicher, dass Ihre Schwachstellenzeit so kurz wie möglich ist.
Es wird empfohlen, dass Ihre Anmeldeinformationen nicht fest codiert sind und dass Sie sie stattdessen zentral, konfigurierbar und leicht zu warten speichern, z. B. als Konfigurations- oder Eigenschaftendatei oder als Datenbank.
Sie sollten Ihre Anmeldeinformationen verschlüsseln, bevor Sie die Datei speichern. Außerdem können Sie eine zweite Verschlüsselung auf die Datei selbst anwenden (2-Schicht-Verschlüsselung für die Anmeldeinformationen und 1-Schicht für andere Dateiinhalte).
Beachten Sie, dass jeder der beiden oben genannten Verschlüsselungsprozesse selbst mehrschichtig sein kann. Jede Verschlüsselung kann eine individuelle Anwendung des Triple Data Encryption Standard (AKA TDES und 3DES) als konzeptionelles Beispiel sein.
Nachdem Ihre lokale Umgebung ordnungsgemäß geschützt wurde (aber denken Sie daran, dass sie niemals "sicher" ist!), Wendet der dritte Schritt einen grundlegenden Schutz auf Ihren Übertragungsprozess an, indem Sie TLS (Transport Layer Security) oder SSL (Secure Sockets Layer) verwenden .
Der vierte Schritt besteht darin, andere Schutzmethoden anzuwenden.
Wenden Sie beispielsweise Verschleierungstechniken auf Ihre "zu verwendende" Kompilierung an, um (wenn auch in Kürze) zu vermeiden, dass Ihre Sicherheitsmaßnahmen offengelegt werden, falls Ihr Programm von Frau Eve, Herrn Mallory oder einer anderen Person (dem Bad-) erhalten wird. Jungs) und dekompiliert.
UPDATE 1:
Auf Anfrage von @ Damien.Bell finden Sie hier ein Beispiel, das den ersten und zweiten Schritt abdeckt:
//These will be used as the source of the configuration file's stored attributes. private static final Map<String, String> COMMON_ATTRIBUTES = new HashMap<String, String>(); private static final Map<String, char[]> SECURE_ATTRIBUTES = new HashMap<String, char[]>(); //Ciphering (encryption and decryption) password/key. private static final char[] PASSWORD = "Unauthorized_Personel_Is_Unauthorized".toCharArray(); //Cipher salt. private static final byte[] SALT = { (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12, (byte) 0xde, (byte) 0x33, (byte) 0x10, (byte) 0x12,}; //Desktop dir: private static final File DESKTOP = new File(System.getProperty("user.home") + "/Desktop"); //File names: private static final String NO_ENCRYPTION = "no_layers.txt"; private static final String SINGLE_LAYER = "single_layer.txt"; private static final String DOUBLE_LAYER = "double_layer.txt"; /** * @param args the command line arguments */ public static void main(String[] args) throws GeneralSecurityException, FileNotFoundException, IOException { //Set common attributes. COMMON_ATTRIBUTES.put("Gender", "Male"); COMMON_ATTRIBUTES.put("Age", "21"); COMMON_ATTRIBUTES.put("Name", "Hypot Hetical"); COMMON_ATTRIBUTES.put("Nickname", "HH"); /* * Set secure attributes. * NOTE: Ignore the use of Strings here, it's being used for convenience only. * In real implementations, JPasswordField.getPassword() would send the arrays directly. */ SECURE_ATTRIBUTES.put("Username", "Hypothetical".toCharArray()); SECURE_ATTRIBUTES.put("Password", "LetMePass_Word".toCharArray()); /* * For demosntration purposes, I make the three encryption layer-levels I mention. * To leave no doubt the code works, I use real file IO. */ //File without encryption. create_EncryptedFile(NO_ENCRYPTION, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 0); //File with encryption to secure attributes only. create_EncryptedFile(SINGLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 1); //File completely encrypted, including re-encryption of secure attributes. create_EncryptedFile(DOUBLE_LAYER, COMMON_ATTRIBUTES, SECURE_ATTRIBUTES, 2); /* * Show contents of all three encryption levels, from file. */ System.out.println("NO ENCRYPTION: \n" + readFile_NoDecryption(NO_ENCRYPTION) + "\n\n\n"); System.out.println("SINGLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(SINGLE_LAYER) + "\n\n\n"); System.out.println("DOUBLE LAYER ENCRYPTION: \n" + readFile_NoDecryption(DOUBLE_LAYER) + "\n\n\n"); /* * Decryption is demonstrated with the Double-Layer encryption file. */ //Descrypt first layer. (file content) (REMEMBER: Layers are in reverse order from writing). String decryptedContent = readFile_ApplyDecryption(DOUBLE_LAYER); System.out.println("READ: [first layer decrypted]\n" + decryptedContent + "\n\n\n"); //Decrypt second layer (secure data). for (String line : decryptedContent.split("\n")) { String[] pair = line.split(": ", 2); if (pair[0].equalsIgnoreCase("Username") || pair[0].equalsIgnoreCase("Password")) { System.out.println("Decrypted: " + pair[0] + ": " + decrypt(pair[1])); } } } private static String encrypt(byte[] property) throws GeneralSecurityException { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD)); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.ENCRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); //Encrypt and save to temporary storage. String encrypted = Base64.encodeBytes(pbeCipher.doFinal(property)); //Cleanup data-sources - Leave no traces behind. for (int i = 0; i < property.length; i++) { property[i] = 0; } property = null; System.gc(); //Return encryption result. return encrypted; } private static String encrypt(char[] property) throws GeneralSecurityException { //Prepare and encrypt. byte[] bytes = new byte[property.length]; for (int i = 0; i < property.length; i++) { bytes[i] = (byte) property[i]; } String encrypted = encrypt(bytes); /* * Cleanup property here. (child data-source 'bytes' is cleaned inside 'encrypt(byte[])'). * It's not being done because the sources are being used multiple times for the different layer samples. */ // for (int i = 0; i < property.length; i++) { //cleanup allocated data. // property[i] = 0; // } // property = null; //de-allocate data (set for GC). // System.gc(); //Attempt triggering garbage-collection. return encrypted; } private static String encrypt(String property) throws GeneralSecurityException { String encrypted = encrypt(property.getBytes()); /* * Strings can't really have their allocated data cleaned before CG, * that's why secure data should be handled with char[] or byte[]. * Still, don't forget to set for GC, even for data of sesser importancy; * You are making everything safer still, and freeing up memory as bonus. */ property = null; return encrypted; } private static String decrypt(String property) throws GeneralSecurityException, IOException { SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(new PBEKeySpec(PASSWORD)); Cipher pbeCipher = Cipher.getInstance("PBEWithMD5AndDES"); pbeCipher.init(Cipher.DECRYPT_MODE, key, new PBEParameterSpec(SALT, 20)); return new String(pbeCipher.doFinal(Base64.decode(property))); } private static void create_EncryptedFile( String fileName, Map<String, String> commonAttributes, Map<String, char[]> secureAttributes, int layers) throws GeneralSecurityException, FileNotFoundException, IOException { StringBuilder sb = new StringBuilder(); for (String k : commonAttributes.keySet()) { sb.append(k).append(": ").append(commonAttributes.get(k)).append(System.lineSeparator()); } //First encryption layer. Encrypts secure attribute values only. for (String k : secureAttributes.keySet()) { String encryptedValue; if (layers >= 1) { encryptedValue = encrypt(secureAttributes.get(k)); } else { encryptedValue = new String(secureAttributes.get(k)); } sb.append(k).append(": ").append(encryptedValue).append(System.lineSeparator()); } //Prepare file and file-writing process. File f = new File(DESKTOP, fileName); if (!f.getParentFile().exists()) { f.getParentFile().mkdirs(); } else if (f.exists()) { f.delete(); } BufferedWriter bw = new BufferedWriter(new FileWriter(f)); //Second encryption layer. Encrypts whole file content including previously encrypted stuff. if (layers >= 2) { bw.append(encrypt(sb.toString().trim())); } else { bw.append(sb.toString().trim()); } bw.flush(); bw.close(); } private static String readFile_NoDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException { File f = new File(DESKTOP, fileName); BufferedReader br = new BufferedReader(new FileReader(f)); StringBuilder sb = new StringBuilder(); while (br.ready()) { sb.append(br.readLine()).append(System.lineSeparator()); } return sb.toString(); } private static String readFile_ApplyDecryption(String fileName) throws FileNotFoundException, IOException, GeneralSecurityException { File f = new File(DESKTOP, fileName); BufferedReader br = new BufferedReader(new FileReader(f)); StringBuilder sb = new StringBuilder(); while (br.ready()) { sb.append(br.readLine()).append(System.lineSeparator()); } return decrypt(sb.toString()); }
Ein vollständiges Beispiel, das sich mit jedem Schutzschritt befasst, würde weit über das hinausgehen, was ich für diese Frage für angemessen halte, da es um "Was sind die Schritte" und nicht um "Wie man sie anwendet" geht .
Es würde meine Antwort (endlich die Stichprobe) weit überbewerten, während andere Fragen hier auf SO bereits auf das "Wie" dieser Schritte gerichtet sind, weitaus angemessener sind und eine weitaus bessere Erklärung und Stichprobe für die Implementierung von bieten jeder einzelne Schritt.
quelle
[*]
- @ Damien.Bell Um Ihre Anfrage nicht unbeaufsichtigt zu lassen, habe ich ein Beispiel beigefügt, das den ersten (~) und zweiten Schritt abdeckt. --- Warum nicht alle Schritte, wie Sie sehen können, können Sie nicht mit einem winzigen kleinen Codeausschnitt abtasten. Und ein Beispiel für den Netzwerkschutz würde sogar mehr erfordern als das des lokalen Bereichs, selbst wenn es teilweise pseudocodiert werden soll. Die Verschleierung verfügt auch über eine Vielzahl von Implementierungsmethoden, und obwohl das Konzept einfach ist, ist es aufgrund der Tatsache, dass es auf den Quellcode selbst angewendet wird, in Beispielen schwer zu erklären.CharBuffer
Besonderes getan habe, aber im Grunde genommen, wenn es nicht unveränderlich ist (kann es dazu führen, dass interne Daten überschrieben werdennull
oder Nullen ohne Warten auf GC), dann können Sie es verwenden, solange Sie nicht vergessen, es aufzuräumen.Wenn Sie die Basisauthentifizierung verwenden, sollten Sie diese mit SSL koppeln, um zu vermeiden, dass Ihre Anmeldeinformationen in Base64-codiertem Klartext übergeben werden. Sie möchten es nicht einfach machen, dass jemand, der an Ihren Paketen schnüffelt, Ihre Anmeldeinformationen erhält. Codieren Sie Ihre Anmeldeinformationen auch nicht fest in Ihrem Quellcode. Machen Sie sie konfigurierbar. Lesen Sie sie aus einer Konfigurationsdatei. Sie sollten die Anmeldeinformationen verschlüsseln, bevor Sie sie in einer Konfigurationsdatei speichern, und Ihre App sollte die Anmeldeinformationen entschlüsseln, sobald sie aus der Konfigurationsdatei gelesen werden.
quelle
plus wahrscheinlich tausend Dinge, die ich vergessen habe :)
quelle
Es ist im Allgemeinen kein guter Rat, Anmeldeinformationen zu verschlüsseln. Etwas, das verschlüsselt ist, kann entschlüsselt werden. Es wird empfohlen, Kennwörter als gesalzenen Hash zu speichern. Ein Hash kann nicht entschlüsselt werden. Das Salz wird hinzugefügt, um das Erraten von Brute Force mit Rainbow Tables zu besiegen . Solange jede Benutzer-ID ihr eigenes zufälliges Salz hat, müsste ein Angreifer eine Reihe von Tabellen für jeden möglichen Wert des Salzes erstellen, was diesen Angriff innerhalb der Lebensdauer des Universums schnell unmöglich macht. Dies ist der Grund, warum Websites Ihnen Ihr Passwort im Allgemeinen nicht senden können, wenn Sie es vergessen haben, aber sie können es nur zurücksetzen. Sie haben Ihr Passwort nicht gespeichert, nur einen Hash davon.
Das Hashing von Passwörtern ist nicht sehr schwierig, aber es ist ein so häufiges Problem, dass unzählige andere es für Sie getan haben. Ich habe festgestellt, dass jBcrypt einfach zu bedienen ist.
Als zusätzlichen Schutz gegen das Brute-Force-Erraten von Kennwörtern wird empfohlen, eine Benutzer-ID oder eine Remote-IP nach einer bestimmten Anzahl von Anmeldeversuchen mit dem falschen Kennwort einige Sekunden lang zu warten. Ohne dies kann ein Brute-Force-Angreifer so viele Passwörter pro Sekunde erraten, wie Ihr Server verarbeiten kann. Es gibt einen großen Unterschied zwischen dem Erraten von 100 Passwörtern pro 10 Sekunden oder einer Million.
Ich habe den Eindruck, dass Sie die Kombination aus Benutzername und Passwort in Ihren Quellcode aufgenommen haben. Dies bedeutet, dass Sie, wenn Sie das Kennwort jemals ändern möchten, Ihren Dienst neu kompilieren, stoppen und neu starten müssen. Außerdem bedeutet dies, dass jeder, der Ihren Quellcode erhält, auch über Ihre Kennwörter verfügt. Es wird empfohlen, dies niemals zu tun, sondern die Anmeldeinformationen (Benutzername, Kennwort-Hash, Kennwort-Salt) in Ihrem Datenspeicher zu speichern
quelle
security.Provider
auch in älteren Versionen, sodass ich immer noch keine Notwendigkeit für APIs von Drittanbietern sehe.Warum reden die Leute über Hashing? OP möchte die Anmeldeinformationen seiner Benutzer speichern, um auf externe Ressourcen zuzugreifen. Das Hashing seines Passworts hilft nicht.
Das ist aus dem Weg. Ich würde nur einfache Best Practices für jede Ebene.
1. Speichern Sie Ihr Passwort in der Java-App. : Speichern Sie es als Char Array. Erstellen Sie eine Kennwortspeicherklasse und speichern Sie das Kennwort als Hashmap mit dem Schlüssel als Ressource, auf die Sie zugreifen möchten, und bewerten Sie es als ein Objekt, das Benutzername und Kennwort enthält. Beschränken Sie den Einstiegspunkt auf diese API mit einer gewissen Authentifizierung. Beispiel: Akzeptieren Sie die Anmeldeinformationen der angemeldeten Benutzer, um die Zugriffsebene dieses Benutzers für diese Ressource zu überprüfen (ordnen Sie den Benutzer einfach der Liste der Kennwörter zu, auf die er zugreifen kann. Wenn Sie häufig eine Gruppe erstellt haben und ordnen Sie dieser Gruppe den Kennwortzuordnungsschlüssel zu.) Alles darüber hinaus, um das Kennwort zu speichern, hängt davon ab, wie paranoid Sie sind, wenn Sie jvm selbst verlieren.
quelle
Wenn Sie der Umgebung, in der Ihr Programm ausgeführt wird, nicht vertrauen können, sich jedoch über einfache Kennwörter oder Zertifikate authentifizieren müssen, können Sie Ihre Anmeldeinformationen nicht sichern. Sie können sie höchstens mit den in den anderen Antworten beschriebenen Methoden verschleiern.
Als Problemumgehung würde ich alle Anforderungen an die RESTful-API über einen Proxy ausführen, dem Sie vertrauen können, und von dort aus die Klartextkennwortauthentifizierung durchführen.
quelle
0
und1
im Speicher ankommt, die nach einem bestimmten Satz logischer Regeln erreicht werden, die von Natur aus immer irgendwie umkehrbar sind. ___ Die Grundlage der kryptografischen Sicherheit ist, war und wird wahrscheinlich immer sein, "es so schwierig zu machen, dass sich die Mühe nicht lohnt". ___ Zuletzt verwechseln Sie das automatische Ausfüllen / Anmelden des Browsers (für Websites) mit dem automatischen Authentifizieren / Anmelden der Anwendung (das nur in verschlüsselten Dateien gespeichert wird).