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. Thejavax.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.