bc-java
bc-java copied to clipboard
Cannot decrypt openPGP encrypted data using keys created by, and payload encrypted by gnupg. Caused by: org.bouncycastle.crypto.InvalidCipherTextException: block incorrect
I created and exported openPGP keys using the gpg cli and the RSA RSA algorithims option. I then encrypted a small file. I load the private key and attempt to decrypt in https://github.com/apple-corps/bouncycastle-decrypt-gpg-payload-keys/blob/main/src/main/java/com/example/DecryptionService.java
I am getting : org.bouncycastle.crypto.InvalidCipherTextException: block incorrect . However I can decrypt the file with the same keys and passphrase using the gpg cli.
After cloning https://github.com/apple-corps/bouncycastle-decrypt-gpg-payload-keys , reproduce by running
gradle wrapper gradle test
Stack trace
java.lang.UnsupportedOperationException: Could not decrypt the encoded message from the application Secret Key or the embedded Private Key
at com.example.DecryptionService.decrypt(DecryptionService.java:154)
at com.example.DecryptionService.initialize(DecryptionService.java:25)
at com.example.DecryptionServiceTest.testEncDec(DecryptionServiceTest.java:9)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.junit.platform.commons.util.ReflectionUtils.invokeMethod(ReflectionUtils.java:725)
at org.junit.jupiter.engine.execution.MethodInvocation.proceed(MethodInvocation.java:60)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$ValidatingInvocation.proceed(InvocationInterceptorChain.java:131)
at org.junit.jupiter.engine.extension.TimeoutExtension.intercept(TimeoutExtension.java:149)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestableMethod(TimeoutExtension.java:140)
at org.junit.jupiter.engine.extension.TimeoutExtension.interceptTestMethod(TimeoutExtension.java:84)
at org.junit.jupiter.engine.execution.ExecutableInvoker$ReflectiveInterceptorCall.lambda$ofVoidMethod$0(ExecutableInvoker.java:115)
at org.junit.jupiter.engine.execution.ExecutableInvoker.lambda$invoke$0(ExecutableInvoker.java:105)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain$InterceptedInvocation.proceed(InvocationInterceptorChain.java:106)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.proceed(InvocationInterceptorChain.java:64)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.chainAndInvoke(InvocationInterceptorChain.java:45)
at org.junit.jupiter.engine.execution.InvocationInterceptorChain.invoke(InvocationInterceptorChain.java:37)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:104)
at org.junit.jupiter.engine.execution.ExecutableInvoker.invoke(ExecutableInvoker.java:98)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.lambda$invokeTestMethod$7(TestMethodTestDescriptor.java:214)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.invokeTestMethod(TestMethodTestDescriptor.java:210)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:135)
at org.junit.jupiter.engine.descriptor.TestMethodTestDescriptor.execute(TestMethodTestDescriptor.java:66)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:151)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at java.base/java.util.ArrayList.forEach(ArrayList.java:1541)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.invokeAll(SameThreadHierarchicalTestExecutorService.java:41)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$6(NodeTestTask.java:155)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$8(NodeTestTask.java:141)
at org.junit.platform.engine.support.hierarchical.Node.around(Node.java:137)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.lambda$executeRecursively$9(NodeTestTask.java:139)
at org.junit.platform.engine.support.hierarchical.ThrowableCollector.execute(ThrowableCollector.java:73)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.executeRecursively(NodeTestTask.java:138)
at org.junit.platform.engine.support.hierarchical.NodeTestTask.execute(NodeTestTask.java:95)
at org.junit.platform.engine.support.hierarchical.SameThreadHierarchicalTestExecutorService.submit(SameThreadHierarchicalTestExecutorService.java:35)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestExecutor.execute(HierarchicalTestExecutor.java:57)
at org.junit.platform.engine.support.hierarchical.HierarchicalTestEngine.execute(HierarchicalTestEngine.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:108)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:88)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.lambda$execute$0(EngineExecutionOrchestrator.java:54)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.withInterceptedStreams(EngineExecutionOrchestrator.java:67)
at org.junit.platform.launcher.core.EngineExecutionOrchestrator.execute(EngineExecutionOrchestrator.java:52)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:96)
at org.junit.platform.launcher.core.DefaultLauncher.execute(DefaultLauncher.java:75)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.processAllTestClasses(JUnitPlatformTestClassProcessor.java:99)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor$CollectAllTestClassesExecutor.access$000(JUnitPlatformTestClassProcessor.java:79)
at org.gradle.api.internal.tasks.testing.junitplatform.JUnitPlatformTestClassProcessor.stop(JUnitPlatformTestClassProcessor.java:75)
at org.gradle.api.internal.tasks.testing.SuiteTestClassProcessor.stop(SuiteTestClassProcessor.java:61)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.base/java.lang.reflect.Method.invoke(Method.java:566)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:36)
at org.gradle.internal.dispatch.ReflectionDispatch.dispatch(ReflectionDispatch.java:24)
at org.gradle.internal.dispatch.ContextClassLoaderDispatch.dispatch(ContextClassLoaderDispatch.java:33)
at org.gradle.internal.dispatch.ProxyDispatchAdapter$DispatchingInvocationHandler.invoke(ProxyDispatchAdapter.java:94)
at com.sun.proxy.$Proxy2.stop(Unknown Source)
at org.gradle.api.internal.tasks.testing.worker.TestWorker$3.run(TestWorker.java:193)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.executeAndMaintainThreadName(TestWorker.java:129)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:100)
at org.gradle.api.internal.tasks.testing.worker.TestWorker.execute(TestWorker.java:60)
at org.gradle.process.internal.worker.child.ActionExecutionWorker.execute(ActionExecutionWorker.java:56)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:133)
at org.gradle.process.internal.worker.child.SystemApplicationClassLoaderWorker.call(SystemApplicationClassLoaderWorker.java:71)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.run(GradleWorkerMain.java:69)
at worker.org.gradle.process.internal.worker.GradleWorkerMain.main(GradleWorkerMain.java:74)
Caused by: org.bouncycastle.openpgp.PGPException: exception decrypting session data
at app//org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(Unknown Source)
at app//org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.access$100(Unknown Source)
at app//org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder$2.recoverSessionData(Unknown Source)
at app//org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getSessionKey(Unknown Source)
at app//org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at app//com.example.DecryptionService.decrypt(DecryptionService.java:150)
... 85 more
Caused by: org.bouncycastle.jcajce.provider.util.BadBlockException: unable to decrypt block
at app//org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.getOutput(Unknown Source)
at app//org.bouncycastle.jcajce.provider.asymmetric.rsa.CipherSpi.engineDoFinal(Unknown Source)
at [email protected]/javax.crypto.Cipher.doFinal(Cipher.java:2085)
... 91 more
Caused by: org.bouncycastle.crypto.InvalidCipherTextException: block incorrect
at app//org.bouncycastle.crypto.encodings.PKCS1Encoding.decodeBlock(Unknown Source)
at app//org.bouncycastle.crypto.encodings.PKCS1Encoding.processBlock(Unknown Source)
... 94 more
You are attempting to decrypt using the signing key!! In getSecretKey() you are currently searching for isSigningKey(). You should be looking for isEncryptionKey(), as you are doing in getPublicKey()
@tonywasher there is no isEncryptionKey() method https://javadoc.io/doc/org.bouncycastle/bcpg-jdk15on/latest/org/bouncycastle/openpgp/PGPSecretKey.html
True. It is there is PGPPublicKey, but not in PGPSecretKey. Regardless you are still using the wrong key. You should therefore access the keyID of the key that was used to encrypt the data from pgpPublicKeyEncryptedData.getKeyID(), and pass it to getSecretKey(). You can then obtain the correct key from the keyRing by calling pgpSec.getSecretKey(pKeyId)
Hi @tonywasher . I created a branch following your suggestions. https://github.com/apple-corps/bouncycastle-decrypt-gpg-payload-keys/blob/cab198e969a18f6b5db811040d53a896c7b3138f/src/main/java/com/example/DecryptionService.java
It failed with the same exception. If you have more suggestions, would you so kindly please clone the branch, make changes, and run gradle test? I think it will be much easier to be sure we address the issue at hand and don't have unnecessary back and forth conversations.
Thanks for your help.
Your code is likely trying to decrypt using the wrong key.
You should change your getSecretKey()
method to return a PGPSecretKeyRing
object which contains all subkeys of your secret key. Then get the right decryption key by calling pgpSecretKeyRing.getSecretKey(pgpPublicKeyEncryptedData.getKeyID())
in your decryption method.
Hi @vanitasvitae that looks similar to the above suggestion and code changes. Are you sure https://github.com/apple-corps/bouncycastle-decrypt-gpg-payload-keys/blob/cab198e969a18f6b5db811040d53a896c7b3138f/src/main/java/com/example/DecryptionService.java#L168 from above post is not performing what you are suggesting?
See also it's application here: https://github.com/apple-corps/bouncycastle-decrypt-gpg-payload-keys/blob/cab198e969a18f6b5db811040d53a896c7b3138f/src/main/java/com/example/DecryptionService.java#L152
I am receiving the same exception. But I am writing a similar test and process for PGPainless, where I obtain a different exception.
Lets take a look at the data from your branch:
This is your key (I show the public key here, but the secret key has the same key-ids):
Public-Key Packet, old CTB, 397 bytes
Version: 4
Creation time: 2022-04-19 00:33:01 UTC
Pk algo: RSA (Encrypt or Sign)
Pk size: 3072 bits
Fingerprint: 2500BA96B1666311B63A71EECC3869871B1E903B
KeyID: CC3869871B1E903B
User ID Packet, old CTB, 49 bytes
Value: Colin Williams <[email protected]>
Signature Packet, old CTB, 468 bytes
Version: 4
Type: PositiveCertification
Pk algo: RSA (Encrypt or Sign)
Hash algo: SHA1
Hashed area:
Issuer Fingerprint: 2500BA96B1666311B63A71EECC3869871B1E903B
Signature creation time: 2022-04-19 00:33:01 UTC
Key flags: CS
Key expiration time: P730D
Symmetric algo preferences: AES256, AES192, AES128, TripleDES
Hash preferences: SHA512, SHA384, SHA256, SHA224, SHA1
Compression preferences: Zlib, BZip2, Zip
Features: MDC
Keyserver preferences: no modify
Unhashed area:
Issuer: CC3869871B1E903B
Digest prefix: 063D
Level: 0 (signature over data)
Public-Subkey Packet, old CTB, 397 bytes
Version: 4
Creation time: 2022-04-19 00:33:01 UTC
Pk algo: RSA (Encrypt or Sign)
Pk size: 3072 bits
Fingerprint: D5D7D860AA406653B81DCF52324098E03279F8BD
KeyID: 324098E03279F8BD
Signature Packet, old CTB, 444 bytes
Version: 4
Type: SubkeyBinding
Pk algo: RSA (Encrypt or Sign)
Hash algo: SHA1
Hashed area:
Issuer Fingerprint: 2500BA96B1666311B63A71EECC3869871B1E903B
Signature creation time: 2022-04-19 00:33:01 UTC
Key flags: EtEr
Key expiration time: P730D
Unhashed area:
Issuer: CC3869871B1E903B
Digest prefix: 2F8C
Level: 0 (signature over data)
The output was created by pasting the armored public key to dump.sequoia-pgp.org
This is the content of your encrypted file
Public-Key Encrypted Session Key Packet, old CTB, 396 bytes
Version: 3
Recipient: 324098E03279F8BD
Pk algo: RSA (Encrypt or Sign)
Sym. Encrypted and Integrity Protected Data Packet, new CTB, 194 bytes
Version: 1
No session key supplied
(I took the liberty to ASCII armor the message to help with dumping the contents)
As you can see it is encrypted with the subkey 324098E03279F8BD
.
What's important to know for BCs PGPPublicKey.isSigningKey()
is that it only looks at the key algorithm, so every RSA_GENERAL key will return true
here. However, OpenPGP uses Key Flags to mark signing/encryption keys.
You should only use isSigningKey()
to determine keys which IN THEORY COULD be used to sign data.
So your getPublicKey()
method will falsely return the primary key of your key, while in fact the message was encrypted by GnuPG correctly using the encryption subkey.
Therefore you should identify the encryption key by looking at what the encrypted data packet states which key was used to encrypt it.
Hi @apple-corps, Unfortunately you didn't do the change that I was requesting you to do.
line 152 should be .build(getPrivateKey(getSecretKey1(pgpPublicKeyEncryptedData.getKeyID()), password)));
and lines 167/168 should be
private PGPSecretKey getSecretKey1(final long id) throws PGPException { //long id = getPublicKey().getKeyID();
This decrypts successfully although the output is not ASCII.
From what you have described, the file that you are trying to decrypt launch-docker.sh.gpg was created by gpg. In this case you are still missing several steps in the decryption process.
line 162 should change to final BcPGPObjectFactory myFact = new BcPGPObjectFactory(decryptedInputStream); final PGPCompressedData myCompressed = (PGPCompressedData) myFact.nextObject(); final BcPGPObjectFactory plainFact = new BcPGPObjectFactory(myCompressed.getDataStream()); final PGPLiteralData myData = (PGPLiteralData) plainFact.nextObject();
return IOUtils.toString(myData.getDataStream(), StandardCharsets.UTF_8.name());
With these changes I was able to decrypt the file.
I can't upload these changes to your branch, since I don't have permission.
Assuming dealt with. Thanks to everyone who contributed.