Learnitweb

A guide to Java KeyStore and KeyStore API

1. Introduction

In this tutorial, we’ll discuss a very important concept of security – keystore. A keystore is a storage facility for cryptographic keys and certificates and represented by java.security.KeyStore class in Java.

A KeyStore manages different types of entries and the three basic type of entries are PrivateKey, SecretKey and Certificate.

Let us discuss these three entries briefly.

  • PrivateKey: A Private key is accompanied by a certificate chain for the corresponding public key. Private keys and certificate chains are used to self-authenticate by a given entity. Private keys and certificates are used in TLS hanhshake process (used in https to make communication secure). In Java this is represented as KeyStore.PrivateKeyEntry. This entry holds a cryptographic PrivateKey. A private key is usually (but optionally) stored in a protected format because a stolen private key can lead to unauthorized access.
  • SecretKey: Secret keys are used in symmetric encryption. In Java this is represented as KeyStore.SecretKeyEntry and this entry holds a cryptographic SecretKey. Like private key, a secret key is usually (but optionally) stored in a protected format because a stolen secret key can lead to unauthorized access.
  • Certificate: A certificate is used to identify the identity of a party in communication which claims to own a public key. In Java this is represented as KeyStore.TrustedCertificateEntry. It is called a trusted certificate because the keystore owner trusts that the public key in the certificate indeed belongs to the subject (owner) of the certificate.

Typically, keystore is saved to a file system on a hard disk. By default, Java has a keystore file located at JAVA_HOME/jre/lib/security/cacerts. The default password for this keystore is changeit. The Java cacerts file stores root certificates for the most common CAs, such as VeriSign. This means that a TLS request to a server will be trusted and successful if the certificate was issued by most common and well-known CA.

For all TLS requests, Java automatically looks for a certificate (or its root CA) in its keystores. If it is found, the TLS handshake is successful. There are keystores which are called “truststores”, that do not contain any keys are and used only for trust only.

Since Java 9, the default keystore format is PKCS12. The biggest difference between JKS and PKCS12 is that JKS is a format specific to Java, while PKCS12 is a standardized and language-neutral way of storing encrypted private keys and certificates.

Note that the same password or different passwords may be used to load the keystore, to protect the private key entry, to protect the secret key entry, and to store the keystore.

2. Keystores type

Following are some of the KeyStore types:

  • jceks: The proprietary keystore implementation provided by the SunJCE provider.
  • jks: The proprietary keystore implementation provided by the SUN provider.
  • dks: A domain keystore is a collection of keystores presented as a single logical keystore.
  • pkcs11: A keystore backed by a PKCS #11 token.
  • pkcs12: The transfer syntax for personal identity information as defined in PKCS #12.

3. Java Keystore best practices

Following are some Java keystore best practices:

  • Do not use default KeyStores: A default keystore is bundled with Java at JAVA_HOME/jre/lib/security/cacerts. You should not use the default keystore for your application. Instead, you should create a keystore specifically for your application. This keystore should contain all keys, certificates (including CA certs) used by your application. This keystore should not contain any unused or expired certificates, keys etc. The javax.net.ssl.trustStore system property is used to set your custom truststore file.
  • Change the default password: The default password for the default keystore is “changeit”. If there is a need to use the default keystore, you should at least change the default password.
  • Do not save keystore passwords as plain text in properties or configuration file. It is ideal to save save keystore password in secret manager.
  • Keep private keys in a separate keystore file.
  • Use the PKCS12 format for keystores.
  • Production environment must have dedicated keystore file.
  • Keystores should not be packaged inside application archive or Jar files. This makes easy to update the keystore if required.
  • Do not Package Keystores inside Docker Containers. This makes it easy to update keystore without updating docker image.
  • Do not store keystore in Git repository.

4. Create a KeyStore

KeyStore can be created using keytool, but in this tutorial we’ll discuss how to do it programmatically.

To create the keystore implementation for the default type:

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

There are few other KeyStore types. You can have a look at this page: https://docs.oracle.com/en/java/javase/17/docs/specs/security/standard-names.html#keystore-types

To create a specific type:

KeyStore ks = KeyStore.getInstance("pkcs12");

5. Loading a keystore

Before a keystore can be accessed, it must be loaded.

KeyStore ks = KeyStore.getInstance(KeyStore.getDefaultType());

// get user password and file input stream
char[] password = "password".toCharArray();

java.io.FileInputStream fis = null;
try {
	fis = new java.io.FileInputStream("keyStoreName");
    ks.load(fis, password);
} finally {
    if (fis != null) {
        fis.close();
    }
}

The load method is used to create an empty keystore or load existing one. In order to create an empty keystore, pass null as the stream argument.

char[] pwdArray = "password".toCharArray();
ks.load(null, pwdArray);

Here, ‘password’ is provided but can be set as null. The password used to check the integrity of the keystore or to unlock the keystore.

// store away the keystore
java.io.FileOutputStream fos = null;
try {
    fos = new java.io.FileOutputStream("newKeyStoreName");
    ks.store(fos, password);
} finally {
    if (fos != null) {
        fos.close();
    }
}

6. Storing keys in keystore

In the keystore, we can store three different kinds of entries:

  • Symmetric Keys (referred to as Secret Keys in the JCE)
  • Asymmetric Keys (referred to as Public and Private Keys in the JCE)
  • Trusted Certificates

We’ll see each one of these.

The signature of setEntry method is:

public final void setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam) throws KeyStoreException

6.1 Storing symmetric keys

To save a symmetric key, we’ll need three things:

  • an alias – a name used to refer the entry
  • a key – an entry represented by KeyStore.SecretKeyEntry
  • a password – wrapped in a ProtectionParam
KeyStore.SecretKeyEntry secret = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.ProtectionParameter password = new KeyStore.PasswordProtection(passwordArray);
ks.setEntry("encryption-secret-alias", secret, password);

6.2 Saving a private key

So to save an asymmetric key, we’ll need following:

  • an alias – a name used to refer the entry
  • a private key – it is an instance of PrivateKey.
  • a password – used to access the entry. This is mandatory.
  • a certificate chain – used to show the public key
// Store the private key and a self-signed certificate in the keystore
PrivateKey privateKey = keyPair.getPrivate();
Certificate[] chain = { generateSelfSignedCertificate(privateKey) };
keyStore.setKeyEntry("myPrivateKeyAlias", privateKey, keystorePassword, chain);

6.3 Saving a trusted certificate

You can use setCertificateEntry following method to save a trusted certificate:

void setCertificateEntry(String alias, Certificate cert)
ks.setCertificateEntry("someWebsite.com", trustedCertificate);

Note that validation of the certificate is not done here. You should validate the certificate before adding to the keystore.
If an entry exists for the given alias, the existing entry is overridden by the given certificate.

7. Get all aliases

The aliases method returns an Enumeration of all aliases in KeyStore.

Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    System.out.println(alias);
}

8. Reading a single entry

The getKey method is used to read a single entry.

ks.getKey("my-key-alias", passwordArray);

The null is returned if the given alias does not exist or does not identify a key-related entry.

9. Reading a Certificate by alias

The getCertificate method is used to read certificate by alias.

Certificate getCertificate(String alias)
Certificate google = ks.getCertificate("someWebsite.com");

10. Get size of keystore

int sizeOfKeyStore = ks.size();

11. Checking if a KeyStore contains an alias

ks.containsAlias("api-secret")

12. Deleting entries

The deleteEntry is used to delete an entry from keystore.

ks.deleteEntry("my-secret-key");

To delete all the entries:

Enumeration<String> aliases = keyStore.aliases();
while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    keyStore.deleteEntry(alias);
}

13. Deleting the keystore

There is no method to delete the keystore, but you can delete the file since KeyStore is a file saved on the hard disk.

Files.delete(Paths.get(keystorePath));

14. Conclusion

Understanding KeyStore is a very important in Java security. KeyStore plays a very important in securing application and TLS communication. In this tutorial, we discussed KeyStore API. There are other methods which were not discussed here which are also important. We recommend to check the official documentation for the same.