3ric-T
3ric-T

Reputation: 41

Wrap-unwrap of private key using EC master key and Bouncy Castle

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

Upvotes: 0

Views: 819

Answers (2)

3ric-T
3ric-T

Reputation: 41

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.

Upvotes: 1

John Williams
John Williams

Reputation: 5365

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);

Upvotes: 0

Related Questions