Nitrokey HSM - n-of-m threshold for DKEK backup: change key custodians

I have multiple HSMs and using a 3-of-5 threshold for the DKEK to create a single security domain and backup the private keys to different HSMs works fine.
(Used sc-hsm-tool --create-dkek-share dkek-share.pbe --pwd-shares-threshold 3 --pwd-shares-total 5)

However, what is the recommended way for the following use case:
One of the 5 current key custodians is leaving and another one is joining.
The obvious straight-forward procedure is that the former custodian hands over his/her key share to the new custodian.
But what if that share was lost, or the former custodian needs to be explicitly excluded from holding a share?

Or more general: Is it possible to create a completely new 3-of-5 threshold - DKEK (involving 5 new key custodians) - and migrate the existing keys from the former security domain to the new one (by involving 3 of the old 5 key custodians)?

Yes, you can create a completely new group of key custodians (as long as you “have” 3 custodiants) and import the keys of your old group into it. Effectively this removes access of your left custodiants to the new group.

sc-hsm-tool is missing a function to re-create pwd-shares for an existing DKEK.

The schema does actually have two layers:

The final DKEK is assembled in the sc-hsm from DKEK shares using an internal XOR operation. Each DKEK share is generated by the sc-hsm-tool using the card’s random number generator. The DKEK share is then encrypted using PBKDF from OpenSSL. The password for PBKDF is either entered directly or the result of a Shamir-Shared-Secret (n-of-m) algorithm.

If you want to recreate a n-of-m control for a DKEK share, then you would need to first reassemble the PBKDF using SSS, decrypt the DKEK share and then recreate n-of-m shares and re-encrypt the DKEK share.

It’s important to understand, that the SmartCard-HSM/Nitrokey HSM supports two different n-of-m schemes: The n-of-m based on SSS to share a DKEK password and n-of-m using public key authentication to control access to the device. The former is part of the sc-hsm-tool, the later is a function of the HSM.

There is a presentation in [1] that gives an overview and a how-to at [2] (CDN access required) that shows the process step by step in the Smart Card Shell.

So far n-of-m authentication is only supported in OpenSCDP. For OpenSC there is a ticket [3] to get that integrated.

n-of-m authentication is also supported in the PKI-as-a-Service Portal [4] for locally and remote connected HSMs. The PKI-as-a-Service Portal is based on OpenSCDP.

[1] https://www.smartcard-hsm.com/2015/10/10/Shared_Control_over_Key_Usage.html
[2] https://devnet.cardcontact.de/documents/7
[3] https://github.com/OpenSC/OpenSC/issues/594
[4] https://www.smartcard-hsm.com/2018/02/13/pki-as-a-service.html

Hello,

Is it still the case in 2025 ?

Also, not sure to understand what we have to do here:

Is there any example somewhere ?

Context:

  • If I talk about ‘n-of-m’ here, it’s NOT for authentification (Smart Card Shell) but for DKEK share
  • We are not decided yet if the DKEK Share will be protected by N .pbe files or n-of-m (unique .pbe file, with multiple custodians)

Situation 1: The DKEK Share is protected with dkek-share-alice.pbe and dkek-share-bob.pbe

  1. What happens if Alice leave the company in bad terms, loss the password or disapear ?
→ Maybe here it's critical: We can't add a new Nitrokey HSM 2 to the DKEK Share
→ So we can't create backups on new Nitrokey HSM 2 keys
→ So if we loose the main key, then we have only the initial backup and can't create more backups!

→ Can you confirm ?
  1. What happens if Alice want to leave the company and John replace Alice ?
→ Alice have to give the [.pbe file + password] to John ?
→ So Alice can still be in possession of [.pbe file + password]

→ Is it possible to replace the .pbe of Alice by the .pbe of John without resetting the HSM / loosing the keys ?
→ IF not: Is it possible to change the password of Alice in her .pbe ?

Situation 2: The DKEK Share is protected with dkek-share.pbe (n-of-m : 4 custonians, minimum 3)

  1. What happens if Alice leave the company in bad terms, loss the password or disapear ?
→ Less critical than previous situation but, we loose a custonian, if it happens more one time it's critical.

→Is it possible to replace Alice by John if we are 3 custodian without resetting the keys ? (Just to be more resilient if the situation appear again)
  1. What happens if Alice want to leave the company and John replace Alice ?
→ Alice have to give the [Share ID + Share value] to John ?
→ So Alice can still be in possession of [Share ID + Share value]

→ Is it possible to replace the [Share ID + Share value] of Alice by new one for John in 'dkek-share.pbe' ?

Technically it is possible for both cases you have mentioned. I have scripted something similar to this for one of my customers. ShamirSharedSecret - Reference Documentation is a starting point.

Hello,

@saper or @sc-hsm Do you have a concrete example about how to add a custodian in an existing .pbe file ?

The idea is to create a new .pbe file without any modifications in the Nitrokey HSM 2 ?

Have a look at the shareSecret() and reconstructSecret() methods in the KeyManager (located at keymanager/keymanager.js in the SCSH installation directory).

The former creates the password and SSS shares, the later reconstructs the password from SSS shares. You could write a key management script that first recovers the existing share from the PBE file and then stores a new PBE files with the fresh encryption password.

If you have two DKEK shares and loose one, then you are lost, as the final DKEK is the xor of both shares. Ideally you would use a SSS on both DKEK shares.

Done that for you. The script can be found as examples/rewrap-dkek-with-sss.js in the sc-hsm-sdk-scripts repo at the CDN.

/**
 *  ---------
 * |.##> <##.|  SmartCard-HSM Support Scripts
 * |#       #|
 * |#       #|  Copyright (c) 2011-2025 CardContact Systems GmbH
 * |'##> <##'|  32429 Minden, Germany (www.cardcontact.de)
 *  ---------
 *
 * Consult your license package for usage terms and conditions.
 *
 * @fileoverview Rewrap a n-of-m encrypted DKEK share, e.g. if a SSS share needs to be replaced.
 */

var File = require("scsh/file/File").File;
var DKEK = require("scsh/sc-hsm/DKEK").DKEK;



function DKEKRewrapper() {
	this.crypto = new Crypto();
}



DKEKRewrapper.prototype.shareSecret = function() {
	var str = Dialog.prompt("Total number of shares", "3");
	if (str == null) {
		return null;
	}
	var totalshares = parseInt(str);
	if (totalshares < 2 || totalshares > 100) {
		print("Total number of shares must be between 2 and 100");
		return null;
	}

	var str = Dialog.prompt("Shares required to reconstruct secret", "2");
	if (str == null) {
		return null;
	}

	var threshold = parseInt(str);
	if (threshold < 2 || threshold > totalshares) {
		print("Threshold shares must be between 2 and " + totalshares);
		return null;
	}

	var html = "<html><p>The DKEK will be enciphered using a randomly generated 64 bit password.<br>" +
		             "This password is split using a (" + threshold + "-of-" + totalshares + ") threshold scheme.</p><br>" +
			 "<p>Please keep the generated and encrypted DKEK file in a safe location. We also recommend <br>" +
			     "to keep a paper printout, in case the electronic version becomes unavailable. A printable version <br>" +
			     "of the file can be generated using \"openssl base64 -in &lt;filename&gt;\".</p><br>" +
			 "<p>Press YES to continue</p></html>";

	var str = Dialog.prompt(html);
	if (str == null) {
		return;
	}

	var sss = new ShamirSharedSecret(64);

	var prime = sss.getPrime();
	var pwd = this.crypto.generateRandom(8);
	pwd = sss.trimSecret(pwd);

	var shares = sss.createShares(pwd, threshold, totalshares);

//	print(prime.toString(HEX));

	for (var i = 0; i < totalshares; i++) {
//		print(shares[i].toString(HEX));
		var html = "<html><p>Share " + (i + 1) + " of " + totalshares + "</p><br>" +
				  "<p>Prime " + prime.toString() + "</p><br>" +
				  "<p>Share ID " + (i + 1) + "</p><br>" +
				  "<p>Share value " + shares[i].toString() + "</p><br>" +
				  "<p>Please note ALL values above and press YES when finished</p></html>";
		Dialog.prompt(html);
	}

	return pwd;
}



DKEKRewrapper.prototype.promptShare = function(name) {
	do	{
		var str = Dialog.prompt("Enter " + name, "");
		if (str == null) {
			return null;
		}
		str = str.replace(":", "");
		str = str.replace(" ", "");
		var val = new ByteString(str, HEX);
		if (val.length != 8) {
			print("Entered value must be 8 bytes");
		}
	} while (val.length != 8);
	return val;
}



DKEKRewrapper.prototype.reconstructSecret = function() {
	var str = Dialog.prompt("Number of shares to enter", "2");
	if (str == null) {
		return null;
	}
	var cnt = parseInt(str);
	if (cnt < 2 || cnt > 100) {
		print("Number of shares must be between 2 and 100");
		return null;
	}

	var prime = this.promptShare("prime");
	if (prime == null) {
		return null;
	}

	var sss = new ShamirSharedSecret(prime);

	for (var i = 0; i < cnt; i++) {
		str = Dialog.prompt("Enter share id", "" + (i + 1));
		if (str == null) {
			return null;
		}
		var id = parseInt(str);
		var share = this.promptShare("share #" + id);
		if (share == null) {
			return null;
		}
		sss.addShare(id, share);
	}

	return sss.reconstructSecret();
}



var rw = new DKEKRewrapper();

if (typeof(fname) == "undefined" || fname == null) {
	fname = "";
}

var fname = Dialog.prompt("Enter file name containing DKEK share", fname, null, "*.pbe");

if (fname == null) {
	throw new Error("User abort");
}

var f = new File(fname);
var encdkekshare = f.readAllAsBinary();
f.close();

var pwd = rw.reconstructSecret();

var share = DKEK.decryptKeyShare(encdkekshare, pwd);

var pwd = rw.shareSecret();

var encdkekshare = DKEK.encryptKeyShare(share, pwd);

var f = new File(fname + ".new");
f.writeAll(encdkekshare);
f.close();
2 Likes

Thanks you so much it’s working very well !!!

:heart_eyes:

1 Like