Course – LSS – NPI (cat=Security/Spring Security)
announcement - icon

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE

1. Overview

In this tutorial, we’ll learn how to manage cryptographic keys and certificates in Java using the KeyStore API.

2. Keystores

If we need to manage keys and certificates in Java, we need a keystore, which is simply a secure collection of aliased entries of keys and certificates.

We typically save keystores to a file system, and we can protect it with a password.

By default, Java has a keystore file located at JAVA_HOME/jre/lib/security/cacerts. We can access this keystore using the default keystore password changeit.

Now that we’ve established some background, let’s create our first one.

3. Creating a Keystore

3.1. Construction

We can easily create a keystore using keytool, or we can do it programmatically using the KeyStore API:

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

Here we used the default type, though there are a few keystore types available, like jceks or pkcs12.

We can override the default “JKS”(an Oracle-proprietary keystore protocol) type using a -Dkeystore.type parameter:

-Dkeystore.type=pkcs12

Or we can list one of the supported formats in getInstance:

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

3.2. Initialization

Initially, we need to load the keystore:

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

We use load whether we’re creating a new keystore or opening up an existing one. We’ll tell KeyStore to create a new one by passing null as the first parameter.

We also provide a password, which will be used for accessing the keystore in the future. We can also set this to null, though that would make our secrets open.

3.3. Storage

Finally, we save our new keystore to the file system:

try (FileOutputStream fos = new FileOutputStream("newKeyStoreFileName.jks")) {
    ks.store(fos, pwdArray);
}

Note that not shown above are the several checked exceptions that getInstanceload, and store each throw.

4. Loading a Keystore

To load a keystore, we first need to create a KeyStore instance, like before.

This time though, we’ll specify the format, since we’re loading an existing one:

KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("newKeyStoreFileName.jks"), pwdArray);

If our JVM doesn’t support the keystore type we passed, or if it doesn’t match the type of the keystore on the filesystem that we’re opening, we’ll get a KeyStoreException:

java.security.KeyStoreException: KEYSTORE_TYPE not found

Also, if the password is wrong, we’ll get an UnrecoverableKeyException:

java.security.UnrecoverableKeyException: Password verification failed

5. Storing Entries

In the keystore, we can store three different kinds of entries, each under its alias:

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

Let’s take a look at each one.

5.1. Saving a Symmetric Key

The simplest thing we can store in a keystore is a Symmetric Key.

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

  1. an alias – this is simply the name that we’ll use in the future to refer to the entry
  2. a key – which is wrapped in a KeyStore.SecretKeyEntry
  3. a password – which is wrapped in what is called a ProtectionParam
KeyStore.SecretKeyEntry secret
 = new KeyStore.SecretKeyEntry(secretKey);
KeyStore.ProtectionParameter password
 = new KeyStore.PasswordProtection(pwdArray);
ks.setEntry("db-encryption-secret", secret, password);

Keep in mind that the password can’t be null; however, it can be an empty String. If we leave the password null for an entry, we’ll get a KeyStoreException:

java.security.KeyStoreException: non-null password required to create SecretKeyEntry

It may seem a little weird that we need to wrap the key and the password in wrapper classes.

We wrap the key because setEntry is a generic method that can be used for the other entry types as well. The type of entry allows the KeyStore API to treat it differently.

We wrap the password because the KeyStore API supports callbacks to GUIs and CLIs to collect the password from the end user. We can check out the KeyStore.CallbackHandlerProtection Javadoc for more details.

We can also use this method to update an existing key; we just need to call it again with the same alias and password and our new secret.

5.2. Saving a Private Key

Storing asymmetric keys is a bit more complex, since we need to deal with certificate chains.

The KeyStore API gives us a dedicated method called setKeyEntry, which is more convenient than the generic setEntry method.

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

  1. an alias – like before
  2. a private key – because we aren’t using the generic method, the key won’t get wrapped. Also, in our case, it should be an instance of PrivateKey.
  3. a password – used to access the entry. This time, the password is mandatory.
  4. a certificate chain – this certifies the corresponding public key
X509Certificate[] certificateChain = new X509Certificate[2];
chain[0] = clientCert;
chain[1] = caCert;
ks.setKeyEntry("sso-signing-key", privateKey, pwdArray, certificateChain);

Lots can go wrong here, of course, like if pwdArray is null:

java.security.KeyStoreException: password can't be null

But there’s a really strange exception to be aware of, which occurs if pwdArray is an empty array:

java.security.UnrecoverableKeyException: Given final block not properly padded

To update, we can simply call the method again with the same alias and a new privateKey and certificateChain.

It might also be valuable to do a quick refresher on how to generate a certificate chain.

5.3. Saving a Trusted Certificate

Storing trusted certificates is quite simple. It only requires the alias and the certificate itself, which is of type Certificate:

ks.setCertificateEntry("google.com", trustedCertificate);

Usually, the certificate is one that we didn’t generate, and instead came from a third-party.

Because of that, it’s important to note here that KeyStore doesn’t actually verify this certificate. We should verify it on our own before storing it.

To update, we can simply call the method again with the same alias and a new trustedCertificate.

6. Reading Entries

Now that we’ve written some entries, we’ll certainly want to read them.

6.1. Reading a Single Entry

First, we can pull keys and certificates out by their alias:

Key ssoSigningKey = ks.getKey("sso-signing-key", pwdArray);
Certificate google = ks.getCertificate("google.com");

If there’s no entry by that name, or it’s of a different type, then getKey simply returns null:

public void whenEntryIsMissingOrOfIncorrectType_thenReturnsNull() {
    // ... initialize keystore
    // ... add an entry called "widget-api-secret"

   Assert.assertNull(ks.getKey("some-other-api-secret"));
   Assert.assertNotNull(ks.getKey("widget-api-secret"));
   Assert.assertNull(ks.getCertificate("widget-api-secret")); 
}

But if the password for the key is wrong, we’ll get that same odd error we talked about earlier:

java.security.UnrecoverableKeyException: Given final block not properly padded

6.2. Checking if a Keystore Contains an Alias

Since KeyStore just stores entries using a Map, it exposes the ability to check for existence without retrieving the entry:

public void whenAddingAlias_thenCanQueryWithoutSaving() {
    // ... initialize keystore
    // ... add an entry called "widget-api-secret"
    assertTrue(ks.containsAlias("widget-api-secret"));
    assertFalse(ks.containsAlias("some-other-api-secret"));
}

6.3. Checking the Kind of Entry

KeyStore#entryInstanceOf is a bit more powerful.

It’s similar to containsAlias, except it also checks the entry type:

public void whenAddingAlias_thenCanQueryByType() {
    // ... initialize keystore
    // ... add a secret entry called "widget-api-secret"
    assertTrue(ks.containsAlias("widget-api-secret"));
    assertFalse(ks.entryInstanceOf(
      "widget-api-secret",
      KeyType.PrivateKeyEntry.class));
}

7. Deleting Entries

KeyStore, of course, supports deleting the entries we’ve added:

public void whenDeletingAnAlias_thenIdempotent() {
    // ... initialize a keystore
    // ... add an entry called "widget-api-secret"
    assertEquals(ks.size(), 1);
    ks.deleteEntry("widget-api-secret");
    ks.deleteEntry("some-other-api-secret");
    assertFalse(ks.size(), 0);
}

Fortunately, deleteEntry is idempotent, so the method reacts the same whether the entry exists or not.

8. Deleting a Keystore

If we want to delete our keystore, the API is no help to us, but we can still use Java to do it:

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

Or, as an alternative, we can keep the keystore around and just remove entries:

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

9. Conclusion

In this article, we learned how to manage certificates and keys using KeyStore API. We discussed what a keystore is, and explored how to create, load and delete one. We also demonstrated how to store a key or certificate in the keystore, and how to load and update existing entries with new values.

The full implementation of the example can be found over on Github.

Course – LSS (cat=Security/Spring Security)

I just announced the new Learn Spring Security course, including the full material focused on the new OAuth2 stack in Spring Security:

>> CHECK OUT THE COURSE
res – Security (video) (cat=Security/Spring Security)
Comments are closed on this article!