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:
- PKI environment.
- Payment card industry.
- TLS connection establishment.
- DNSSEC.
- Cryptocurrency wallet.
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:
- The manipulations are carried out on Rocky Linux 8.
- A connection via an SSH key pair is already configured.
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:
--module
: specify the module to load, here the shared library seen above must be specified.--login
: as a write is performed, it is necessary to log in as User (by default).--keypairgen
: generate an RSA key pair.--usage-sign
: specify ‘sign’ key usage flag (sets SIGN in private key, sets VERIFY in public key).--usage-decrypt
: specify ‘decrypt’ key usage flag (RSA only, sets DECRYPT in private key, sets ENCRYPT in public key).--key-type
: specify the type and length of the key to create.--label
: specify the label of the object, hereRSA
for the used cryptosystem,4096
for the key length,S
for sign andD
for decrypt.
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:
--read-object
: get object’sCKA_VALUE
attribute.--type
: specify the type of object (e.g. cert, privkey, pubkey, secrkey, data).--output-file
: specify the output file.
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:
- Computes SHA256 hash of the data just like
CKM_SHA256
does. - Constructs DER encoded
DigestInfo
structure defined in RFC 8017. - Signs
DigestInfo
structure with private key just likeCKM_RSA_PKCS
does.
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.