Securely manage passwords in Java applications ๐Ÿ”

Securely manage passwords in Java applications ๐Ÿ”
Photo by rc.xyz NFT gallery / Unsplash

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 isa 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

Source code

The source code can be found on my GitHub: https://github.com/camelcodes/java-keystore-example.

Hope this helps someone.

Keep coding ๐Ÿงก

Sponsorship