# client programm, passwortverschlüsselung



## dermoritz (12. Aug 2010)

mich würde mal interessieren, wie man am besten Passwörter im Sourcecode von Java-Client-anwendungen verschlüsseln kann. 
Also zum Beispiel in Java-Programmen die sich zu einer Datenbank verbinden oder zu einem Windows LDAP-Server. Wenn man nach sowas googelt kommt eben immer die Hash-Lösung: Jasypt. Aber wenn ich das richtig verstanden habe nützt das nur was wenn man die serverseite/ rechteverwaltung selbst implementiert.
in einer reinen clientanwendung kommt man nicht umhin das Passwort vor dem Login (falls es vrschlüsselt ist) zu entschlüsseln?!
Also wie kann man reine Java-(Fat)Clientprogramme halbwegs sicher diesbezüglich machen?


----------



## XHelp (12. Aug 2010)

Hash-Algorithmen sind keine Verschlüsselungsalgorithmen. Du kommst nicht mehr ohne weiteres auf den Klartext
Wie du schon richtig bemerkt hast, musst du es an einer Stelle entschlüsseln, deswegen wirst du nicht so leicht da etwas machen.
(wobei ich mir vorstelle, dass da die Nachfrage an Lösungen ziemlich groß ist, deswegen da ggf gute Ansätze existieren)


----------



## dermoritz (12. Aug 2010)

die einzige halbwegs brauchbare sache könnte eben symmetrische verschlüsselung sei:
Basic symmetric encryption example : EncryptionSecurityJava

oder?

nur wie implementiert man damit dass was ich will? - den key muss ich ja auch im sourcecode speichern oder?


----------



## Otzelot (12. Aug 2010)

Findet eine Kommunikation zwischen Server und Client statt?

Wenn ja könnte darüber der Key getauscht werden. 
Wenn nein dann musste dir was anderes ausdenken: Eine gängige Lösung wäre eine asymmetrische Verschlüsselung. Findet keine Kommunikation zwischen den Clienten statt würde sich eine symmetrische empfehlen.

Ich muss ehrlich gestehen ich kenn mich mit diesem Server/Client Sachen die du vor hast nicht aus mit Kryptoverfahren und deren Sicherheit aber schon wenn ich genau wüsste wie was abläuft und worauf du Zugriff hast wäre es einfacher dazu was zu sagen.


----------



## dermoritz (23. Aug 2010)

in meinem ganz speziellen Fall ist der Client einfach eine Swinganwendung, welche eine Datenbankverbindung(JPA oder klassisch) aufbaut. In beiden Fällen muss man dieser Anwendung ja login und passwort für die DB mitgeben. Jeder der nur etwas Java-know-how mitbringt findet dann diese Daten in der jar-Datei.
Kann man diese Möglichkeit die login-Daten zu erfahren unterbinden? Gibt es da eine "Best Practice"?

Nun bin ich beim Darübernachdenken auf den Trichter gekommen, dass so eine Art der Anwendungsarchitektur eventuell suboptimal ist. 
Für zukunftige Programme (letztendlich sind es meistens irgendwelche speziellen DB-Anwendungen die nur bestimmte Gruppen in unserer Firma benutzen) würde ich dieses Thema prinzipiell anders angehen wollen: Bei uns meldet sich ja jeder mit einem Windows Benutzer an irgendeinem Windows-LDAP Server an - dass würde ich gerne Nutzen.
Also in Zukunft würde ich gerne Java-Anwendungen so bauen, dass sie über den Benutzer der sie startet, die Rechte/Gruppen abfragen und sich darüber authorisieren. Und wie man sowas baut würde mich sehr interessieren. (irgendeinen Java webservice der Zentral authentifiezierung/autorisierung macht??)

Also mir ist bewusst, dass ich im ersten Post quasi 2 fragen vermischt habe, bzw. sie völlig falsch gestellt habe. Für mich wäre die Beantwortung des 2. Abschnitts (grundlegende Sicherheitsarchitektur) wichtiger. Aber falls jemand ein gute Lösung für das erste Problem hat (unkenntlichmachen von login-Daten in bestehenden Anwendungen) wär ich auch sehr dankbar.


----------



## kay73 (28. Aug 2010)

Credentials in eine Client-Anwendung zu packen ist eine Notlösung, die nur in kleinem Rahmen geht, das sie für mehrere Installationen schnell zum Maintenance-Nightmare wird. (Nicht, dass ich sowas nicht schon bei sehr großen Kunden gesehen habe...). Es scheint vermünftig, eine 
	
	
	
	





```
DataSource
```
 in einem JNDI zu parken und dessen Authentifizierung zu nutzen, damit nur erlaubte User zugreifen können.

LDAP Authentication
Data Sources and JNDI


----------



## illuminus (28. Aug 2010)

Ich würde ein Serverprogramm schreiben, welches die Verbindung zur DB übernimmt.
Der Client verbindet sich dann zu dem Server und kann diesen auch nur nach den für ihn bestimmten Daten fragen.

Man sollte einem Client (der jetzt nicht gerade nen MySQL Management Tool ist) auch keinen Zugang zur DB geben.

Damit lösen sich dann auch alle Probleme.


----------



## kay73 (28. Aug 2010)

illuminus hat gesagt.:


> Ich würde ein Serverprogramm schreiben, welches die Verbindung zur DB übernimmt. Damit lösen sich dann auch alle Probleme.


Sicherlich richtig. Das ist dann aber auch ein anderes Deployment-Szenario. Manchmal gehen auch in großen Firmen die USB Sticks mit einer Client-Anwendung durch die Gegend.

Java bietet für solche Szenarien eine u. U. vertretbare Lösung, die ich aber selbst noch nicht implementiert habe: Man erzeugt einmalig einen Blowfish- oder AES-Key und verschlüsselt damit vorher die Einträge im Properties-File. Den Key parkt man mit alias und Passwort-geschützt in einem Java-KeyStore-File, das man mit der Client-Anwendung zusammen ausliefert. 

Wenn das Passwort kompromittiert wird, ändert man die Datenbank-Credentials und baut neue Keystores, die man dann ausliefert.


----------



## illuminus (28. Aug 2010)

Wenn zusätzlich für die Verbdingung zum Server Passörter genutzt werden sollte sich das Sicherheitrisiko minimieren.

"admin" hat passwort1
passwort1 erlaubt zugriff über Client auf den Server nicht aber dierekten SQL zugriff.
Wird das Clientprog kopiert kommt ein "Hacker" ohne Passwort trotz des Clients nicht an die Daten.

passwort1 sollte natürlich != SQL Pass sein


----------



## dermoritz (31. Aug 2010)

danke für die vielen Tips. Summa Summarum kann man also ein Clientprogramm, welches sich direkt zu einer DB verbindet nicht sicherer machen?! Einzig praktikabel sind also client-server Architekturen?!

@kay73: genau das wäre die Richtung die ich in Zukunft einschlagen würde. Nur mal ne Frage zur konkreten Architektur: Das Clientprogramm verbindet sich nur zu einer JNDI Resource. Diese Resource fragt dannach dem Win-Benutzer der das Clientprogramm ausführt. In der Serverseite sind dann LDAP-(Admin)-Credentials hinterlegt mit der die Serverseite den LDAP nach z.b. der Zugehörigkeit zu einer Gruppe fragt. Falls alles pass steht die JNDI-Resource entsprechend zur Verfügung?!

Welche Rechte müssen diese LDAP Credentials haben? Google meint of das man einen LDAP-Admin braucht um z.B. Gruppenzugehörigkeit zu erfragen - ist das so?


EDIT: gibt es eigentlich irgendwelche Java-Bücher die Sicherheit zu einem großen Thema machen. Und eben den Entwurf "sicherer" Javasysteme beschreiben?


----------



## kay73 (31. Aug 2010)

Tut mir echt leid, bei den Details zu JNDI muss ich einfach passen. Vielleicht mal ins Enterprise Forum posten? Aber: Aus der JNDI Ressource kommt eine DataSource, z. B. BasicDataSource (Commons DBCP 1.4.1-SNAPSHOT API), die das Referenceable Interface implementiert, damit man das Objekt in einem JNDI Store abspeichern kann. Die Client-Anwendung fragt die 
	
	
	
	





```
DataSource
```
 an und ruft darauf 
	
	
	
	





```
getConnection()
```
 auf, um die Verbindung aufzubauen.

Wie man genau die eigentliche Authentifizierung und die Zuordnung der JNDI-Ressource innerhalb des darunterliegenden LDAP zuordnet, weiss ich leider nicht.

Was das Stand-Alone-Clientprogramm anbetrifft: So schlecht ist das gar nicht. Du lieferst insgesamt drei Files aus; Client-Jar-Anwendung, Key-Store-File und Blowfish-verschlüsselte Properties-Datei. 
- Es bringt nichts, das Client-Programm zu kopieren oder irgendeinen Teil zu disassemblieren.
- Die Credentials im Properties-File sind definitiv sicher.
- Der Keystore rückt den Schlüssel nur mit Passphrase heraus.


----------



## Empire Phoenix (1. Sep 2010)

Also direkt kannst du nur dafür sorgen das nicht jeder mit notepad das pw herausbekommt. Aber sowas ist produktiv mal absolut inakzeptabel.

Saubere simple Lösung: In der DB für jeden nutzer einen user erstellen, und ein Loginfeld in den Client einbauen.
Damit ist das sicherheitsproblem auf den Kopf der betroffenen Person verschoben ^^ (und die person die den usern hoffentlich sinnvolle rechte gibt)

Oder halt einen Server zwischen setzen, der entweder die Daten bereitstellt oder zumindest wenn er nur Login verwaltet die Querys auf auffälligkeiten Filtert (Kommentare im query sind zb auffälllig).


----------



## dermoritz (13. Sep 2010)

@ Phoenix
so wäre der Plan für die Zukunft, nur eben das es kein Loginfeld gibt, sondern dass direkt der LDAP Benutzer, der das Programm startet "benutzt" wird. Und da frage ich mich eben wie das geht. Man müsste irgendwie entweder den MySQl Server an Ldap anschließen und Berechtigungen auf LDAP-Basis verteilen oder eben eine Server zwischenschalten der die Ldap Berechtigungen "versteht" und eben ggf. eine JNDI/DB Resource zur Verfügung stellt.
Hab ich das so richtig verstanden? Zu LDAP hätte ich nur noch eine Frage - muss der LDAP-Benutzer des Servers Adminrechte haben um z.B. Gruppenzugehörigkeiten zu sehen?


@kay
"Du lieferst insgesamt drei Files aus; Client-Jar-Anwendung, Key-Store-File und Blowfish-verschlüsselte Properties-Datei. "
Gibt es für eine solche "Architektur" irgendwo Beispiele? (Wonach googelt man? Blowfish oder KEystore oder encrypt Properties?)
Mit KeyStore bin ich bis jetzt nur bei jar-Signierung für Webstart in Berührung gekommen. Mit Blowfish noch nie. Ich schätze im Keystore wäre der Schlüssel um die Properties zu entschlüsseln?!


----------



## kay73 (14. Sep 2010)

dermoritz hat gesagt.:


> Ich schätze im Keystore wäre der Schlüssel um die Properties zu entschlüsseln?!


Genau. Da ist er auch gut aufgehoben. Das Parken des Keys im KeyStore ist erstaunlich simpel. Du musst auch nicht unbedingt Blowfish nehmen; jede "gute" symmetrische Chiffre wie AES tut es auch.

Ich kann heute abend mal ein Demo coden, falls Interesse besteht. 

Aber entscheidend ist: Das Programm ist zwar sicher gegen Dekompilation, weil es ein Passwort fuer die interne Verwendung des Schluessels vom Benutzer abfragt. Wenn das Passwort kompromittiert wird, sind mit einem Schlag alle in Umlauf befindlichen KeyStores unsicher und die Datenbankcredentials muessen geaendert werden. Es reicht nicht, neue KeyStores auszuliefern.


----------



## dermoritz (15. Sep 2010)

danke, ich hätte in jedem fall interesse an democode. die sicherheitsproblematik ist mir durchaus bewusst - in diesem Fall geht es aber nur um interne Programme.
Also genau die Architektur die dir vorschwebt kay passt glaube ganz genau.


----------



## kay73 (15. Sep 2010)

```
GenEncryptedProperties
```
 wäre so eine Art Miniatur-Admin Tool und erzeugt zwei Files: Den KeyStore und die verschlüsselten Properties. Um das etwas flexibler zu gestalten, könnte man mit einem besseren Admin-Tool den Masterkey aus einem KeyStore holen, dann Properties im Klartext laden und verschlüsseln.

Du kannst den KeyStore so inspizieren:

```
keytool -list -storepass keypass123 -keystore keystore.bin -storetype jceks
```
Weil der User "keypass123" nicht kennt, kann er den MasterKey nicht extrahieren, obwohl er das Schlüssel-Alias-Passswort kennt.  Man müsste also dekompilieren und das Passwort für den Schlüssel kennen.


```
DatabaseConnectionApp
```
 wäre dann der Code für den Client,

Du brauchst noch einen Base64 Codec. Ich habe den hier genommen:
http://www.source-code.biz/base64coder/java/Base64Coder.java.txt

Der Code hier ist total quick and dirty, ich habe mir alles exception handling gespart. Für eine Prod-version würde man das natürlich viel ordentlicher machen.  Aber dafür ist es recht kurz.


```
import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.HashMap;
import java.util.Map;
import java.util.Properties;
import java.util.Map.Entry;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.KeyGenerator;
import javax.crypto.NoSuchPaddingException;

//Eine Art Batch-File, nur zum Zweck alle Codebeispiele abzuspielen
public class GenEncryptedProperties {

	public static void main(String[] args) throws NoSuchAlgorithmException, KeyStoreException, CertificateException, IOException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {

		// Den geheimem Schlüssel einmalig erzeugen. Für Extraktion aus 
		// einem existierenden Keystore zur Wiederverwendung siehe 
		// DataConnectionApp.
		final KeyGenerator keyGen = KeyGenerator.getInstance("Blowfish");
		final Key highlySecretMasterKey = keyGen.generateKey();
			
		// Dieses Passwort lassen wir geheim.
		final String nonSecretKeyStorePassword = "keypass123";
		
		// KeyStore erzeugen: Typ JCEKS ist notwendig, um Keys zu speichern,
		// die kein PrivateKey sind.
		final KeyStore keyStore = KeyStore.getInstance("JCEKS");
		keyStore.load(null, nonSecretKeyStorePassword.toCharArray());

		// Geheimes Passwort nur für authorisierte Nutzer,
		final String userSecretKeyPassword="confidential!";
		
		// Schlüssel und (-name) speichern
		final String wellKnownMasterKeyAlias="masterkey";
		keyStore.setKeyEntry(wellKnownMasterKeyAlias, highlySecretMasterKey, userSecretKeyPassword.toCharArray(), null);
		
		// KeyStore auf Platte speichern.
		final FileOutputStream fos = new FileOutputStream("keystore.bin");
		keyStore.store(fos, nonSecretKeyStorePassword.toCharArray());
		fos.close();
		
		// Ein paar Properties veschlüsseln.
		final Map<String, String> plainTextProperties = new HashMap<String, String>();
		plainTextProperties.put("db.url", "oracle.company.com");
		plainTextProperties.put("db.sid", "XE");
		plainTextProperties.put("db.role", "SYSDBA");
		plainTextProperties.put("db.username", "scott");
		plainTextProperties.put("db.password", "tiger");
		
		final Properties encryptedProperties = new Properties();
		for(final Entry<String, String> entry : plainTextProperties.entrySet()) {
			encryptedProperties.setProperty(entry.getKey(), encryptBlowfishBase64(entry.getValue(), highlySecretMasterKey));
		}
		
		// Properties speichern.
		final FileOutputStream fos2 = new FileOutputStream("connection.properties");
		encryptedProperties.store(fos2, "Secure database connection properties\nCrack me if you can! :-");
		fos2.close();
	}
	
	public static final String encryptBlowfishBase64(final String content, final Key key) throws NoSuchAlgorithmException, IOException, NoSuchPaddingException, InvalidKeyException, IllegalBlockSizeException, BadPaddingException {
		
		final ByteArrayOutputStream bos = new ByteArrayOutputStream(512);
	
		// Den MD5 (16 byte) vor den zu verschlüsselnden Inhalt hängen.
		// So kann man sicher prüfen, ob korrekt entschlüsslet wurde. 
		final MessageDigest md5 = MessageDigest.getInstance("MD5");
		bos.write(md5.digest(content.getBytes()));
		bos.write(content.getBytes());
		
		//Blowfish Instanz erzeugen
		final Cipher blowfish = Cipher.getInstance("Blowfish");
		blowfish.init(Cipher.ENCRYPT_MODE, key);
		
		// Verschlüsseln und Ergebnis BASE64 kodieren 
		return new String(Base64Coder.encode(blowfish.doFinal(bos.toByteArray())));
	}
}
```


```
import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.security.InvalidKeyException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateException;
import java.util.Arrays;
import java.util.Properties;
import java.util.Map.Entry;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.swing.JOptionPane;

public class DatabaseConnectionApp {
	
	public static void main(String[] args) throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException, InvalidKeyException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		
		// Keystore laden.
		final String nonSecretKeyStorePassword = "keypass123";
		final KeyStore keyStore = KeyStore.getInstance("JCEKS");
		final FileInputStream fis = new FileInputStream("keystore.bin");
		keyStore.load(fis, nonSecretKeyStorePassword.toCharArray());
		
		// Nutzerpasswort abfragen.
		final String masterKeyPassword = readUserInput("Please enter Password:");

		// Masterkey extrahieren.
		final Key masterKey;
		try {
			masterKey = keyStore.getKey("masterkey", masterKeyPassword.toCharArray());
		} catch ( final Throwable t) { 
			JOptionPane.showMessageDialog(null, "You entered a wrong password.\nExiting now.", "Authentication error.", JOptionPane.ERROR_MESSAGE);
			return;
		}
		
		// Properties laden.
		final Properties properties = new Properties();
		properties.load(new FileInputStream("connection.properties"));
		
		// Entschlüsseln und ausgeben.
		System.out.println("Decrypting properties:");
		for(final Entry<?,?> entry : properties.entrySet()) {
			final String propertyName = (String)entry.getKey();			
			final String encryptedValue = (String)entry.getValue();
			
			System.out.println(propertyName+"="+decrypt(encryptedValue, masterKey));
		}
	}
	
	public static String decrypt(final String base64, final Key key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, IllegalBlockSizeException, BadPaddingException {
		
		// Wieder zurückwandeln in bytes.
		final byte [] encrypted = Base64Coder.decode(base64);
		
		// Blowfish, Entschlüsselungsmodus
		final Cipher blowfish = Cipher.getInstance("Blowfish");
		blowfish.init(Cipher.DECRYPT_MODE, key);
		
		// Entschlüsseln, hier kann schon eine BadPaddingException 
		// fliegen, wenn der Key nicht stimmt.
		final byte [] decrypted = blowfish.doFinal(encrypted);
		
		// Prüf-MD5 berechnen 
		final MessageDigest md5 = MessageDigest.getInstance("MD5");
		md5.update(decrypted, 16, decrypted.length - 16);		
		final byte [] digest1 = md5.digest();
		
		// Übermittelten MD5 extrahieren...
		final byte [] digest2 = new byte[16];
		System.arraycopy(decrypted, 0, digest2, 0, 16);
		
		// ...und überprüfen
		if(!Arrays.equals(digest1, digest2)) {
			System.err.println("Bad key.");
			return null;
		}
		
		// ab hier wird es schon stimmen.
		return new String(decrypted, 16, decrypted.length-16); 
	}
	
	public static String readUserInput(final String message) throws IOException {
		System.out.println(message);
		return new BufferedReader(new InputStreamReader(System.in)).readLine().trim();		
	}
}
```


----------



## dermoritz (16. Sep 2010)

vielen vielen Dank!

das reicht völlig - mir geht es ja darum die Architektur zu verstehen und dafür ist Beispiel code perfekt. (Exceptionhandlicng würde wahrscheinlich nur verwirren ;-))


----------

