bc-java icon indicating copy to clipboard operation
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

Open apple-corps opened this issue 2 years ago • 8 comments

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

apple-corps avatar Apr 19 '22 04:04 apple-corps

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 avatar Apr 20 '22 03:04 tonywasher

@tonywasher there is no isEncryptionKey() method https://javadoc.io/doc/org.bouncycastle/bcpg-jdk15on/latest/org/bouncycastle/openpgp/PGPSecretKey.html

apple-corps avatar Apr 20 '22 06:04 apple-corps

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)

tonywasher avatar Apr 20 '22 06:04 tonywasher

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.

apple-corps avatar Apr 20 '22 17:04 apple-corps

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.

vanitasvitae avatar Apr 20 '22 19:04 vanitasvitae

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.

apple-corps avatar Apr 20 '22 20:04 apple-corps

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.

vanitasvitae avatar Apr 20 '22 20:04 vanitasvitae

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.

tonywasher avatar Apr 21 '22 06:04 tonywasher

Assuming dealt with. Thanks to everyone who contributed.

dghgit avatar Aug 15 '22 10:08 dghgit