Repository navigation
Description
I am using bcpg-jdk18on-1.78.1.jar and a CryptoServer HSM JCE (Utimaco) to decrypt PGP data. The private ECC key is stored on HSM, and the JCE provider for HSM is registred in the Java vm. With software ECC keys everything works fine with pgp decryption. I tried to decrypt the PGP session key first and then let BC decrypt the actual payload of the pgp encrypted blob. My code looks like this (taken from BC FIPS 100 examples):
public static byte[] decryptPGPData(PGPPrivateKey encSubPrivateKey,
byte[] pgpEncryptedData) throws Exception {
PGPObjectFactory pgpFact = new JcaPGPObjectFactory(pgpEncryptedData);
PGPEncryptedDataList encList = (PGPEncryptedDataList) pgpFact.nextObject();
PGPPublicKeyEncryptedData encData = (PGPPublicKeyEncryptedData) encList.get(0);
PublicKeyDataDecryptorFactory dataDecryptorFactory =
new JcePublicKeyDataDecryptorFactoryBuilder().build(encSubPrivateKey);
// will fail with HSM based PGPPrivateKey
PGPSessionKey decryptedSessionKey = encData.getSessionKey(dataDecryptorFactory);
SessionKeyDataDecryptorFactory sessionKeyDataDecryptorFactor =
new JceSessionKeyDataDecryptorFactoryBuilder().setProvider("BC").build(decryptedSessionKey);
InputStream clear = encData.getDataStream(sessionKeyDataDecryptorFactor);
byte[] literalData = Streams.readAll(clear);
if (encData.verify()) {
PGPObjectFactory litFact = new JcaPGPObjectFactory(literalData);
PGPLiteralData litData = (PGPLiteralData) litFact.nextObject();
byte[] data = Streams.readAll(litData.getInputStream());
return data;
}
throw new IllegalStateException("modification check failed");
}
Trying the same with a PGPPrivateKey residing within a HSM gives me an exception when calling encData.getSessionKey(encSubPrivateKey) and when instantiating KeyAgreement of the private key associtated HSM JCE:
Exception in thread "main" org.bouncycastle.openpgp.PGPException: error decrypting session data: Unsupported parameter type
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(JcePublicKeyDataDecryptorFactoryBuilder.java:287)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.access$500(JcePublicKeyDataDecryptorFactoryBuilder.java:50)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder$2.recoverSessionData(JcePublicKeyDataDecryptorFactoryBuilder.java:183)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getSessionKey(PGPPublicKeyEncryptedData.java:101)
at Z1HsmBcPGPTestEC.decryptPGPData(Z1HsmBcPGPTestEC.java:415)
at Z1HsmBcPGPTestEC.main(Z1HsmBcPGPTestEC.java:188)
Caused by: java.security.InvalidAlgorithmParameterException: Unsupported parameter type
at CryptoServerJCE.CryptoServerKeyAgreement.engineInit(CryptoServerKeyAgreement.java:170)
at java.base/javax.crypto.KeyAgreement.implInit(KeyAgreement.java:356)
at java.base/javax.crypto.KeyAgreement.chooseProvider(KeyAgreement.java:386)
at java.base/javax.crypto.KeyAgreement.init(KeyAgreement.java:547)
at java.base/javax.crypto.KeyAgreement.init(KeyAgreement.java:517)
at org.bouncycastle.openpgp.operator.jcajce.JcaJcePGPUtil.getSecret(JcaJcePGPUtil.java:102)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.getSessionKey(JcePublicKeyDataDecryptorFactoryBuilder.java:323)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(JcePublicKeyDataDecryptorFactoryBuilder.java:281)
You can see that BC JcaJcePGPUtil.getSecret(JcaJcePGPUtil.java:102) tries to initialize the KeyAgreement with a BC specific KeyAgreement params (UserKeyingMaterialSpec from BC) which doesnt work. Because the KeyAgreement is instantiated on the HSM JCE provider. And th HSM JCE KeyAgreement doesnt know the UserKeyingMaterialSpec
I tried to to create a workaround and supply a vendor specifc HSM KeyAgreement (CryptoServerSharedInfoParameterSpec) and then the KeyAgreement runs fine. But the next problem is that the resulting symmetric aes key is a HSM JCE key (AES symmetric key on HSM) and cannot be used by later to decrypt with a AES Cipher. Here the AES key must be constructed with SecretKeySpec using the bytes from the KeyAgreement. So the resulting AES key is a BC software key.
My changes to make it work with HSM looks like this:
static Key getSecret(OperatorHelper helper, PublicKey cryptoPublicKey,
String keyEncryptionOID, String agreementName,
AlgorithmParameterSpec ukmSpec, Key privKey)
throws GeneralSecurityException
{
ukmSpec = new CryptoServerSharedInfoParameterSpec(
((UserKeyingMaterialSpec) ukmSpec).getUserKeyingMaterial());
KeyAgreement agreement = helper.createKeyAgreement(agreementName);
agreement.init(privKey, ukmSpec);
agreement.doPhase(cryptoPublicKey, true);
byte[] keyBytes = agreement.generateSecret();
SecretKey aesKey = new SecretKeySpec(Arrays.copyOfRange(keyBytes, 0, 32), "AES");
return aesKey;
}
But my change assumes every generated secret key is always AES 256. So its just a quick hack, rather than a final solution.
So I am asking for help for 2 topics here:
-
Could you extend JcePublicKeyDataDecryptorFactoryBuilder so I can supply the KeyAgreement params (from HSM) outside before decrypting the session key? So i could pass my CryptoServerSharedInfoParameterSpec to it?
-
Could you change the way the secret session key is generated and explicitly dont generat the Key in HSM provider but as a BC softwarekey with SecretKeySpec classe for example?

Activity