neuhalje / bouncy-gpg

Make using Bouncy Castle with OpenPGP fun again!
https://neuhalje.github.io/bouncy-gpg/
Other
205 stars 58 forks source link

Unable to encrypt a plaintext file without a secret key and passphrase #38

Closed michaelthecsguy closed 4 years ago

michaelthecsguy commented 4 years ago

Usage: Need to encrypt a text file with ONLY 3rd party client's public key so I can transfer the encrypted data file to the 3rd party client. The client received the encrypted the file and they can use their secret key and passphrase to decrypt it.

It looks like KeyringConfigs.withKeyRingsFromFiles requires secret key and passphrase that I don't have from the 3rd party client. It doesn't allow null value neither. I am wondering if there would be a solution or a patch or a new API to support the usage.

@Test
  public void testEncryption() throws Exception {
    final KeyringConfig keyringConfig = KeyringConfigs.withKeyRingsFromFiles(new File(publicKeyLocation), null, null);
    try (
      final FileInputStream is = new FileInputStream(plainFileLocation);
      final FileOutputStream fileOutput = new FileOutputStream(gpgFileLocation);
      final BufferedOutputStream bufferedOut = new BufferedOutputStream(fileOutput);

      final OutputStream outputStream = BouncyGPG
        .encryptToStream()
        .withConfig(keyringConfig)
        .withStrongAlgorithms()
        .toRecipient("integrationkeys@viantinc.com")
        .andSignWith("integrationkeys@viantinc.com")
        .binaryOutput()
        .andWriteTo(bufferedOut);

    ) {
      Streams.pipeAll(is, outputStream);
    }
  }
michaelthecsguy commented 4 years ago

Not sure if this is same underlying issue with Issue 37.

neuhalje commented 4 years ago

I'll look into it (probably next week)

neuhalje commented 4 years ago

See #37

neuhalje commented 4 years ago

Seems like an easy fix. I'll give it a shot -- since my dev version has some new features (key generation, yay) I will need to clean that up a bit (I just don't want to maintain two branches)

michaelthecsguy commented 4 years ago

if you want, let me know if I can help (although I don't have strong cryptology knowledge) . Tell me where to look to bypass.

neuhalje commented 4 years ago

btw:

...
        .andSignWith("integrationkeys@viantinc.com")
...

Will only work if you have the private key for integrationkeys@viantinc.com

neuhalje commented 4 years ago

@michaelthecsguy : I strongly recommend to switch from FileBasedKeyringConfig to InMemoryKeyring (See here for reasons).

The howto shows you how to migrate to InMemoryKeyring.

neuhalje commented 4 years ago

@michaelthecsguy In any case: please try with 2.2.0

michaelthecsguy commented 4 years ago

btw:

...
        .andSignWith("integrationkeys@viantinc.com")
...

Will only work if you have the private key for integrationkeys@viantinc.com

sorry if I miscommunicate. I had public Key for integrationkeys@viantinc.com but don't have private key. That's only way that I can figure to do BouncyGBG.encrypToStream.

neuhalje commented 4 years ago

From a security point of view: the best way is to create your own private key (e.g. michaelthecsguy@example.com) and transmit its public key to your recipient (integrationkeys@viantinc.com), and then sign via .andSignWith("michaelthecsguy@example.com").

This way the recipient can say: This message is from @michaelthecsguy , and has not been tampered with. If possible, do it like this. Even if the recipient tells you that he cannot validate the signature -- you did everything possible.

If this is not possible/wanted, just call

...
.andDoNotSign()
...

and the message will not be signed.

Does that solve your issue?

michaelthecsguy commented 4 years ago

@michaelthecsguy In any case: please try with 2.2.0

I am going to test it today. Will let you know. Thanks for the fast turn around! @neuhalje

-update- Tried to fetch the latest artifact from Maven seems the artifact is not found. @neuhalje

michaelthecsguy commented 4 years ago

From a security point of view: the best way is to create your own private key (e.g. michaelthecsguy@example.com) and transmit its public key to your recipient (integrationkeys@viantinc.com), and then sign via .andSignWith("michaelthecsguy@example.com").

This way the recipient can say: This message is from @michaelthecsguy , and has not been tampered with. If possible, do it like this. Even if the recipient tells you that he cannot validate the signature -- you did everything possible.

If this is not possible/wanted, just call

...
.andDoNotSign()
...

and the message will not be signed.

Does that solve your issue?

@neuhalje thanks for the explanation. The problem is that I do not have private key. The client has it but the client only shares the public key to me so that I can encrypt the sensitive data using his/her public key and then send it over. Once the client received the encrypted data, he/she will use the private key to decrypt the data that I sent over.

I don't know if I understand correctly, are you saying that I should "generate" private key based on client public key?

neuhalje commented 4 years ago

So, lunch break :-)

Correct, client will never give you his secret key (and neither should you give anyone your secret key).

I found this to be a nice introduction into gpg. It also explains the concepts of public/private keys.

Here is my very short "Public/Private Key Introduction":

  1. A GPG key(-pair) consists of a Public and a Private key. If you'll look at examples and the API you'll find that GPG uses "sub-keys". Just ignore that for now, it is not that important to understand.
  2. GPG can guarantee three things
    • Confidentiality: When you send an encrypted message to a recipient, then only the recipient can read (decrypt) the message. GPG prevents an attacker from reading a message. In GPG this is done by encrypting the message.
    • Integrity: This means that gpg can guarantee that the message is unchanged. Just because you cannot read it (confidentiality) it does not mean that you cannot change it. GPG prevents an attacker from changing a message undetected.
    • Authorship: In cryptography also called "authenticity". Data authenticity means that the initial message sender is who he/she claims to be. Authenticity without integrity makes no sense, so you only get them together: In GPG this is done by signing the message.
  3. Here is the high level workflow without signing. This is what you get with ...andDoNotSign():
    1. Your client has two keys: Client_pub and Client_priv (his key pair). Your client gives you and many others his Client_pub. The client keeps Client_priv. If the client ever looses Client_priv her will no longer be able to decrypt files.
    2. You encrypt a message (file) to your client by using Client_pub as recipient (and key), e.g. ...toRecipient("integrationkeys@viantinc.com"). The result of this encryption is only readable by the client. However, the client cannot be sure that the message came from you, neither can he be sure that the message has not been tampered with.
    3. The client now can decrypt the message using Client_priv
  4. Here is the high level workflow with you signing the message. This is what you get with ...andSignWith(...):
    1. Your client still has two keys: Client_pub and Client_priv.
    2. You also have two keys: You_pub and You_priv. You'd give You_pub to the client.
    3. You encrypt and sign the message: For encryption you still use Client_pub (...toRecipient("integrationkeys@viantinc.com")) and for signing you use You_priv (...andSignWith("michaelthecsguy@my-awesome-company.com")).
    4. The client now can check integrity and authorship (authenticity) by using You_pub (which you gave him in step 3 (ii), and decrypt the message using Client_priv

If your client is fine with getting unsigned files (3), then you do not need to do anything else.

If your client wants signed file (4), you'll need to create Your_pub and Your_priv. You'll have to create the keys one time and to securely store them.

Creating keys:

michaelthecsguy commented 4 years ago
  • s means that gpg can guarantee that the message is unchanged. Just because you cannot read it (confidentiality) it does not mean that you cannot change it. GPG prevents an attacker from changing a message undetected.

Thank you for taking your time to give me a crash course of gpg. It is really helpful. I appreciate it. I also try to maven download 2.2.0 jar and seems it could not find it. Did you upload it? I can still maven download 2.1.2 jar.

The Error msg from Intellij shows: "Dependency 'name.neuhalfen.projects.crypto.bouncycastle.openpgp:bouncy-gpg:2.2.0' not found "

neuhalje commented 4 years ago

You are welcome! You’ll have to download from jcenter: https://github.com/neuhalje/bouncy-gpg/blob/master/README.md#maven

michaelthecsguy commented 4 years ago

You are welcome! You’ll have to download from jcenter: https://github.com/neuhalje/bouncy-gpg/blob/master/README.md#maven

I still can't download 2.2.0 jar from jcenter with `

bintray
  <name>bintray</name>
  <snapshots>
    <enabled>false</enabled>
  </snapshots>
  <url>http://jcenter.bintray.com</url>
</repository>`

My pom.xml was not changed since version 2.1.2. Can you double check it? sorry to bother you again. @neuhalje

neuhalje commented 4 years ago

This seems to be a different problem: #39 -- I'll have to look into that later. Maybe you could try to find out what causes #39? :-) Would be much appreciated!

neuhalje commented 4 years ago

Fixed #39. See here for the solution.

michaelthecsguy commented 4 years ago

Fixed #39. See here for the solution.

verified. Need to change to https from http on repository tag in pom.xml. The jar is successfully imported.

michaelthecsguy commented 4 years ago

So, lunch break :-)

Correct, client will never give you his secret key (and neither should you give anyone your secret key).

I found this to be a nice introduction into gpg. It also explains the concepts of public/private keys.

Here is my very short "Public/Private Key Introduction":

  1. A GPG key(-pair) consists of a Public and a Private key. If you'll look at examples and the API you'll find that GPG uses "sub-keys". Just ignore that for now, it is not that important to understand.
  2. GPG can guarantee three things

    • Confidentiality: When you send an encrypted message to a recipient, then only the recipient can read (decrypt) the message. GPG prevents an attacker from reading a message. In GPG this is done by encrypting the message.
    • Integrity: This means that gpg can guarantee that the message is unchanged. Just because you cannot read it (confidentiality) it does not mean that you cannot change it. GPG prevents an attacker from changing a message undetected.
    • Authorship: In cryptography also called "authenticity". Data authenticity means that the initial message sender is who he/she claims to be. Authenticity without integrity makes no sense, so you only get them together: In GPG this is done by signing the message.
  3. Here is the high level workflow without signing. This is what you get with ...andDoNotSign():

    1. Your client has two keys: Client_pub and Client_priv (his key pair). Your client gives you and many others his Client_pub. The client keeps Client_priv. If the client ever looses Client_priv her will no longer be able to decrypt files.
    2. You encrypt a message (file) to your client by using Client_pub as recipient (and key), e.g. ...toRecipient("integrationkeys@viantinc.com"). The result of this encryption is only readable by the client. However, the client cannot be sure that the message came from you, neither can he be sure that the message has not been tampered with.
    3. The client now can decrypt the message using Client_priv
  4. Here is the high level workflow with you signing the message. This is what you get with ...andSignWith(...):

    1. Your client still has two keys: Client_pub and Client_priv.
    2. You also have two keys: You_pub and You_priv. You'd give You_pub to the client.
    3. You encrypt and sign the message: For encryption you still use Client_pub (...toRecipient("integrationkeys@viantinc.com")) and for signing you use You_priv (...andSignWith("michaelthecsguy@my-awesome-company.com")).
    4. The client now can check integrity and authorship (authenticity) by using You_pub (which you gave him in step 3 (ii), and decrypt the message using Client_priv

If your client is fine with getting unsigned files (3), then you do not need to do anything else.

If your client wants signed file (4), you'll need to create Your_pub and Your_priv. You'll have to create the keys one time and to securely store them.

Creating keys:

  • Either use bouncy-gpg: ExportGeneratedKeysTest shows how to create a key pair and export it for storing.
  • Or use the gpg commanline: The README has a short introduction.

@neuhalje thanks for taking your time to write the quick introduction on GPG. I will share this with my team and your explanation resolved our confusion.

To give you a background requirement, all clients are fine with getting unsigned files because we always upload the "encrypted" data files to their sFTP site with secured login.

michaelthecsguy commented 4 years ago

@neuhalje Sorry to bother you again. I tried KeyringConfigs.withKeyRingsFromFiles(...) without supplying privateKey and passphrase because I only have Client's public key. I also use .andDoNotSign() in my unit Test. It throws NPE: secretKeyring must not be null.

Which KeyRingConfig or other class that I can import the Client's pub key without its private key and passphrase?

java.lang.NullPointerException: secretKeyring must not be null at java.util.Objects.requireNonNull(Objects.java:228) at name.neuhalfen.projects.crypto.bouncycastle.openpgp.keys.keyrings.KeyringConfigs.withKeyRingsFromFiles(KeyringConfigs.java:36)

neuhalje commented 4 years ago

@michaelthecsguy : You'd use the InMemoryKeyring:


        // Input:
        // PUBLIC_KEY : the public key of the recipient. E.g. the output of 
        //              gpg --export -a recipient@example.com
        // UID        : the recipient (recipient@example.com)

        final ByteArrayOutputStream result = new ByteArrayOutputStream();

        InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withUnprotectedKeys());

        keyring.addPublicKey(PUBLIC_KEY.getBytes(StandardCharsets.US_ASCII));

        try (
                BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(result);
                final OutputStream outputStream = BouncyGPG
                        .encryptToStream()
                        .withConfig(keyring)
                        .withStrongAlgorithms()
                        .toRecipient(UID)
                        .andDoNotSign()
                        .binaryOutput()
                        .andWriteTo(bufferedOutputStream);

                final InputStream plaintext = new ByteArrayInputStream(
                       "secret string".getBytes())
        ) {
            Streams.pipeAll(plaintext, outputStream);
        }

    }
michaelthecsguy commented 4 years ago

@neuhalje will test it out today and will let you know.

michaelthecsguy commented 4 years ago

It works in my local! @neuhalje, so this is assuming that you have GPG setup properly in your system, right? or you just need GPG pub key location.

I will close the issue once you reply.

neuhalje commented 4 years ago

It works in my local! @neuhalje, so this is assuming that you have GPG setup properly in your system, right? or you just need GPG pub key location.

If you use the InMemory keyring you do not need gpg

michaelthecsguy commented 4 years ago

@neuhalje Nice. Thanks for the enhancement patch. the Issue is closed.