I'm developing a Java client application that connects to a web service. For this web service, I need an authentication secret (password/token/...). How can I store this password securely within the Operating Systems "default" Key-Store System?
(By "default" I mean a standard tool provided by the OS, e.g. on Mac there is a "Keychain Access" application that stores certificates and passwords. Some applications manage to store their passwords there and each time the password is needed the Operating System itself provides a dialog asking for permission. Or on Windows, there is the Data Protection API.)
So far I came across this question about storing certificates and Java's KeyStore class. And managed to write the following:
//Loading the OS default keystore
KeyStore ks = KeyStore.getInstance("KeychainStore", "Apple");
ks.load(null, null);
//Reading keys
Enumeration<String> aliases = ks.aliases();
while (aliases.hasMoreElements()) {
String alias = aliases.nextElement();
if (ks.isKeyEntry(alias)) {
Key key = ks.getKey(alias, "anything".toCharArray()); //this invokes the OS popup
if (key == null) {
System.err.println(alias + " could not be read");
} else {
System.out.println(alias + " (algorithm = " + key.getAlgorithm() + ", format = " + key.getFormat() + ")");
}
}
}
//Writing keys (does not work yet)
String secretExample = "my very secret secret";
SecretKey generatedSecret = SecretKeyFactory.getInstance("PBE").generateSecret(new PBEKeySpec(secretExample.toCharArray()));
ks.setKeyEntry("my.app.Secret", generatedSecret, null, null); //throws java.security.KeyStoreException: Key protection algorithm not found: java.security.KeyStoreException: Key is not a PrivateKey
ks.setKeyEntry("my.app.Secret", secretExample.getBytes(), null); //throws java.security.KeyStoreException: key is not encoded as EncryptedPrivateKeyInfo
With this I can already read some keys but have not been able to write any. The last two lines are my not working possible solutions (each throws a different exception).
I think storing passwords securely is a very general problem. I would like to address it by using the options provided by Operating Systems. How can I do that?
java.security.KeyStore
was not created to deal with passwords. It is a storage facility for cryptographic keys and certificates. While you can try to use it to store passwords since it can for example store private keys, I would advise you against that, because KeyStore's API is cumbersome as you saw yourself and was not designed for your use case.
There is the Java Keyring library. It stores passwords in:
Here's how to use it:
public static void main(String[] args) throws Exception {
Keyring keyring = Keyring.create();
String serviceName = "test-app";
String accountName = "test-account";
keyring.setPassword(serviceName, accountName, "test-password");
String password = keyring.getPassword(serviceName, accountName);
System.out.println(password);
}
Gradle
implementation 'com.github.javakeyring:java-keyring:1.0.1'
Maven
<dependency>
<groupId>com.github.javakeyring</groupId>
<artifactId>java-keyring</artifactId>
<version>1.0.1</version>
</dependency>
If you want to support desktop environments other than GNOME you would probably have to come up with your own solution or search for a different library, but this should get you started.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With