Cheat Sheet - Java Keystores

Intro

In most cases, we use a keystore and a truststore when our application needs to communicate over SSL/TLS. The default format used for these files is JKS until Java 8.

Since Java 9, though, 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.

Keytool

In order to interact with a keystore or truststore you need the keytool found under the system-wide java path or a java version that is bundled with your application

Note: The keytool can be found in the bin directory where JAVA_HOME is your JDK Installation directory:

$ export PATH=$JAVA_HOME/bin:$PATH
$ keytool ...

By default the keytool will generate a keystore in the user’s home directory (Linux/macOS: $HOME/.keystore, Windows: $env:USERPROFILE/.keystore). Use the -keystore parameter to specify a custom path.

The Java keystore password is required and must be 6 characters or longer. Its value is changeit by default and should be changed for security purposes:

# fully interactive
$ $JAVA_HOME/bin/keytool -storepasswd -keystore /path/to/keystore.jks
Enter keystore password:  changeit
New keystore password:  new-password
Re-enter new keystore password:  new-password

# or provide the new password directly as parameter
# not recommended because the password can end up in the shell history
$ $JAVA_HOME/bin/keytool -storepasswd -new NEWPASSWORD -storepass:env KEYSTORE_PASSWORD -keystore /path/to/keystore.jks

Terminology

KeyStore Entries:

  • key - each holds very sensitive cryptographic key information, which is stored in a protected format to prevent unauthorized access. Typically, a key stored in this type of entry is a secret key, or a private key accompanied by the certificate “chain” for the corresponding public key

  • trusted certificate - each contains a single public key certificate belonging to another party. It is called a “trusted certificate” because the keystore owner trusts that the public key in the certificate indeed belongs to the identity identified by the “subject” (owner) of the certificate. The issuer of the certificate vouches for this, by signing the certificate

Java Keystore

A Java keystore stores private key entries, certificates with public keys or just secret keys that we may use for various cryptographic purposes. It stores each by an alias for ease of lookup.

Generally speaking, keystores hold keys that our application owns that we can use to prove the integrity of a message and the authenticity of the sender, say by signing payloads.

Usually, we’ll use a keystore when we are a server and want to use HTTPS. During an SSL handshake, the server looks up the private key from the keystore and presents its corresponding public key and certificate to the client.

Correspondingly, if the client also needs to authenticate itself – a situation called mutual authentication – then the client also has a keystore and also presents its public key and certificate.

There’s no default keystore, so if we want to use an encrypted channel, we’ll have to set javax.net.ssl.keyStore and javax.net.ssl.keyStorePassword. If our keystore format is different than the default, we could use javax.net.ssl.keyStoreType to customize it.

Of course, we can use these keys to service other needs as well. Private keys can sign or decrypt data, and public keys can verify or encrypt data. Secret keys can perform these functions as well. A keystore is a place that we can hold onto these keys.

Java keystore format (.jks)

env=preprod # options: <prod|preprod|int|dev>
export KEYSTORE_PASSWORD=$(aws --region eu-central-1 secretsmanager get-secret-value --secret-id "myapp-${env}_KEYSTORE_PASSWORD" | jq --raw-output '.SecretString')
 
# list all certificates (short form)
$JAVA_HOME/bin/keytool -list -keystore /opt/deploy/keystore.jks -storepass:env KEYSTORE_PASSWORD
 
# list all certificates (verbose form)
$JAVA_HOME/bin/keytool -list -v -keystore /opt/deploy/keystore.jks -storepass:env KEYSTORE_PASSWORD
 
# find by alias
$JAVA_HOME/bin/keytool -list -v -keystore /opt/deploy/keystore.jks -storepass:env KEYSTORE_PASSWORD -alias "db-client-cert"

# remove environment variable when done
unset KEYSTORE_PASSWORD

PKCS12 format (.pfx/.p12)

env=preprod # options: <prod|preprod|int|dev>
export KEYSTORE_PASSWORD=$(aws --region eu-central-1 secretsmanager get-secret-value --secret-id "myapp-${env}_KEYSTORE_PASSWORD" | jq --raw-output '.SecretString')
 
# list all certificates (short form)
$JAVA_HOME/bin/keytool -list -keystore /opt/deploy/keystore.p12 -storetype PKCS12 -storepass:env KEYSTORE_PASSWORD
 
# list all certificates (verbose form)
$JAVA_HOME/bin/keytool -list -v -keystore /opt/deploy/keystore.p12 -storetype PKCS12 -storepass:env KEYSTORE_PASSWORD
 
# find by alias
$JAVA_HOME/bin/keytool -list -v -keystore /opt/deploy/keystore.p12 -storetype PKCS12 -storepass:env KEYSTORE_PASSWORD -alias "db-client-cert"
 
# use OpenSSL instead:
openssl pkcs12 -nokeys -info -in /opt/deploy/keystore.p12 -passin pass:$KEYSTORE_PASSWORD
 
# remove environment variable when done
unset KEYSTORE_PASSWORD

Java Truststore

A truststore is the opposite - while a keystore typically holds onto certificates that identify us, a truststore holds onto certificates that identify others.

In Java, we use it to trust the third party we’re about to communicate with.

Take our earlier example. If a client talks to a Java-based server over HTTPS, the server will look up the associated key from its keystore and present the public key and certificate to the client.

We, the client, then look up the associated certificate in our truststore. If the certificate or Certificate Authorities presented by the external server is not in our truststore, we’ll get an SSLHandshakeException and the connection won’t be set up successfully.

Java has bundled a truststore called cacerts and it resides in the $JAVA_HOME/jre/lib/security directory.

It represents a system-wide keystore with CA certificates. System administrators can configure and manage that file using keytool (“jks” is assumed as the keystore type by default).

$ $JAVA_HOME/bin/keytool -list -keystore cacerts
Enter keystore password:
Keystore type: JKS
Keystore provider: SUN
 
Your keystore contains 92 entries
 
verisignclass2g2ca [jdk], 2018-06-13, trustedCertEntry,
Certificate fingerprint (SHA1): B3:EA:C4:47:76:C9:C8:1C:EA:F2:9D:95:B6:CC:A0:08:1B:67:EC:9D

We see here that the truststore contains 92 trusted certificate entries and one of the entries is the verisignclass2gca entryThis means that the JVM will automatically trust certificates signed by verisignclass2g2ca.

Here, we can override the default truststore location via the javax.net.ssl.trustStore property. Similarly, we can set javax.net.ssl.trustStorePassword and javax.net.ssl.trustStoreType to specify the truststore’s password and type:

-Djavax.net.ssl.trustStore=/my/custom/path/to/cacerts \
-Djavax.net.ssl.trustStorePassword=changeit

Additional commands

Change keystore alias name

# Changes entry with alias name "1" to "customname"
# Note: add "-storetype PKCS12" when working with pkcs12 files
$JAVA_HOME/bin/keytool -keystore keystore.jks -storepass:env KEYSTORE_PASSWORD \
  -changealias -alias "1" -destalias "customname"

Change key passphrase

$JAVA_HOME/bin/keytool -keystore keystore.jks -storepass:env KEYSTORE_PASSWORD \
  -keypasswd -alias "aliasname"

Delete keystore entry

$JAVA_HOME/bin/keytool -keystore keystore.jks -storepass:env KEYSTORE_PASSWORD \
  -delete -alias "aliasname"

Export certificate

$JAVA_HOME/bin/keytool -keystore keystore.jks -storepass:env KEYSTORE_PASSWORD \
  -export -alias "aliasname" -file myhost.pem

Keytool cannot directly export the private key so you have to use a workaround by exporting it in PKCS12 format:

Note: that we have to give the destkeypass and deststore pass the same value. This is a requirement of PKCS12 as it does not support different passwords for key store and key. If you try to give different passwords, you’ll get a warning as follows as the destkeypass will be ignored.

Warning: Different store and key passwords not supported for PKCS12 KeyStores. Ignoring user-specified -destkeypass value.

Here’s the full command:

$JAVA_HOME/bin/keytool -v -importkeystore \
  -srckeystore keystore.jks -srcalias "aliasname" -srcstorepass "storepassword" -srckeypass "keypassphrase" \
  -destkeystore myhost.pfx -destalias "aliasname" -deststorepass "password" -destkeypass "password" -deststoretype PKCS12

Import certificates

NOTE: The -importcert keytool command was named -import in previous releases. This old name is still supported in this release and will be supported in future releases, but for clarify the new name -importcert is preferred going forward.

Before adding the certificate to the keystore, keytool tries to verify it by attempting to construct a chain of trust from that certificate to a root CA, using trusted certificates that are already available in the keystore. Specifying the -trustcacerts option makes it consider additional certificates for the chain of trust, namely the certificates in the default truststore cacerts.

To proceed you need the keypair in PKCS12 (.pfx/.p12) format or ensure that they are PEM encoded (ASCII) and not DER files (binary certificates format):

PKCS12 (.pfx/.p12)

# build the certificate chain and convert the private key and certificate files into a PKCS12 file
$ cat myhost.pem intermediate.pem root.pem > myhost-with-chain.pem
$ openssl pkcs12 -export -in myhost-with-chain.pem -inkey myhost.key -name "myhost" > myhost.pfx

# import the PKCS12 file into the Java keystore
$ $JAVA_HOME/bin/keytool -importkeystore -srckeystore myhost.pfx -srcstoretype pkcs12 -destkeystore "$HOME/.keystore" -alias "myhost" 

# verify that it's available
$ $JAVA_HOME/bin/keytool -list -alias "myhost" -keystore "$HOME/.keystore"

Separate PEM files

Make sure you have the following files:

  • Certificate
  • Key
  • CA Certificate(s) (Intermediate/Root)
# add a root CA certificate or intermediate root CA certificate to the Java truststore
$ $JAVA_HOME/bin/keytool -keystore $JAVA_HOME/jre/lib/security/cacerts -trustcacerts \
  -importcert -alias "my-root-ca" -file /path/to/root-ca.cert

$ $JAVA_HOME/bin/keytool -keystore $JAVA_HOME/jre/lib/security/cacerts -trustcacerts \
  -importcert -alias "my-intermediate-ca" -file /path/to/intermediate-ca.cert

# merge the PEM encoded certificate and private key
$ cat myhost.pem myhost.key > myhost-keypair.pem

# import the keypair into the keystore
$ $JAVA_HOME/bin/keytool -importcert -alias "myhost" -file myhost-keypair.pem -keystore $HOME/.keystore -trustcacerts 

# verify with
$ $JAVA_HOME/bin/keytool -list -alias "myhost" -keystore $JAVA_HOME/jre/lib/security/cacerts