Install SoftHSMv2 and use it via OpenSSL and PKCS #11

SoftHSM is an implementation of a cryptographic store accessible through a PKCS #11 interface. You can use it to explore PKCS #11 without having a hardware security module. It is being developed as a part of the OpenDNSSEC project. SoftHSM uses Botan for its cryptographic operations.

The basics

What is a hardware security module or HSM? A hardware security module is a secure physical device designed to generate, store, and protect digital, high-value cryptographic keys. It is a secure crypto-processor that often comes in the form of a plug-in card with built-in tamper protection.

Due to the critical role they play in securing applications and infrastructure, HSMs and/or the cryptographic modules are typically certified to internationally recognized standards such as Common Criteria or FIPS 140 to provide users with independent assurance that the design and implementation of the product and cryptographic algorithms are sound. The highest level of FIPS 140 security certification attainable is Security Level 4 (Overall). When used in financial payments applications, the security of an HSM is often validated against the HSM requirements defined by the Payment Card Industry Security Standards Council.

When we talk about HSM, we often hear about FIPS 140. Developed by the National Institute of Standards and Technology (NIST), Federal Information Processing Standards are used by United States government agencies and contractors in non-military computer systems. FIPS 140 series comprises the U.S. government computer security standards that define requirements for cryptography modules, including both hardware and software components. The current version is FIPS 140-2.

FIPS 140 enforces strong cryptographic algorithms, provides good physical security, and requires power-on self tests to ensure a device is still in compliance before operating. FIPS 140-2 evaluation is required of vendors that sell products implementing cryptography to the federal government, and the financial industry is increasingly specifying FIPS 140-2 as a procurement requirement.

A hardware security module can be employed in any application that uses digital keys. Typically the keys would be of high value - meaning there would be a significant, negative impact to the owner of the key if it were compromised. Here are the sectors in which we often deal with HSMs:

What is PKCS #11? In cryptography, PKCS #11 is one of the Public-Key Cryptography Standards, and also refers to the programming interface to create and manipulate cryptographic tokens (a token where the secret is a cryptographic key). The PKCS #11 standard defines a platform-independent API to cryptographic tokens, such as hardware security modules and smart cards, and names the API itself “Cryptoki” (from “cryptographic token interface” and pronounced as “crypto-key”, although “PKCS #11” is often used to refer to the API as well as the standard that defines it).

According to its name, SoftHSM is a contradiction in terms? Yes, that’s right! SoftHSM cryptography is done software only! Looking at the source code everything its based on OpenSSL library calls only. There is PIN and PUK implemented, but as keys are stored in files and not stored in hardware.

Information and requirements

These elements are to be taken into consideration to follow this article:

Update the system

sudo dnf -y update

Install development tools

sudo dnf -y groupinstall "Development Tools"

Install required packages

sudo dnf -y install opensc openssl-devel vim

Download SoftHSMv2

Check the latest available version here.

curl -LOsSf https://dist.opendnssec.org/source/softhsm-2.6.1.tar.gz

Extract source code

tar -xzf softhsm-2.6.1.tar.gz

Increase the maximum locked memory

For optimal performance and increased security of SoftHSMv2, it is recommended to increase the maximum size of memory that a process can “lock” in RAM to prevent it from swapping.

First, to known this value, you can execute the following command.

ulimit -l
64

In my case, a process can lock maximum 64 KB of memory. This parameter should be set to unlimited.

To do this, open the /etc/security/limits.conf file and add the following line just after #<domain> <type> <item> <value>.

*               -       memlock         unlimited

You must log out and log back in to see this value changed.

ulimit -l
unlimited

Compile and install the library

OpenSSL 1.1.0 and later no longer include the GOST engine, so you must use the --disable-gost flag when configuring.

cd softhsm-2.6.1
./configure --disable-gost
make
sudo make install

Check if everything is correctly installed

Shared library

find /usr/local/lib -type f -iname "libsofthsm2.so" -exec file {} \; 2> /dev/null
/usr/local/lib/softhsm/libsofthsm2.so: ELF 64-bit LSB shared object, x86-64, version 1 (SYSV), dynamically linked, BuildID[sha1]=da3860cb692a67ad45be066d86ecfd97f0c0ec3d, with debug_info, not stripped

Looks good for the shared library.

Binaries

find /usr/local/bin/ -type f -iname "softhsm2-*" 2> /dev/null
/usr/local/bin/softhsm2-keyconv
/usr/local/bin/softhsm2-util
/usr/local/bin/softhsm2-dump-file

Looks good for the binaries.

Configure token

You can use either SoftHSMv2 binaries (here softhsm2-util) or the PKCS #11 interface. The SO PIN can be used to re-initialize the token and the User PIN is handed out to the application so it can interact with the token.

List all the available slots

softhsm2-util --show-slots
Available slots:
Slot 0
    Slot info:
        Description:      SoftHSM slot ID 0x0
        Manufacturer ID:  SoftHSM project
        Hardware version: 2.6
        Firmware version: 2.6
        Token present:    yes
    Token info:
        Manufacturer ID:  SoftHSM project
        Model:            SoftHSM v2
        Hardware version: 2.6
        Firmware version: 2.6
        Serial number:
        Initialized:      no
        User PIN init.:   no
        Label:

As you can see, the slot 0 is not initialized Initialized: no and User PIN init.: no.

Initialize token

You can use the flag --free to use the first free/uninitialized token. You can define the label you want. Type in SO PIN and User PIN. Once a token has been initialized, more slots will be added automatically with a new uninitialized token.

softhsm2-util --init-token --free --label "Test token" --so-pin 123456789 --pin 123456
Slot 0 has a free/uninitialized token.
The token has been initialized and is reassigned to slot 31331403

Check

Our token seems to be initialized, we can check it with this command.

softhsm2-util --show-slots
Available slots:
Slot 31331403
    Slot info:
        Description:      SoftHSM slot ID 0x1de144b
        Manufacturer ID:  SoftHSM project
        Hardware version: 2.6
        Firmware version: 2.6
        Token present:    yes
    Token info:
        Manufacturer ID:  SoftHSM project
        Model:            SoftHSM v2
        Hardware version: 2.6
        Firmware version: 2.6
        Serial number:    2987ba2681de144b
        Initialized:      yes
        User PIN init.:   yes
        Label:            Test token
Slot 1
    Slot info:
        Description:      SoftHSM slot ID 0x1
        Manufacturer ID:  SoftHSM project
        Hardware version: 2.6
        Firmware version: 2.6
        Token present:    yes
    Token info:
        Manufacturer ID:  SoftHSM project
        Model:            SoftHSM v2
        Hardware version: 2.6
        Firmware version: 2.6
        Serial number:
        Initialized:      no
        User PIN init.:   no
        Label:

You can create another token by executing again the --init-token command.

How to use SoftHSMv2

Now that you have a slot at your disposal, you can create your very first RSA key pair! There are two ways to do this. The first is to use the pkcs11-tool, an utility for managing and using PKCS #11 security tokens. The second is to use a PKCS11 #11 implementation in a language where it is available: Java, Python, C, C++, Rust, Golang… I will show you both ways. Concerning the second way, I will use the Golang programming language.

pkcs11-tool

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --login --keypairgen --usage-sign --usage-decrypt --key-type rsa:4096 --label RSA4096SD
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Key pair generated:
Private Key Object; RSA
  label:      RSA4096SD
  Usage:      decrypt, sign, unwrap
  Access:     sensitive, always sensitive, never extractable, local
Public Key Object; RSA 4096 bits
  label:      RSA4096SD
  Usage:      encrypt, verify, wrap
  Access:     local

Flags explanation:

The private key, for obvious reasons, can never be extracted. However, you can extract your public key if you need it.

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --read-object --type pubkey --label RSA4096SD --output-file pubkey.der

Flags explanation:

I specified .der for the output file type because when you extract an object it is a Distinguished Encoding Rules (DER) encoded in ASN.1.

You can validate it by using an OpenSSL command.

openssl asn1parse -in pubkey.der -inform DER
    0:d=0  hl=4 l= 546 cons: SEQUENCE
    4:d=1  hl=2 l=  13 cons: SEQUENCE
    6:d=2  hl=2 l=   9 prim: OBJECT            :rsaEncryption
   17:d=2  hl=2 l=   0 prim: NULL
   19:d=1  hl=4 l= 527 prim: BIT STRING

You can also have another view with the dumpasn1 tool.

dumpasn1 -d pubkey.der
  0 546: SEQUENCE {
  4  13: . SEQUENCE {
  6   9: . . OBJECT IDENTIFIER rsaEncryption (1 2 840 113549 1 1 1)
 17   0: . . NULL
       : . . }
 19 527: . BIT STRING, encapsulates {
 24 522: . . SEQUENCE {
 28 513: . . . INTEGER
       : . . . . 00 D7 58 76 E4 75 82 72 23 D4 0E 24 6C 4F F9 BB
       : . . . . 40 B7 11 48 5D 2C 9A 8A 31 62 84 0C CF C1 B2 7D
       : . . . . 35 DA 7D B3 41 DC 63 E5 5B 0E DB 17 CE 92 0B BF
       : . . . . 9E 7E 7D 6C D6 3A C4 7C 57 7B 10 26 D8 07 40 6B
       : . . . . 56 1B 3D AF 7F FD F5 7A 68 19 08 54 55 28 D4 5D
       : . . . . 77 FF C6 1D 66 27 AF 40 82 AB 3C E4 8E 99 3B 81
       : . . . . A3 42 BB 6D 0E B1 AB 1F 64 C9 8C 5F 63 ED B5 0C
       : . . . . FF 91 70 F2 B3 6C 08 8F 82 93 F3 AA 26 FF 7B 6C
       : . . . . . . . . [ Another 385 bytes skipped ]
545   3: . . . INTEGER 65537
       : . . . }
       : . . }
       : . }

0 warnings, 0 errors.

Or transform the public key to PEM format. A .pem file is just a Base64 encoded .der file.

openssl rsa -pubin -in pubkey.der -inform DER -outform PEM -text -noout
RSA Public-Key: (4096 bit)
Modulus:
    00:d7:58:76:e4:75:82:72:23:d4:0e:24:6c:4f:f9:
    bb:40:b7:11:48:5d:2c:9a:8a:31:62:84:0c:cf:c1:
    b2:7d:35:da:7d:b3:41:dc:63:e5:5b:0e:db:17:ce:
    92:0b:bf:9e:7e:7d:6c:d6:3a:c4:7c:57:7b:10:26:
    d8:07:40:6b:56:1b:3d:af:7f:fd:f5:7a:68:19:08:
    54:55:28:d4:5d:77:ff:c6:1d:66:27:af:40:82:ab:
    3c:e4:8e:99:3b:81:a3:42:bb:6d:0e:b1:ab:1f:64:
    c9:8c:5f:63:ed:b5:0c:ff:91:70:f2:b3:6c:08:8f:
    82:93:f3:aa:26:ff:7b:6c:cf:b0:84:3b:b5:af:91:
    14:3e:f3:0a:6b:80:6f:b5:44:78:6b:06:3e:a3:bd:
    4d:be:72:09:2d:b5:50:29:9b:07:e7:bf:35:be:01:
    b4:1c:ac:c7:31:a8:95:a2:98:c7:2f:36:1e:d5:8f:
    66:9f:fe:ab:09:a5:92:5f:69:3e:79:8a:2e:ea:90:
    06:53:fe:28:e6:86:c2:50:97:ef:f1:13:4c:d9:ff:
    d5:c4:98:32:e1:f1:c0:9a:68:02:e9:e9:c7:98:f5:
    76:9b:9b:21:d2:14:2f:14:93:6c:49:5b:ca:36:ce:
    09:ed:52:cc:5c:bc:85:56:53:54:58:79:35:6f:1c:
    ce:4e:1d:25:80:da:1f:20:c0:ac:1b:6a:ad:d0:ec:
    57:5c:8a:f9:4b:4a:55:30:ee:5c:40:af:3d:12:fc:
    72:11:12:09:1a:dc:2b:03:83:6b:03:5c:7d:e3:32:
    d9:0b:73:a9:61:73:5e:e9:33:b7:c5:c5:4f:29:7b:
    e0:10:45:ab:4b:20:6a:40:32:62:8b:b6:17:7c:9a:
    9b:ea:ac:7e:39:49:a7:1a:62:08:b7:db:a4:5a:1e:
    b0:53:c6:94:26:77:fa:e1:59:60:4c:3a:f2:11:05:
    69:58:5d:cd:5b:f9:1c:32:5a:3a:5f:54:34:24:7b:
    fa:d1:66:42:be:1e:41:10:92:0b:e7:92:6b:06:ec:
    05:e4:bb:69:9f:50:1c:72:22:35:e3:ca:d3:1d:2a:
    72:f9:32:9f:ea:08:ba:f9:b6:ff:7f:54:c0:d0:b9:
    fb:d6:c0:f4:c8:3a:a9:05:74:ee:cc:28:49:c4:28:
    f0:65:e2:75:cb:0c:e4:d6:96:f6:05:53:1e:3e:3d:
    02:c3:6c:fe:64:f1:02:ce:ee:a7:5b:29:f6:05:2a:
    d4:17:64:4d:64:17:15:3d:14:fd:9f:66:c5:31:26:
    e5:ef:02:7a:0b:16:03:41:96:61:e3:96:28:64:cd:
    05:80:3c:2b:16:a9:bb:65:52:e7:93:6f:c8:0f:c4:
    27:1a:75
Exponent: 65537 (0x10001)

Cipher

First, you must convert your .der file in a .pem file.

openssl rsa -pubin -in pubkey.der -inform DER -outform PEM -out pubkey.pem
writing RSA key

Then, you can cipher a file.

echo "Hello, world!" | tee -a hello.txt
Hello, world!
openssl rsautl -encrypt -inkey pubkey.pem -in hello.txt -pubin -out hello.txt.ciphered

Decipher

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --decrypt --input-file hello.txt.chiphered --mechanism RSA-PKCS
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Using decrypt algorithm RSA-PKCS
Hello, world!

Flags are quite easy to understand. Concerning the --mechanism RSA-PKCS flag, this is the PKCS #1 RSA mechanism, denoted CKM_RSA_PKCS. It’s a multi-purpose mechanism based on the RSA public-key cryptosystem and the block formats defined in PKCS #1. It supports single-part encryption and decryption, single-part signatures and verification with and without message recovery, key wrapping, and key unwrapping. This mechanism corresponds only to the part of PKCS #1 that involves RSA; it does not compute a message digest or a DigestInfo encoding as specified for the md2withRSAEncryption and md5withRSAEncryption algorithms in PKCS #1.

We recover our Hello, World!.

Sign

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --sign --mechanism SHA512-RSA-PKCS-PSS --input-file hello.txt.ciphered --output-file hello.txt.sig
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Using signature algorithm SHA512-RSA-PKCS-PSS
PSS parameters: hashAlg=SHA512, mgf=MGF1-SHA512, salt_len=64 B

Once again, flags are quite easy to understand. Concerning the --machanism SHA512-RSA-PKCS1-PSS flag, denoted CKM_SHA512_RSA_PKCS_PSS perform the same operations using the SHA-512 hash functions.

Verify

openssl dgst -keyform DER -verify pubkey.der -sha512 -sigopt rsa_padding_mode:pss -sigopt rsa_pss_saltlen:auto -signature hello.txt.sig hello.txt.ciphered
Verified OK

Hash

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --sign --mechanism SHA384-RSA-PKCS --input-file hello.txt.ciphered --output-file hello.txt.sig.hash
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:
Using signature algorithm SHA384-RSA-PKCS
openssl rsautl -verify -inkey pubkey.pem -in hello.sig.hash -pubin | openssl asn1parse -inform DER
    0:d=0  hl=2 l=  65 cons: SEQUENCE
    2:d=1  hl=2 l=  13 cons: SEQUENCE
    4:d=2  hl=2 l=   9 prim: OBJECT            :sha384
   15:d=2  hl=2 l=   0 prim: NULL
   17:d=1  hl=2 l=  48 prim: OCTET STRING      [HEX DUMP]:79A7AEC70847C242102B889B298A0720803B340B350CD9E89F574C684D46BFB232B92D2DF356FD77E4D2047C43B3F8A0
sha384sum hello.txt
79a7aec70847c242102b889b298a0720803b340b350cd9e89f574c684d46bfb232b92d2df356fd77e4d2047c43b3f8a0  hello.txt

Pretty easy, isn’t it?

Before proceeding to the next step, I suggest that you delete the keys and files that have been created to avoid any ambiguity.

Delete the private key

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --login --delete-object --type privkey -a RSA4096SD
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:

Delete the public key

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --login --delete-object --type pubkey -a RSA4096SD
Using slot 0 with a present token (0x1de144b)
Logging in to "Test token".
Please enter User PIN:

Remove files

rm -f hello.*

PKCS #11 implementation in Golang

I like Golang and we are lucky, a PKCS #11 implementation is available in this language. The first thing to do is to install Golang.

Install Golang

curl -LOsSf https://go.dev/dl/go1.18.3.linux-amd64.tar.gz
sudo tar -xzf go1.18.3.linux-amd64.tar.gz -C /usr/local

Add the following lines to ~/.bash_profile.

PATH=$PATH:/usr/local/go/bin
export PATH

Source your ~/.bash_profile file.

source ~/.bash_profile

Check.

go version
go version go1.18.3 linux/amd64

Create the Go’s directories.

mkdir -p ~/go/{pkg,src,bin}

Create the folder where our code will be located.

mkdir -p ~/go/src/playground
cd $_

Initialize the Go’s mod to write our code.

go mod init playground
go: creating new go.mod: module playground

Download the library.

go get github.com/miekg/pkcs11
go: downloading github.com/miekg/pkcs11 v1.1.1
go get: added github.com/miekg/pkcs11 v1.1.1

Generate RSA key pair

Create a file called main.go and add the following content. I’ll let you browse the comments to understand what the code does.

In the main() function, we call the different functions.

package main

import (
	"fmt"
	"github.com/miekg/pkcs11"
	"github.com/miekg/pkcs11/p11"
	"log"
)

var (
	slotID      uint = 31331403
	pin              = "123456"
	objectLabel      = "RSA4096SD"
)

func main() {
	if err := generateRSAKeyPair(slotID, pin, objectLabel); err != nil {
		log.Fatalln(err)
	}

	log.Println("RSA key pair generated!")
}

Also, don’t forget to add the two functions below that manage the retrieval of available slots.

// getAvailableSlots retrieves available slots and return them
func getAvailableSlots(module p11.Module) ([]p11.Slot, error) {
	slots, err := module.Slots()
	if err != nil {
		return nil, err
	}
	return slots, nil
}

// findRequestedSlot finds the requested slot given in parameter and return its instance if exists
func findRequestedSlot(module p11.Module, slotID uint) (*p11.Slot, error) {
	slots, err := getAvailableSlots(module)
	if err != nil {
		return nil, err
	}

	var slot *p11.Slot

	found := false

	// Loop through the available slots returned by the function getAvailableSlots
	for _, availableSlot := range slots {
		if availableSlot.ID() == slotID {
			// Fill the structure
			slot = &availableSlot
			found = true
			break
		}
	}

	if !found {
		return nil, fmt.Errorf("failed to find slot with ID: %d", slotID)
	}

	return slot, nil
}
// generateRSAKeyPairRequest generates a request to generate an RSA key pair
func generateRSAKeyPairRequest(objectLabel string) p11.GenerateKeyPairRequest {
	publicKeyTemplate := []*pkcs11.Attribute{
		// The object type
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
		// The type of the key
		pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
		// The CKA_TOKEN attribute identifies whether the object is a token object or a session object
		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
		// The CKA_VERIFY attribute of the verification key, which indicates whether the key supports verification where the signature is an appendix to the data
		pkcs11.NewAttribute(pkcs11.CKA_VERIFY, true),
		// The CKA_ENCRYPT attribute of the encryption key, which indicates whether the key supports encryption
		pkcs11.NewAttribute(pkcs11.CKA_ENCRYPT, true),
		// Public exponent e, here 65537
		pkcs11.NewAttribute(pkcs11.CKA_PUBLIC_EXPONENT, []byte{1, 0, 1}),
		// Key length
		pkcs11.NewAttribute(pkcs11.CKA_MODULUS_BITS, 4096),
		// The CKA_LABEL attribute is intended to assist users in browsing
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
	}

	privateKeyTemplate := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_KEY_TYPE, pkcs11.CKK_RSA),
		pkcs11.NewAttribute(pkcs11.CKA_TOKEN, true),
		// The CKA_DECRYPT attribute of the decryption key, which indicates whether the key supports decryption
		pkcs11.NewAttribute(pkcs11.CKA_DECRYPT, true),
		// When the CKA_PRIVATE attribute is CK_TRUE (true), a user may not access the object until the user has been authenticated to the token.
		pkcs11.NewAttribute(pkcs11.CKA_PRIVATE, true),
		// The CKA_EXTRACTABLE attribute is CK_FALSE (false), then certain attributes of the secret key cannot be revealed in plaintext outside the token
		pkcs11.NewAttribute(pkcs11.CKA_EXTRACTABLE, false),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
	}

	// Return the request with the different information
	return p11.GenerateKeyPairRequest{
		Mechanism:            *pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS_KEY_PAIR_GEN, nil),
		PublicKeyAttributes:  publicKeyTemplate,
		PrivateKeyAttributes: privateKeyTemplate,
	}
}

// generateRSAKeyPair generates an RSA key pair based on a request
func generateRSAKeyPair(slotID uint, pin, objectLabel string) error {
	var slot *p11.Slot
	var session p11.Session

	// Load the shared library
	module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
	defer module.Destroy()
	if err != nil {
		return err
	}

	// Check if the slot ID given in parameter is available
	slot, err = findRequestedSlot(module, slotID)
	if err != nil {
		return err
	}

	// Open a RW session
	session, err = slot.OpenWriteSession()
	if err != nil {
		return err
	}

	// Login to the token, pin is the User PIN defined during the `softhsm2-util --init-token` command
	if err = session.Login(pin); err != nil {
		return err
	}

	// Generate the RSA key pair with label `RSA4096SD`
	request := generateRSAKeyPairRequest(objectLabel)

	_, err = session.GenerateKeyPair(request)
	if err != nil {
		return err
	}

	return nil
}
go run main.go
2022/06/14 23:02:34 RSA key pair generated!

You have just created your RSA key via PKCS #11! You can check if it is in your token with a pkcs11-tool command.

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --list-objects
Using slot 0 with a present token (0x1de144b)
Public Key Object; RSA 4096 bits
  label:      RSA4096SD
  Usage:      encrypt, verify, wrap
  Access:     local
Public Key Object; RSA 4096 bits
  label:      RSA4096SD
  Usage:      encrypt, verify, wrap
  Access:     local

It’s right there, it’s the one with the RSA4096SD label.

Cipher

func main() {
	if err := cipherData(slotID, objectLabel, "String to cipher"); err != nil {
		log.Fatalln(err)
	}

	log.Println("Ciphered data written into text.ciphered")
}
// cipherData ciphers data with a public key
func cipherData(slotID uint, objectLabel, data string) error {
	var slot *p11.Slot
	var session p11.Session
	var publicKeyObject p11.Object
	var cipheredData []byte

	// Load the shared library
	module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
	defer module.Destroy()
	if err != nil {
		return err
	}

	// Check if the slot ID given in parameter is available
	slot, err = findRequestedSlot(module, slotID)
	if err != nil {
		return err
	}

	// Open a RO session
	session, err = slot.OpenSession()
	if err != nil {
		return err
	}

	// Create a template to find the public key
	template := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
	}

	// Find the object based on template
	publicKeyObject, err = session.FindObject(template)
	if err != nil {
		return err
	}

	// Cast the p11.Object to p11.PublicKey
	publicKey := p11.PublicKey(publicKeyObject)

	// Cipher the message
	cipheredData, err = publicKey.Encrypt(*pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil), []byte(data))
	if err != nil {
		return err
	}

	// Write the ciphered data to a file
	if err = os.WriteFile("text.ciphered", cipheredData, 0644); err != nil {
		return err
	}

	return nil
}
go run main.go
2022/06/14 23:31:51 Ciphered data written into text.ciphered

Decipher

func main() {
	message, err := decipherData(slotID, pin, objectLabel)
	if err != nil {
		log.Fatalln(err)
	}

	fmt.Printf("Data deciphered from text.ciphered file: %s\n", message)
}
// decipherData deciphers data with a private key
func decipherData(slotID uint, pin, objectLabel string) (string, error) {
	var slot *p11.Slot
	var session p11.Session
	var privateKeyObject p11.Object
	var data []byte
	var decipheredMessage []byte

	// Load the shared library
	module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
	defer module.Destroy()
	if err != nil {
		return "", err
	}

	// Check if the slot ID given in parameter is available
	slot, err = findRequestedSlot(module, slotID)

	// Open a RO session
	session, err = slot.OpenSession()

	// Login to the token, pin is the User PIN defined during the `softhsm2-util --init-token` command
	if err = session.Login(pin); err != nil {
		return "", err
	}

	// Create a template to find the private key
	template := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
	}

	// Find the object based on template
	privateKeyObject, err = session.FindObject(template)
	if err != nil {
		return "", err
	}

	// Cast the p11.Object to p11.PrivateKey
	privateKey := p11.PrivateKey(privateKeyObject)

	// Read the ciphered data
	data, err = os.ReadFile("./text.ciphered")
	if err != nil {
		return "", err
	}

	// Decipher the data
	decipheredMessage, err = privateKey.Decrypt(*pkcs11.NewMechanism(pkcs11.CKM_RSA_PKCS, nil), data)
	if err != nil {
		return "", err
	}

	return string(decipheredMessage), nil
}
go run main.go
Data deciphered from text.ciphered file: String to cipher

Sign

func main() {
	if err := signData(slotID, pin, objectLabel); err != nil {
		log.Fatalln(err)
	}

	fmt.Println("Signed data written into text.sig")
}
// signData signs data with a private key
func signData(slotID uint, pin, objectLabel string) error {
	var slot *p11.Slot
	var session p11.Session
	var privateKeyObject p11.Object
	var data []byte
	var signedData []byte

	// Load the shared library
	module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
	defer module.Destroy()
	if err != nil {
		return err
	}

	// Check if the slot ID given in parameter is available
	slot, err = findRequestedSlot(module, slotID)
	if err != nil {
		return err
	}

	// Open a RO session
	session, err = slot.OpenSession()
	if err != nil {
		return err
	}

	// Login to the token, pin is the User PIN defined during the `softhsm2-util --init-token` command
	if err = session.Login(pin); err != nil {
		return err
	}

	// Create a template to find the private key
	template := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PRIVATE_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
	}

	// Find the object based on template
	privateKeyObject, err = session.FindObject(template)
	if err != nil {
		return err
	}

	// Cast the p11.Object to p11.PrivateKey
	privateKey := p11.PrivateKey(privateKeyObject)

	// Read the ciphered data
	data, err = os.ReadFile("text.ciphered")
	if err != nil {
		return err
	}

	// Sign data
	signedData, err = privateKey.Sign(*pkcs11.NewMechanism(pkcs11.CKM_SHA384_RSA_PKCS, nil), data)
	if err != nil {
		return err
	}

	// Write the signed data to a file
	if err = os.WriteFile("text.sig", signedData, 0644); err != nil {
		return err
	}

	return nil
}
go run main.go
Signed data written into text.sig

Verify

func main() {
	if err := verifySignedData(slotID, objectLabel); err != nil {
		log.Fatalln(err)
	}

	fmt.Println("Verification OK!")
}
// verifySignedData verifies signed data with a public key
func verifySignedData(slotID uint, objectLabel string) error {
	var slot *p11.Slot
	var session p11.Session
	var publicKeyObject p11.Object
	var data []byte
	var signedData []byte

	// Load the shared library
	module, err := p11.OpenModule("/usr/local/lib/softhsm/libsofthsm2.so")
	defer module.Destroy()
	if err != nil {
		return err
	}

	// Check if the slot ID given in parameter is available
	slot, err = findRequestedSlot(module, slotID)
	if err != nil {
		return err
	}

	// Open a RO session
	session, err = slot.OpenSession()

	// Create a template to find the public key
	template := []*pkcs11.Attribute{
		pkcs11.NewAttribute(pkcs11.CKA_CLASS, pkcs11.CKO_PUBLIC_KEY),
		pkcs11.NewAttribute(pkcs11.CKA_LABEL, objectLabel),
	}

	// Find the object based on template
	publicKeyObject, err = session.FindObject(template)
	if err != nil {
		return err
	}

	// Cast the p11.Object to p11.PublicKey
	publicKey := p11.PublicKey(publicKeyObject)

	// Read the unsigned data
	data, err = os.ReadFile("text.ciphered")
	if err != nil {
		return err
	}

	// Read the signed data
	signedData, err = os.ReadFile("text.sig")
	if err != nil {
		return err
	}

	// Verify signature
	if err = publicKey.Verify(*pkcs11.NewMechanism(pkcs11.CKM_SHA384_RSA_PKCS, nil), data, signedData); err != nil {
		return err
	}

	return nil
}
go run main.go
Verification OK!

Concerning the signature, you have noticed that the mechanism used is CKM_SHA256_RSA_PKCS. Mechanims CKM_SHA256_RSA_PKCS does following things:

To see the hash, in the same way as before, we need the public key, so we have to extract it.

pkcs11-tool --module /usr/local/lib/softhsm/libsofthsm2.so --read-object --type pubkey --label RSA4096SD --output-file pubkey.der
Using slot 0 with a present token (0x1de144b)

Next, you must convert your .der file in a .pem file.

openssl rsa -pubin -in pubkey.der -inform DER -outform PEM -out pubkey.pem
writing RSA key

Then, you can use rsautl and asn1parse to examine the ASN.1 structure.

openssl rsautl -verify -inkey pubkey.pem -in text.sig -pubin | openssl asn1parse -inform DER
    0:d=0  hl=2 l=  65 cons: SEQUENCE
    2:d=1  hl=2 l=  13 cons: SEQUENCE
    4:d=2  hl=2 l=   9 prim: OBJECT            :sha384
   15:d=2  hl=2 l=   0 prim: NULL
   17:d=1  hl=2 l=  48 prim: OCTET STRING      [HEX DUMP]:03B002CE15E3A77C82455A04EFF8CD542DF031626517B795172BEF8C3AAA9B4A01ACE766DA4ECA47AF4958D5BA5A6553

By performing a simple sha384sum on the ciphered text (text.ciphered), we get the same hash value.

sha384sum hello.txt.ciphered
03b002ce15e3a77c82455a04eff8cd542df031626517b795172bef8c3aaa9b4a01ace766da4eca47af4958d5ba5a6553  hello.txt.ciphered

That’s all for this article. We have seen how to compile and install SoftHSMv2 and interact with it via basic OpenSSL commands and going a bit further by using a PKCS #11 implementation. We have of course only scratched the surface of this specification, there are many interesting things that can be done and that will most certainly be in another article.