Recovering a private key without MicroB2B
MicroB2B stores the private key of a certificate encrypted under the master
key (SECRETS_KEY), never in the clear. This page is how to get a key back
from a database row and the master key with OpenSSL alone, for instance when
MicroB2B cannot run or when a key must move to another product.
It needs OpenSSL 3, base64, od and tr. The commands below are run by a
test against a key MicroB2B wrote, so they are what this version does.
What a row holds
The table certificates has, for a certificate with a private key:
| Column | Holds |
|---|---|
private_key_bytes | the key as an encrypted PKCS#8 PEM (-----BEGIN ENCRYPTED PRIVATE KEY-----): PBES2 (RFC 8018) with PBKDF2-HMAC-SHA256 and AES-256-CBC, the profile openssl pkcs8 -topk8 -v2 aes-256-cbc -v2prf hmacWithSHA256 writes |
private_key_kid | the id of the master key it is encrypted under |
Version 1
Every value of version 1 is derived from the 32 bytes of the master key:
| Value | Definition |
|---|---|
| key id | the first 4 bytes of HMAC-SHA256(master key, microb2b secret key id), as 8 lowercase hex characters |
| private key subkey | HKDF-SHA256 (RFC 5869) of the master key, salt empty, info microb2b private key v1, 32 bytes |
| password | the private key subkey as 64 lowercase hex characters |
The password is the PBES2 password of the PEM. Its salt, iteration count and IV are in the PEM itself.
The commands
Set SECRETS_KEY to the master key as configured (base64; for a key given as
hex, set MASTER_KEY to it directly and skip the first line), write the
row’s private_key_bytes to key.pem and set KID to its
private_key_kid:
MASTER_KEY=$(printf '%s' "$SECRETS_KEY" | base64 -d | od -An -tx1 | tr -d ' \n')
# The key id of this master key: it must be the row's private_key_kid.
printf '%s' 'microb2b secret key id' | openssl dgst -sha256 -mac HMAC -macopt hexkey:"$MASTER_KEY" -r | cut -c1-8
PASSWORD=$(openssl kdf -keylen 32 -kdfopt digest:SHA256 -kdfopt hexkey:"$MASTER_KEY" \
-kdfopt info:'microb2b private key v1' HKDF | tr -d ':' | tr 'A-F' 'a-f')
openssl pkcs8 -in key.pem -passin pass:"$PASSWORD" -out recovered.pemrecovered.pem is the key as an unencrypted PKCS#8 PEM. A key id that is not
KID means this is not the master key the row was encrypted under: during a
rotation the row may name a previous key (SECRETS_PREVIOUS_KEYS).
Keep recovered.pem and the master key away from where the database is.
The other secrets
The write-only values of the configurations (passwords, header values, signing secrets, webhook URLs) are sealed as JSON documents with AES-256-GCM under another subkey and bound to their row. They are not covered here: a lost one is entered again.