Unable to encrypt a plaintext file without a secret key and passphrase
michaelthecsguy opened this issue · 26 comments
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);
}
}
Not sure if this is same underlying issue with Issue 37.
I'll look into it (probably next week)
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)
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.
btw:
...
.andSignWith("integrationkeys@viantinc.com")
...
Will only work if you have the private key for integrationkeys@viantinc.com
@michaelthecsguy : I strongly recommend to switch from FileBasedKeyringConfig
to InMemoryKeyring
(See here for reasons).
The howto shows you how to migrate to InMemoryKeyring
.
@michaelthecsguy In any case: please try with 2.2.0
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.
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 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
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?
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":
- 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.
- 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.
- Here is the high level workflow without signing. This is what you get with
...andDoNotSign()
:- Your client has two keys:
Client_pub
andClient_priv
(his key pair). Your client gives you and many others hisClient_pub
. The client keepsClient_priv
. If the client ever loosesClient_priv
her will no longer be able to decrypt files. - 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. - The client now can decrypt the message using
Client_priv
- Your client has two keys:
- Here is the high level workflow with you signing the message. This is what you get with
...andSignWith(...)
:- Your client still has two keys:
Client_pub
andClient_priv
. - You also have two keys:
You_pub
andYou_priv
. You'd giveYou_pub
to the client. - You encrypt and sign the message: For encryption you still use
Client_pub
(...toRecipient("integrationkeys@viantinc.com")
) and for signing you useYou_priv
(...andSignWith("michaelthecsguy@my-awesome-company.com")
). - 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 usingClient_priv
- Your client still has two keys:
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.
- 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 "
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
<repository> <id>bintray</id> <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
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":
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.
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.
Here is the high level workflow without signing. This is what you get with
...andDoNotSign()
:
- Your client has two keys:
Client_pub
andClient_priv
(his key pair). Your client gives you and many others hisClient_pub
. The client keepsClient_priv
. If the client ever loosesClient_priv
her will no longer be able to decrypt files.- 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.- The client now can decrypt the message using
Client_priv
Here is the high level workflow with you signing the message. This is what you get with
...andSignWith(...)
:
- Your client still has two keys:
Client_pub
andClient_priv
.- You also have two keys:
You_pub
andYou_priv
. You'd giveYou_pub
to the client.- You encrypt and sign the message: For encryption you still use
Client_pub
(...toRecipient("integrationkeys@viantinc.com")
) and for signing you useYou_priv
(...andSignWith("michaelthecsguy@my-awesome-company.com")
).- 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 usingClient_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
andYour_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.
@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)
@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);
}
}
@neuhalje will test it out today and will let you know.
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 Nice. Thanks for the enhancement patch. the Issue is closed.