0

I want to wrap a private key out of a HSM, using an external EC key pair (master key) and then verify that I can recover it.
The wrapping occurs as follow:

  1. Generate a secret AES key in the HSM, using the public part of the EC master key, the private part of the internal key pair and a derivation mechanism CKM_ECDH1_DERIVE. The derivation parameters for this mechanism are: a derivation function CKD_SHA256_KDF, shared data and public data (public data are taken from the public EC master key).
  2. Wrap the private key, using the secret AES key and a mechanims such as CKM_AES_GCM, CKM_AES_KEY_WRAP_PAD or CKM_AES_CBC_PAD.
  3. The HSM returns a byte array.

Then I would like to verify if the wrapped private is the expected one.

I know how to decrypt the private key once I have recovered the secret key used ot protect it. Because it is not like RSA, I have to derive the same secret key using some elements I have, but I don't know how to do this with BC.
I'm trying to use something like this, trying to find an concrete implementation of AlgorithmParamSpec:

KeyAgreement agreement = KeyAgreement.getInstance("ECCDHwithSHA256CKDF", "BC");
agreement.init(externalEcMasterKey.getPrivate(), someAlgorithmParamSpec);
agreement.doPhase(internalEcKeyPair.asJavaPublic(), true);
SecretKey agreedKey = agreement.generateSecret("AES[256]");

Unfortunately, with UserKeyingMaterialSpec for example, it returns a different key at each time, which is not what I want :)

Thanks in advance

3ric-T
  • 31
  • 5

2 Answers2

1

In the HSM response, I have:

  • A byte array which is the public key of the ephemeral EC transport key generated from my long-term EC master key: transportPublicKey;
  • A byte array which is the private key wrapped by the session key: privateKeyWrappedBySessionKey;

To decrypt the private key:

  1. Generate the same session key, using the derivation mechanism CKM_ECDH1_DERIVE and the derivation function CKD_SHA256_KDF, the private part of the EC master key, shared data (which can be null) and the public key of the ephemeral EC transport key:
KeyAgreement recipientAgreement = KeyAgreement.getInstance(
                    "ECDHWithSHA256KDF",
                    BouncyCastleProvider.PROVIDER_NAME);
recipientAgreement.init(recipientPrivateKey,
                        new UserKeyingMaterialSpec(sharedData));
recipientAgreement.doPhase(transportPublicKey), true);            
final byte[] secret = recipientAgreement.generateSecret();
SecretKeySpec sharedSecret = new SecretKeySpec(secret, "AES");         
  1. Decrypt the private key with the session key and the appropriate symmetric algorithm:
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS7PADDING");
final InitializationVectorParameters initializationVectorParameters = new InitializationVectorParameters(new byte[16]);
byte[] ivb = initializationVectorParameters.getInitializationVector();
cipher.init(Cipher.DECRYPT_MODE, sharedSecret, new IvParameterSpec(ivb));
byte[] decryptedPrivateKey = cipher.doFinal(privateKeyWrappedBySessionKey);         

PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(decryptedPrivateKey);            
KeyFactory keyFactory = KeyHelper.keyFactory(wrappedPrivateKeyType);
PrivateKey privateKey = keyFactory.generatePrivate(privateKeySpec);

Additional information:

  • In case of CKD_NULL derivation function, AlgorithmParameterSpec MUST NOT be provided, as stated in PKCS#11

(§ 2.3.20) With the key derivation function CKD_NULL, pSharedData must be NULL and ulSharedDataLen must be zero.

recipientAgreement.init(recipientPrivateKey);
  • In case of a session key with size shorter than 256 bits, only the keySize / 8 first bits of the generated secret have to be used:

(§ 2.3.20) ... and gets the first ulAESKeyBits bits of the derived key to be the temporary AES key.

  • In the case of CKM_AES_KEY_WRAP or CKM_AES_KEY_WRAP_PAD mechanisms, no initialization vector is required:
cipher.init(Cipher.DECRYPT_MODE, sharedSecret);
  • Note that, at the moment, this does not work with CKD_NULL and session key size other than 256 bits.
3ric-T
  • 31
  • 5
0

Decrypt the encrypted private key as follows:

byte[] ivbuf = new byte[16];
new SecureRandom().nextBytes(ivbuf);
IvParameterSpec iv = new IvParameterSpec(ivbuf);
String algorithm = “AES/CBC/PKCS5Padding”; // CKM_AES_GCM, CKM_AES_KEY_WRAP_PAD or CKM_AES_CBC_PAD
Cipher cipher = Cipher.getInstance(algorithm);
cipher.init(Cipher.DECRYPT_MODE, aesSecretKey, iv);
byte[] plainText = cipher.doFinal(encryptedPrivateKeyByteArray);
return new String(plainText);
John Williams
  • 4,252
  • 2
  • 9
  • 18
  • In fact, it's not the decryption of the private key itself that I have a problem with, but rather finding the secret key from the elements I have: the private key of the master key pair and the public key of the internal key pair. The description of my probleme seems to be unclear, I'll edit it to be more precise. – 3ric-T Apr 25 '23 at 17:27
  • My experience of HSMs is that you cannot exfiltrate private or secret keys. HSMs exist to keep these types of keys safe. This is achieved by keeping the processing that uses these types of keys (asymmetric signing and symmetric encryption/decryption) within the HSM. Data to be signed (hashes thereof) or encrypted is passed to the HSM and the results are passed back. – John Williams Apr 25 '23 at 17:43
  • It is possible to extract private keys from the HSM, as stated in the PKCS#11 version 3.0 document (§ 5.8.3): "`C_WrapKey` wraps (i.e., encrypts) a private or secret key. (...) The `CKA_WRAP` attribute of the wrapping key, which indicates whether the key supports wrapping, *MUST* be *CK_TRUE*. The `CKA_EXTRACTABLE` attribute of the key to be wrapped *MUST* also be *CK_TRUE*. (...)" To protect your HSM, it is always possible to forbid object creation, so wrapping of keys will only be possible using keys already present. – 3ric-T Apr 26 '23 at 08:41