Let's get started with a Microservice Architecture with Spring Cloud:
Java KeyStore API
Last updated: February 22, 2025
1. Overview
In this tutorial, we’ll learn how to manage cryptographic keys and certificates in Java using the KeyStore API.
2. Keystores
A Keystore in Java is a secure storage mechanism used to manage cryptographic keys and certificates. It’s a collection of key entries, each identified by an alias, and can store private keys, public keys, secret keys, and trusted certificates.
Java provides different types of keystores, each having its specific format and use case:
- JKS (Java Keystore): The default keystore type used by Java. It’s primarily used for storing key pairs (private and public keys) and certificates
- PKCS12: A more widely used format for storing keys and certificates. It’s a standard format supported by many applications and tools, making it more interoperable
- JCEKS (Java Cryptography Extension Keystore): Used for storing secret keys that require stronger encryption than JKS
- BKS (Bouncy Castle Keystore): A keystore format provided by the Bouncy Castle cryptographic provider
By default, the Java runtime includes a cacerts file which is typically located in JAVA_HOME/jre/lib/security/cacerts that acts as a trusted certificate store. This file contains certificates from trusted Certificate Authorities (CAs).
The default password for accessing the cacerts file is changeit. However, it’s not recommended to use the default password for production environments. We can configure the keystore password using the -Dkeystore.password system property.
3. KeyStore Class
The KeyStore class in Java is part of the java.security package and provides a comprehensive API for managing cryptographic keys and certificates. It serves as the core class for interacting with keystores, allowing us to create, load, store, and retrieve entries securely.
Here are some of the most important methods provided by the KeyStore class:
- getInstance(String type): Creates a new KeyStore instance of the specified type (e.g., JKS, PKCS12, JCEKS)
- store(OutputStream stream, char[] password): Saves the keystore to the specified output stream, protected by the given password
- load(InputStream stream, char[] password): Loads the keystore from the specified input stream. If null is passed, it initializes an empty keystore
- setEntry(String alias, KeyStore.Entry entry, KeyStore.ProtectionParameter protParam): Adds or updates an entry (e.g., key or certificate) in the keystore under the specified alias
- getEntry(String alias, KeyStore.ProtectionParameter protParam): Retrieves an entry from the keystore using the specified alias and protection parameters
- getCertificate(String alias): Retrieves a certificate from the keystore using the specified alias
- setCertificateEntry(String alias, Certificate cert): Adds or updates a certificate entry in the keystore under the specified alias
- containsAlias(String alias): Checks if the keystore contains an entry with the specified alias
- aliases(): Returns an enumeration of all aliases in the keystore
4. Creating a Keystore
Now that we’ve established some background, let’s create our first one.
4.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");
4.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.
4.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 getInstance, load, and store each throw.
5. 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
6. 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.
6.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:
- an alias – this is simply the name that we’ll use in the future to refer to the entry
- a key – which is wrapped in a KeyStore.SecretKeyEntry
- 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.
6.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:
- an alias – like before
- 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.
- a password – used to access the entry. This time, the password is mandatory.
- 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.
6.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.
7. Reading Entries
Now that we’ve written some entries, we’ll certainly want to read them.
7.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
7.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"));
}
7.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));
}
8. 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.
9. 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);
}
10. 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 code backing this article is available on GitHub. Once you're logged in as a Baeldung Pro Member, start learning and coding on the project.
















