Securely manage passwords in Java applications ๐
Using Java KeyStore API
In this article, we are gonna talk about how to securely manage passwords in Java applications, and for this, we'll be using the Java KeyStore API.
I will not be diving into details about Key stores but you can read more here.
What is a KeyStore ?
A Java Keystore is
a key-value store.
protected by a password.
contains key-value entries.
each entry is identified by an alias.
What are the types of key stores?
A key store has three types:
jceks: The proprietary keystore implementation provided by the "SunJCE" provider.
jks: The proprietary keystore implementation provided by the "SUN" provider.
pkcs12: The transfer syntax for personal identity information as defined in PKCS12.
Since Java 9, PKCS12 is the default key store type.
Use case: Calling an external password-protected web service
In this tutorial, we'll be creating a java keystore to securely store external web service credentials. Then we will use the Java keystore API to retrieve the stored API password to use in our web service call.
We'll be using Java 11 for this project. Make sure you have Java 11 installed before proceeding.
Creating the keystore
We can create a keystore using the keytool command that packs with the JDK.
We will use these parameters for the keytool command:
-importpassword
: We are adding a keystore entry after the keystore is created
-alias API_PASSWORD_ALIAS
: This is the alias that we'll be using to retrieve the web service password from the keystore
-keystore keystore.pkcs12
: Create a keystore with the name: keystore.pkcs12
-storetype pkcs12
: The keystore type should be PKCS12
keytool -importpassword -alias API_PASSWORD_ALIAS -keystore keystore.pkcs12 -storetype pkcs12
After running this command, you will be prompted to type the keystore password, we will be using this password: a1dfs5f1sqf25aerg1uju8kig2
Then you will be prompted to type the key API_PASSWORD_ALIAS
value and that one will be: api_password_stored_into_keystore
We will store the generated keystore file in a secure location, in my case I will store it in C:\secure-folder.
The code:
We will create a new Java project with four files:
application.properties
Main.java
KeyStoreLoader.java
AppPropertiesReader.java
In our application.properties
file, we will have configuration properties for our app.
keystore.path=C:\\secure-folder\\keystore.pkcs12
keystore.type=PKCS12
keystore.password=a1dfs5f1sqf25aerg1uju8kig2
externalapi.username=user123
externalapi.password-alias=API_PASSWORD_ALIAS
In the KeyStoreLoader.java
package net.camelcodes.keystores;
import static net.camelcodes.keystores.AppPropertiesReader.appProperties;
import java.io.FileInputStream;
import java.io.IOException;
import java.security.Key;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableKeyException;
import java.security.cert.CertificateException;
import java.util.Optional;
import java.util.logging.Level;
import java.util.logging.Logger;
public class KeyStoreLoader {
Logger log = Logger.getLogger(KeyStoreLoader.class.getName());
private static final String KS_TYPE_PROPERTY = "keystore.type";
private static final String KS_PATH_PROPERTY = "keystore.path";
private static final String KS_PASSWORD_PROPERTY = "keystore.password";
private KeyStore keyStore;
private char[] keyStorePassword;
KeyStoreLoader() {
loadKeyStore();
}
public Optional<String> getStringKeyEntry(String alias) {
Optional<String> password = Optional.empty();
try {
if (!keyStore.containsAlias(alias)) {
log.log(Level.WARNING, "Keystore doesn't contain alias: {}", alias);
return password;
}
Key apiPassword = keyStore.getKey(alias, keyStorePassword);
password = Optional.of(new String(apiPassword.getEncoded()));
} catch (KeyStoreException |
NoSuchAlgorithmException |
UnrecoverableKeyException e) {
log.log(Level.SEVERE, "Unable to get key entry", e);
}
return password;
}
private void loadKeyStore() {
try {
this.keyStorePassword = appProperties
.getProperty(KS_PASSWORD_PROPERTY).toCharArray();
String keyStorePath = appProperties.getProperty(KS_PATH_PROPERTY);
String keyStoreType = appProperties.getProperty(KS_TYPE_PROPERTY);
keyStore = KeyStore.getInstance(keyStoreType);
keyStore.load(new FileInputStream(keyStorePath), keyStorePassword);
} catch (KeyStoreException |
IOException |
NoSuchAlgorithmException |
CertificateException e) {
throw new IllegalStateException("Unable to load keystore", e);
}
}
}
In the utility class AppPropertiesReader.java
we will be using the appProperties
static field to retrieve properties from the application.properties
file.
package net.camelcodes.keystores;
import java.io.IOException;
import java.io.InputStream;
import java.util.Properties;
public class AppPropertiesReader {
static final Properties appProperties = readPropertiesFile();
private AppPropertiesReader() {
}
private static Properties readPropertiesFile() {
Properties prop = new Properties();
try (InputStream stream = AppPropertiesReader.class.getResourceAsStream(
"application.properties")) {
prop.load(stream);
return prop;
} catch (IOException e) {
throw new IllegalStateException("Unable to load properties file", e);
}
}
}
And our Main.java
class will contain this
package net.camelcodes.keystores;
import static net.camelcodes.keystores.AppPropertiesReader.appProperties;
import java.util.Optional;
import java.util.logging.Logger;
public class Main {
Logger log = Logger.getLogger(Main.class.getName());
private static final String API_USERNAME_PROPERTY = "externalapi.username";
private static final String API_PASSWORD_PROPERTY = "externalapi.password-alias";
public Main() {
callExternalApi();
}
/**
* This method is simulating a web service call,
in our case will just log a message.
* <p>
* The idea is to fetch the password from the key store and
log it in the console
* The method should log the following message:
* [Mock Call] External api called with username: user123 and password: api_password_stored_into_keystore
*/
private void callExternalApi() {
// Load the key store
KeyStoreLoader ksLoader = new KeyStoreLoader();
// Fetch the web service username entry from application.properties
String apiUsername = appProperties.getProperty(API_USERNAME_PROPERTY);
// fetch the password from the keystore using
// the alias: API_PASSWORD_PROPERTY
Optional<String> apiPassword = ksLoader.getStringKeyEntry(
appProperties.getProperty(API_PASSWORD_PROPERTY));
if (apiPassword.isEmpty()) {
throw new IllegalStateException("Couldn't retreive api password from keystore");
}
log.info(String.format("[Mock Call] External api called with username: %s and password: %s",
apiUsername,
apiPassword.get()));
}
public static void main(String[] args) {
new Main();
}
}
After running the Main.java#main
method we should have this message in the console
, showing that we successfully load the password from the keystore.
[Mock Call] External api called with username: user123 and password: api_password_stored_into_keystore
The source code can be found on my GitHub: https://github.com/camelcodes/java-keystore-example.
Hope this helps someone.
Keep coding ๐งก