How to Use YubiHSM 2 With PKCS#11 in Java for RSA Decryption

Recently, I had to implement a solution to keep an RSA private key safe with YubiHSM 2 and Java, also using PKCS#11. It required a bit more steps than I would’ve liked and there wasn’t much information on the web. Hence, I decided to share what’s needed to make it work.

Hendrig Sellik
RingIT Blog
8 min readFeb 1, 2021

--

Photo by Philipp Katzenberger on Unsplash

Table of Contents

· What Is an HSM?
· What Is YubiHSM 2?
· Setting Up YubiHSM 2
· PKCS#11
· Generating 2048 Bit RSA Key for PKCS#11
· Generating Authentication Key
· Configuration for Java
· PKCS#11 implementation in Java code
· Debugging YubiHSM 2
· Errors you may face when trying to use PKCS#11
· Conclusion

What Is an HSM?

HSM (Hardware Security Module) is a physical device safeguarding private keys and performing cryptographical functions. The key idea is that the private keys never leave the hardware, hence when a system gets compromised or data were stolen, there is no way to decrypt the data without having access to the HSM.

While HSM-s can be used for various cryptographic tasks, in this article, we only focus on decryption using a 2048 bit RSA key.

What Is YubiHSM 2?

HSM-s usually get installed into servers and can be as big as a graphics card or even an entire rack of a server. YubiHSM 2, however, is one of the tiniest (and cheapest) HSM-s available. It barely reaches out of the USB port.

This, however, comes at the cost of computing power and some other limitations. Be sure to check out the known issues with YubiHSM 2 before making your decision.

While using the HSM, I also measured the time that it takes for 2048 bit RSA decryption on an unoccupied YubiHSM 2 and it took ~150ms. This time will increase when many requests are incoming at once.

Setting Up YubiHSM 2

For YubiHSM 2 to work, a Connector needs to be launched first and it is not possible to use YubiHSM 2 without it. It is a layer between physical HSM and the software that uses it. Regarding the security of the connection, Yubico says that:

“The Connector is not a trusted component. Sessions are established cryptographically between the application and the YubiHSM 2 using a symmetric mutual authentication scheme that is both encrypted and authenticated.“

They continue to say that

“The Connector is not required to run on the same host as the applications which access it.”

To set up the YubiHSM you need to connect your YubiHSM 2 into your available USB slot and install the software from the Yubico releases page.

The Connector needs access to the HSM USB device, hence, for simplicity, we start the connector with sudo privileges, but the safest way to run the Connector is to use your operating system’s configuration to only give it the privileges necessary to access the YubiHSM 2 USB device.

To start the Connector, run:

sudo yubihsm-connector -d --config "/location/to/yubihsm-connector-config.yaml"

The config option is not mandatory but gives you some extra options. The contents of the config look like this.

# Listening address. Defaults to "localhost:12345".
listen: localhost:12345
# Device serial in case of multiple devices
serial: ""
# Log to syslog/eventlog. Defaults to "false".
syslog: false
# Use to enable host header filtering. Default to "false".
# Use this if there is an absolute need to use a web browser on the host where the YubiHSM 2 is installed to connect to untrusted web sites on the Internet.
enable-host-whitelist: false
# Default list for the host header filter
host-whitelist: localhost,localhost.,127.0.0.1,[::1]

Once you have started the connector. Open http://127.0.0.1:12345/connector/status to check the status of the Connector. You should see something like this.

$curl http://127.0.0.1:12345/connector/status
status=OK
serial=*
version=2.2.0
pid=303337
address=localhost
port=12345

PKCS#11

PKCS#11 or Public-Key Cryptography Standard defines a platform-independent API to communicate with cryptographic tokens. The vendor of the cryptographic device (smart card, HSM, etc.) is usually responsible for providing it. It is also possible to use PCKS#11 standard without owning an HSM with the likes of SoftHSM. For YubiHSM 2, the PKCS#11 library is available on their releases page.

Once downloaded and installed, the PKCS#11 library can be found under /usr/lib/x86_64-linux-gnu/pkcs11/yubihsm_pkcs11.so on an Ubuntu machine.

Generating 2048 Bit RSA Key for PKCS#11

To use the YubiHSM 2 device with PKCS#11 in Java, some special care must be taken when generating the RSA key. The YubiHSM 2 documentation states:

“For the JAVA implementation, a keypair can be used to perform PKCS#11 operations only if the key and its corresponding X509 certificate are stored under the same ID on the device (The value of their CKA_ID attribute is the same).”

The X509 certificate is an attestation certificate, which helps to verify that the private key used in Java originates from the HSM and is not tampered with. It is generated when using the yubihsm-setup tool for ejbca, however, it does not give the decrypt-pkcs capability to asymmetric RSA key, which is needed for decrypting using PKCS#11.

Hence, we need to generate a key and put the attestation certificate on the YubiHSM 2 device. In other words, we need to duplicate what the yubihsm-setup tool is doing, but also add the decrypt-pkcs capability to our asymmetric key.

Basically what yubihsm-setup is doing, are these 5 commands below to make the self-signed certificate.

yubihsm> generate asymmetric SESSION_ID KEY_ID LABEL DOMAINS CAPABILITIES KEY_ALGORITHMyubihsm> put opaque SESSION_ID KEY_ID LABEL DOMAINS none opaque-x509-certificate /ABSOLUTE/PATH/TO/TEMPLATE_X509_CERT.deryubihsm> attest asymmetric SESSION_ID KEY_ID KEY_ID /ABSOLUTE/PATH/TO/SELF_SIGNED_CERT.deryubihsm> delete SESSION_ID KEY_ID opaqueyubihsm> put opaque SESSION_ID KEY_ID LABEL DOMAINS CAPABILITIES opaque-x509-certificate /ABSOLUTE/PATH/TO/SELF_SIGNED_CERT.der

Some things that are important to keep in mind are

  • The certificate format must be der.
  • RSA key needs to have at least the capabilities sign-attestation-certificate, sign-pkcs, and sign-pss.
  • EC key needs to have at least the capabilities sign-ecdsa and sign-attestation-certificate.

To manually do the same as yubihsm-setup, we first need to create a template certificate.

openssl req -x509 -outform der -keyout /tmp/privkey.pem -out /tmp/TEMPLATE_X509_CERT.der

To connect to the YubiHSM 2, you need your master authentication key id and its’ secret. For example, the hexadecimal id of the key is 45bd and the secret for the key is9ga2k5jhk1234hkl.

$yubihsm-shell --connector http://127.0.0.1:12345
yubihsm> connect
yubihsm> session open 45bd 9ga2k5jhk1234hkl

Then generate the asymmetric key and import the template certificate to the yubihsm (make a note of the key alias)

yubihsm> generate asymmetric 0 0x1e16 my_key_alias 1 exportable-under-wrap:sign-pkcs:sign-pss:decrypt-pkcs,sign-attestation-certificate rsa2048yubihsm> put opaque 0 0x1e16 my_key_alias 1 none opaque-x509-certificate /tmp/TEMPLATE_X509_CERT.derThen we use the attest function to sign the self-signed certificate and the delete the template certificate and import the new self-signed certificateyubihsm> attest asymmetric 0 0x1e16 0x1e16 /tmp/SELF_SIGNED_CERT.dercopy the console output to /tmp/SELF_SIGNED_CERT.deryubihsm> delete 0 0x1e16 opaqueyubihsm> put opaque 0 0x1e16 my_key_alias 1 none opaque-x509-certificate /tmp/SELF_SIGNED_CERT.der

Currently, the yubihsm-shell does not output SELF_SIGNED_CERT.derto a file, that’s why I explicitly ask to copy it manually. However, their currently unreleased version in GitHub writes attestation certification directly to file, which is a feature that will be included in the next release of YubiHSM 2 SDK. So your experience may vary.

Generating Authentication Key

Using the master key in your application is dangerous since it allows you to do almost anything. You need a separate authentication key so you can authenticate only for specific privileges. Once you have opened a session with yubihsm-shell, run the following command.

yubihsm> put authkey 0 0x3c86 my_auth_key 1,2,3 sign-pkcs,sign-ecdsa,sign-eddsa,decrypt-pkcs,decrypt-oaep,get-pseudo-random,sign-hmac,verify-hmac,get-log-entries,sign-ssh-certificate,decrypt-otp get-pseudo-random k9fo5lsotks7

After the authentication key is created we are finally ready to move on to Java.

Configuration for Java

For the Java code to work, you need to set up the YUBIHSM_PKCS11_CONF environment variable which contains the location of the configuration file with the command

export YUBIHSM_PKCS11_CONF=/location/to/yubihsm_pkcs11.conf

To do this more permanently:

  • Open a terminal window with Ctrl+Alt+T.
  • Open the file for editing with
  • gedit ~/.profile
  • Add the command to the bottom of the file.
  • Save and close gedit.
  • Log out and log in again.

The yubihsm_pkcs11.conffile should contain at least the connector address, we’ll see later how to add debugging.

# The address of the Connector
connector=http://127.0.0.1:12345

PKCS#11 implementation in Java code

After the YubiHSM is plugged into the USB port, the Connector is running, keys are created, and the YUBIHSM_PKCS11_CONF environment is set, we are finally able to run our Java code.

The code below illustrates how to encrypt a simple message using a public key retrieved from the HSM and then decrypt the message using YubiHSM 2. The private key never leaves the HSM.

Debugging YubiHSM 2

First, you can enable a few Java VM options which might give you some hints to the output console.

-Djava.security.debug=sunpkcs11,pkcs11keystore

Secondly, you can get a debug log from the yubihsm PKCS#11 library. Just add the following options below to your yubihsm_pkcs11.conf.

# Enables debug output in the module
debug
# Redirects the debug output to a specific file. The file is created if it does not exist. The content is appended
debug-file = /location/to/yubihsm_pkcs11_debug.txt
# Enables function tracing (ingress/egress) debug output in the module
dinout
# Enables libyubihsm debug output in the module
libdebug

Errors you may face when trying to use PKCS#11

I compiled a list of errors that I encountered while implementing the Java code. Maybe you will bump into similar errors.

1) error: PKCS11 function C_Login failed: rv = CKR_ARGUMENTS_BAD (0x7)

One of the reasons this can occur is that the authentication PIN is too short. The PIN (<keyID> + <password>) needs to be between 12 and 68 characters long.

2) sun.security.pkcs11.wrapper.PKCS11Exception: CKR_ATTRIBUTE_TYPE_INVALID

The key stored on the Yubico HSM 2 is missing the attestation certificate (opaque object). Double-check the steps while creating the key.

3) javax.crypto.BadPaddingException: dofinal() failed. Caused by sun.security.pkcs11.wrapper.PKCS11Exception: CKR_FUNCTION_FAILED

The YubiHSM 2 key does not have the correct capabilities. In my case, I was missing decrypt-pkcs capability when decrypting with PKCS#11 in Java. Interestingly, the same key worked with YubiHSM 2 Java Library.

4) java.security.KeyStoreException: PKCS11 not found. Caused by: java.security.NoSuchAlgorithmException: no such algorithm: PKCS11 for provider SunPKCS11-YubiHSM

This might happen when the Connector is not working properly or the HSM is disconnected. Double-check the http://127.0.0.1:12345/connector/status page.

Conclusion

Hardware Security Modules provide an excellent way to protect your private keys and one of the cheapest ways to do it is to use YubiHSM 2. The PKCS#11 interface provides a way to use the HSM in a way that is independent of the HSM model. This means the system should continue to work without any major change in the software when you switch to a more powerful HSM later.

However, to use YubiHSM 2 with PKCS#11, in addition to launching the connector, some extra steps are needed while creating the RSA key because the setup tool does not add the decrypt-pkcs capability.

If you prefer, you can use the yubihsm-setup -d --no-new-authkey ejbca command to create the key with minimal effort. However, this key is not usable with PKCS#11, but you have to use the yubishm-java library provided by Yubico. This means that you will have an extra dependency and the Java code works only with YubiHSM 2 and not with other HSM devices.

Thanks for reading. I hope this was helpful. If you have any questions, feel free to leave a response.

Additional Reading

--

--