Getting GPG Decryption To Work In Java (Bouncy Castle) - java

let me start by saying I'm extremely new to all of this. What I am trying to do is to use gpg from within Java in order to decrypt an encrypted file.
What I've done successfully:
Had a colleague encrypt a file using my public key and his private key and successfully decrypted it.
Went the other way
Had another colleague try to decrypt a file that wasn't for him: fail (as expected)
My key was generated like this...
(gpg --version tells me I'm using 1.4.5 and I'm using Bouncy Castle 1.47)
gpg --gen-ley
Select option "DSA and Elgamal (default)"
Fill in the other fields and generate a key.
The file was encrypted using my public key and another's secret key. I want to decrypt it. I've written the following Java code to accomplish this. I'm using several deprecated methods, but I can't figure out how to properly implement the factory methods required to use the non-deprecated versions, so if anyone has an idea on implementations of those that I should be using that would be a nice bonus.
Security.addProvider(new BouncyCastleProvider());
PGPSecretKeyRingCollection secretKeyRing = new PGPSecretKeyRingCollection(new FileInputStream(new File("test-files/secring.gpg")));
PGPSecretKeyRing pgpSecretKeyRing = (PGPSecretKeyRing) secretKeyRing.getKeyRings().next();
PGPSecretKey secretKey = pgpSecretKeyRing.getSecretKey();
PGPPrivateKey privateKey = secretKey.extractPrivateKey("mypassword".toCharArray(), "BC");
System.out.println(privateKey.getKey().getAlgorithm());
System.out.println(privateKey.getKey().getFormat());
PGPObjectFactory pgpF = new PGPObjectFactory(
new FileInputStream(new File("test-files/test-file.txt.gpg")));
Object pgpObj = pgpF.nextObject();
PGPEncryptedDataList encryptedDataList = (PGPEncryptedDataList) pgpObj;
Iterator objectsIterator = encryptedDataList.getEncryptedDataObjects();
PGPPublicKeyEncryptedData publicKeyEncryptedData = (PGPPublicKeyEncryptedData) objectsIterator.next();
InputStream inputStream = publicKeyEncryptedData.getDataStream(privateKey, "BC");
So when I run this code I learn that my algorithm and format are as follows for my secret key:
Algorithm: DSA
Format: PKCS#8
And then it breaks on the last line:
Exception in thread "main" org.bouncycastle.openpgp.PGPException: error setting asymmetric cipher
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.decryptSessionData(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder.access$000(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder$2.recoverSessionData(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at org.bouncycastle.openpgp.PGPPublicKeyEncryptedData.getDataStream(Unknown Source)
at TestBouncyCastle.main(TestBouncyCastle.java:74)
Caused by: java.security.InvalidKeyException: unknown key type passed to ElGamal
at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source)
at org.bouncycastle.jcajce.provider.asymmetric.elgamal.CipherSpi.engineInit(Unknown Source)
at javax.crypto.Cipher.init(DashoA13*..)
at javax.crypto.Cipher.init(DashoA13*..)
... 8 more
I'm open to a lot of suggestions here, from "don't use gpg, use x instead" to "don't use bouncy castle, use x instead" to anything in between. Thanks!

If anyone is interested to know how to encrypt and decrypt gpg files using bouncy castle openPGP library, check the below java code:
The below are the 4 methods you going to need:
The below method will read and import your secret key from .asc file:
public static PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator());
PGPSecretKey key = pgpSec.getSecretKey(keyId);
if (key == null) {
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
return key;
}
The below method will read and import your public key from .asc file:
#SuppressWarnings("rawtypes")
public static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator());
PGPPublicKey key = null;
Iterator rIt = pgpPub.getKeyRings();
while (key == null && rIt.hasNext()) {
PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
Iterator kIt = kRing.getPublicKeys();
while (key == null && kIt.hasNext()) {
PGPPublicKey k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
key = k;
}
}
}
if (key == null) {
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
return key;
}
The below 2 methods to decrypt and encrypt gpg files:
public void decryptFile(InputStream in, InputStream secKeyIn, InputStream pubKeyIn, char[] pass) throws IOException, PGPException, InvalidCipherTextException {
Security.addProvider(new BouncyCastleProvider());
PGPPublicKey pubKey = readPublicKeyFromCol(pubKeyIn);
PGPSecretKey secKey = readSecretKeyFromCol(secKeyIn, pubKey.getKeyID());
in = PGPUtil.getDecoderStream(in);
JcaPGPObjectFactory pgpFact;
PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator());
Object o = pgpF.nextObject();
PGPEncryptedDataList encList;
if (o instanceof PGPEncryptedDataList) {
encList = (PGPEncryptedDataList) o;
} else {
encList = (PGPEncryptedDataList) pgpF.nextObject();
}
Iterator<PGPPublicKeyEncryptedData> itt = encList.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData encP = null;
while (sKey == null && itt.hasNext()) {
encP = itt.next();
secKey = readSecretKeyFromCol(new FileInputStream("PrivateKey.asc"), encP.getKeyID());
sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
}
if (sKey == null) {
throw new IllegalArgumentException("Secret key for message not found.");
}
InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
pgpFact = new JcaPGPObjectFactory(clear);
PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject();
pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
InputStream inLd = ld.getDataStream();
int ch;
while ((ch = inLd.read()) >= 0) {
bOut.write(ch);
}
//System.out.println(bOut.toString());
bOut.writeTo(new FileOutputStream(ld.getFileName()));
//return bOut;
}
public static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey) throws IOException, NoSuchProviderException, PGPException {
Security.addProvider(new BouncyCastleProvider());
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));
comData.close();
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));
cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey));
byte[] bytes = bOut.toByteArray();
OutputStream cOut = cPk.open(out, bytes.length);
cOut.write(bytes);
cOut.close();
out.close();
}
Now here is how to invoke/run the above:
try {
decryptFile(new FileInputStream("encryptedFile.gpg"), new FileInputStream("PrivateKey.asc"), new FileInputStream("PublicKey.asc"), "yourKeyPassword".toCharArray());
PGPPublicKey pubKey = readPublicKeyFromCol(new FileInputStream("PublicKey.asc"));
encryptFile(new FileOutputStream("encryptedFileOutput.gpg"), "fileToEncrypt.txt", pubKey);
} catch (PGPException e) {
fail("exception: " + e.getMessage(), e.getUnderlyingException());
}

To any one looking for an alternative solution, see https://stackoverflow.com/a/42176529/7550201
final InputStream plaintextStream = BouncyGPG
.decryptAndVerifyStream()
.withConfig(keyringConfig)
.andRequireSignatureFromAllKeys("sender#example.com")
.fromEncryptedInputStream(cipherTextStream)
Long story short: Bouncycastle is programming is often a lot of cargo cult programming and I wrote a library to change that.

I've decided to go with a much different approach, which is to forego the use of bouncy castle altogether and simply use a runtime process instead. For me this solution is working and completely removes the complexity surrounding bouncy castle:
String[] gpgCommands = new String[] {
"gpg",
"--passphrase",
"password",
"--decrypt",
"test-files/accounts.txt.gpg"
};
Process gpgProcess = Runtime.getRuntime().exec(gpgCommands);
BufferedReader gpgOutput = new BufferedReader(new InputStreamReader(gpgProcess.getInputStream()));
BufferedReader gpgError = new BufferedReader(new InputStreamReader(gpgProcess.getErrorStream()));
After doing that you need to remember to drain your input stream as your process is execing or your program will probably hang depending on how much you're outputing. See my answer in this thread (and also that of Cameron Skinner and Matthew Wilson who got me on the proper path) for a bit more context: Calling GnuPG in Java via a Runtime Process to encrypt and decrypt files - Decrypt always hangs

The first Google result is this. It looks like you are trying to decrypt ElGamal data, but you are not passing in an ElGamal key.
There are two easy possibilities:
Your keyring collection has multiple keyrings.
Your keyring has subkeys.
You've picked DSA with ElGamal encryption, so I suspect at least the latter: Subkeys are signed by the master key; ElGamal is not a signing algorithm (I don't know if DSA and ElGamal can use the same key, but it's generally seen as a good idea to use different keys for different purposes).
I think you want something like this (also, secretKeyRing should probably be renamed to secretKeyRingCollection):
PGPSecretKey secretKey = secretKeyRing.getSecretKey(publicKeyEncryptedData.getKeyID());

The error message is difficult because it's not completely accurate. Besides the illegal key size or default parameters the exception doesn't says it could be failing because of crypto permission check fails. That means you haven't setup the JCE permissions properly. You'll need to install the JCE Unlimited Strength Policy.
You can see the debug messages by setting the system property on the jvm
java -Djava.security.debug=access ....

Related

Unable to decrypt CAST5 Encrypted PGP file using Bouncy Castle libraries on Java 8

We are using Bouncy Castle Java libraries for PGP Decryption and all was working well till we hit the point where we had to decrypt a file with the following attributes (snippet from pgpdump <filename> output)
> pgpdump file-name.csv.pgp
New: Symmetric-Key Encrypted Session Key Packet(tag 3)(29 bytes)
New version(4)
Sym alg - CAST5(sym 3)
Salted string-to-key(s2k 1):
Hash alg - SHA1(hash 2)
Salt - c3 89 7b 22 c0 90 a2 1f
Encrypted session key
-> sym alg(1 bytes) + session key
New: Public-Key Encrypted Session Key Packet(tag 1)(268 bytes)
New version(3)
Key ID - 0x00FB426B9D39D2D9
Pub alg - RSA Encrypt or Sign(pub 1)
RSA m^e mod n(2048 bits) - ...
-> m = sym alg(1 byte) + checksum(2 bytes) + PKCS-1 block type 02
New: Public-Key Encrypted Session Key Packet(tag 1)(268 bytes)
New version(3)
Key ID - 0x13BCC3DF42A3585C
Pub alg - RSA Encrypt or Sign(pub 1)
RSA m^e mod n(2048 bits) - ...
-> m = sym alg(1 byte) + checksum(2 bytes) + PKCS-1 block type 02
New: Symmetrically Encrypted and MDC Packet(tag 18)(8192 bytes) partial start
Ver 1
Encrypted data [sym alg is specified in pub-key encrypted session key]
(plain text + MDC SHA1(20 bytes))
I am able to decrypt the file on my mac using
gpg --passphrase <passphrase> -v -d --pinentry-mode=loopback -o foo.out
Using the java code below based on BouncyCastle example, I am ending up with org.bouncycastle.openpgp.PGPException: Exception creating cipher CAST5.
public static byte[] decrypt(
byte[] encrypted,
char[] passPhrase)
throws IOException, PGPException, NoSuchProviderException
{
InputStream in = new ByteArrayInputStream(encrypted);
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
//
// the first object might be a PGP marker packet.
//
if (o instanceof PGPEncryptedDataList)
{
enc = (PGPEncryptedDataList)o;
}
else
{
enc = (PGPEncryptedDataList)pgpF.nextObject();
}
PGPPBEEncryptedData pbe = (PGPPBEEncryptedData)enc.get(0);
//****************************************
// Hitting an exception here while doing pbe.getDataStream()...
// Getting "org.bouncycastle.openpgp.PGPException: Exception creating cipher CAST5"
//****************************************
InputStream clear = pbe.getDataStream(new JcePBEDataDecryptorFactoryBuilder(new JcaPGPDigestCalculatorProviderBuilder().setProvider("BC").build()).setProvider("BC").build(passPhrase));
PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
PGPCompressedData cData = (PGPCompressedData)pgpFact.nextObject();
pgpFact = new PGPObjectFactory(cData.getDataStream());
PGPLiteralData ld = (PGPLiteralData)pgpFact.nextObject();
return Streams.readAll(ld.getInputStream());
}
Exception info -
org.bouncycastle.openpgp.PGPException: Exception creating cipher
at org.bouncycastle.openpgp.operator.jcajce.OperatorHelper.createDataDecryptor(Unknown Source)
at org.bouncycastle.openpgp.operator.jcajce.JcePBEDataDecryptorFactoryBuilder$1.createDataDecryptor(Unknown Source)
at org.bouncycastle.openpgp.PGPPBEEncryptedData.getDataStream(Unknown Source)
...
...
Caused by: java.lang.IllegalArgumentException: unknown symmetric algorithm: 112
at org.bouncycastle.openpgp.PGPUtil.getSymmetricCipherName(Unknown Source)
... 21 more
I tried running Security.getAlgorithms("Cipher") as suggested here and I do not see CAST5 in the list of returned algorithms.
I'm on Java 1.8 and using Bouncy Castle libraries below
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk15on</artifactId>
<version>1.62</version>
<scope>compile</scope>
</dependency>
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk15on</artifactId>
<version>1.62</version>
<scope>compile</scope>
</dependency>
Any ideas what should I be pursuing next?
I also made sure that my code is setting Security.addProvider(new BouncyCastleProvider()); during startup.
I am feeling this should be a matter of adding more support to JVM Ciper Suites. But I don't know where to start.
Please advice.
NOTE: The above code was an example from internet, but based on Maarten Bodewes comments, I realized after copying that code from internet locally - I had made a lot of changes to get it functional.
Exact code I am using from my machine
public static void decryptSymmetricFile(
InputStream in,
char[] passwd,
String outFileName) throws IOException {
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
Iterator<?> it = enc.getEncryptedDataObjects();
PGPPBEEncryptedData pbe = (PGPPBEEncryptedData) it.next();
try {
Security.getAlgorithms("Cipher");
//****************************************
// Hitting an exception here while doing pbe.getDataStream()...
// Getting "org.bouncycastle.openpgp.PGPException: Exception creating cipher CAST5"
//****************************************
InputStream dataStream = pbe.getDataStream((new JcePBEDataDecryptorFactoryBuilder((new JcaPGPDigestCalculatorProviderBuilder()).setProvider("BC").build())).setProvider("BC").build(passwd));
JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(dataStream);
PGPCompressedData pgpCompressedData = (PGPCompressedData) pgpObjectFactory.nextObject();
pgpObjectFactory = new JcaPGPObjectFactory(pgpCompressedData.getDataStream());
PGPLiteralData literalData = (PGPLiteralData) pgpObjectFactory.nextObject();
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
InputStream unc = literalData.getInputStream();
int ch;
while ((ch = unc.read()) >= 0)
{
bOut.write(ch);
}
if (pbe.isIntegrityProtected() && !pbe.verify())
{
throw new PGPException("Data is integrity protected but integrity is lost.");
}
OutputStream fOut = new BufferedOutputStream(new FileOutputStream(outFileName));
Streams.pipeAll(new ByteArrayInputStream(bOut.toByteArray()), fOut);
fOut.close();
} catch (Exception ex) {
ex.printStackTrace();
logger.error(ex.getLocalizedMessage());
}
}

What can i use equivalent to RSAPKCS1SignatureDeformatter in java?

I have file creation process in which we create a signature using RSAPKCS1Signature class for signing & validating in c#.
Now we are moving to Java, we are using the same algorithm used in c# but it's not validating the same file in java which is created in c#.
I have attached the sample code using. Please suggest need full.
Thanks..!
C#:
RSACryptoServiceProvider RSA = new RSACryptoServiceProvider();
byte[] Hash = {59,4,248,102,77,97,142,201,210,12,224,93,25,41,100,197,213,134,130,135};
RSAPKCS1SignatureFormatter RSAFormatter = new RSAPKCS1SignatureFormatter(RSA);
RSAFormatter.SetHashAlgorithm("SHA1");
byte[] SignedHash = RSAFormatter.CreateSignature(Hash);
RSAPKCS1SignatureDeformatter RSADeformatter = new
RSAPKCS1SignatureDeformatter(RSA);
RSADeformatter.SetHashAlgorithm("SHA1");
Console.WriteLine(RSADeformatter.VerifySignature(Hash, SignedHash));
Java:
KeyStore ex = KeyStore.getInstance("JKS");
ex.load("c://sample.jks", "password");
PrivateKey privateKey = (PrivateKey) ex.getKey("1", "password");
Signature signature = Signature.getInstance("SHA1WithRSA");
signature.initSign(privateKey);
String line = null;
while ((line = reader.readLine()) != null) {
signature.update(line.getBytes());
}
signature.sign();

Bouncycastle PGP encryption error Illegal Key Size

I'm currently writing an encrypted messaging service in java, and I'm using the bouncycastle PGP library. I have written a test program that generates a key pair, and encrypts/decrypts a message. This was working for a while, but it recently stopped in the decrypt stage, giving me an InvalidKeyException.
I've done some research and downloaded the JCE .jar files and imported them into my project (through Eclipse project -> properties -> add external JARs). I saw that for windows users, they should be put into a specific folder in the java library, but i couldn't find a similar one on my Mac. I tried looking through the usr/library folder but couldn't find anything of use.
Has anyone solved this issue on Mac?
EDIT: here's some code from my main test function
// decrypt
byte[] decrypted = PGPEncryptDecrypt.decrypt(encFromFile, secKey, pass.toCharArray());
Here's my decrypt method( this was not written by me, but I made a PGPEncryptDecrypt class to hold related static methods, and it worked for me)
public static byte[] decrypt(byte[] encrypted, InputStream keyIn, char[] password)
throws IOException, PGPException, NoSuchProviderException {
InputStream in = new ByteArrayInputStream(encrypted);
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpF = new PGPObjectFactory(in);
PGPEncryptedDataList enc = null;
Object o = pgpF.nextObject();
//
// the first object might be a PGP marker packet.
//
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
//
// find the secret key
//
Iterator it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(keyIn));
while (sKey == null && it.hasNext()) {
pbe = (PGPPublicKeyEncryptedData) it.next();
sKey = findSecretKey(pgpSec, pbe.getKeyID(), password);
}
if (sKey == null) {
throw new IllegalArgumentException(
"secret key for message not found.");
}
InputStream clear = pbe.getDataStream(sKey, "BC");
PGPObjectFactory pgpFact = new PGPObjectFactory(clear);
PGPCompressedData cData = (PGPCompressedData) pgpFact.nextObject();
pgpFact = new PGPObjectFactory(cData.getDataStream());
PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
InputStream unc = ld.getInputStream();
ByteArrayOutputStream out = new ByteArrayOutputStream();
int ch;
while ((ch = unc.read()) >= 0) {
out.write(ch);
}
byte[] returnBytes = out.toByteArray();
out.close();
return returnBytes;
}
The error points to the findSecretKey (in PGPEncryptDecrypt class) method, which is as follows
public static PGPPrivateKey findSecretKey(
PGPSecretKeyRingCollection pgpSec, long keyID, char[] pass)
throws PGPException, NoSuchProviderException {
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null) {
return null;
}
return pgpSecKey.extractPrivateKey(pass, "BC");
}
These functions all worked perfectly when i first implemented them, but they stopped working.
For anyone else looking, i found the answer to this after digging around a little.
what i did was open terminal, enter root library (as sudo), found the appropriate java library, and did a manual copy from my downloads folder into the appropriate java security folder
path was Library/Java/JavaVirtualMachines/jdk1.7.0_80.jdk/Contents/Home/jre/lib/security
then in there i did two cp filename commands to copy the appropriate file

Getting BouncyCastle to decrypt a GPG-encrypted message

How can I get BouncyCastle to decrypt a GPG-encrypted message?
I have created a GPG key pair at the CentOS 7 command line using gpg --gen-key. I chose RSA RSA as the encryption types, and I exported the keys using gpg --export-secret-key -a "User Name" > /home/username/username_private.key and gpg --armor --export 66677FC6 > /home/username/username_pubkey.asc
I am able to import username_pubkey.asc into a remote Thunderbird client of another email account and successfully send an encrypted email to username#mydomain.com. But when my Java/BouncyCastle code running at mydomain.com tries to decrypt the GPG-encoded data, it gives the following error:
org.bouncycastle.openpgp.PGPException:
Encrypted message contains a signed message - not literal data.
If you look at the code below, you will see this corresponds with the line in PGPUtils.decryptFile() which states else if (message instanceof PGPOnePassSignatureList) {throw new PGPException("Encrypted message contains a signed message - not literal data.");
The original code for this came from the blog entry at this link, though I made minor changes to get it to compile in Eclipse Luna with Java 7. A user of the linked blog reported the same error, and the blog author replied by saying that it does not work with GPG. So how do I fix this to make it work with GPG?
The Java decryption code starts when the GPG-encoded-file and the GPG-secret-key are passed into Tester.testDecrypt() as follows:
Tester.java contains:
public InputStream testDecrypt(String input, String output, String passphrase, String skeyfile) throws Exception {
PGPFileProcessor p = new PGPFileProcessor();
p.setInputFileName(input);//this is GPG-encoded data sent from another email address using Thunderbird
p.setOutputFileName(output);
p.setPassphrase(passphrase);
p.setSecretKeyFileName(skeyfile);//this is the GPG-generated key
return p.decrypt();//this line throws the error
}
PGPFileProcessor.java includes:
public InputStream decrypt() throws Exception {
FileInputStream in = new FileInputStream(inputFileName);
FileInputStream keyIn = new FileInputStream(secretKeyFileName);
FileOutputStream out = new FileOutputStream(outputFileName);
PGPUtils.decryptFile(in, out, keyIn, passphrase.toCharArray());//error thrown here
in.close();
out.close();
keyIn.close();
InputStream result = new FileInputStream(outputFileName);//I changed return type from boolean on 1/27/15
Files.deleteIfExists(Paths.get(outputFileName));//I also added this to accommodate change of return type on 1/27/15
return result;
}
PGPUtils.java includes:
/**
* decrypt the passed in message stream
*/
#SuppressWarnings("unchecked")
public static void decryptFile(InputStream in, OutputStream out, InputStream keyIn, char[] passwd)
throws Exception
{
Security.addProvider(new BouncyCastleProvider());
in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in);
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPObjectFactory pgpF = new JcaPGPObjectFactory(in);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
//
// the first object might be a PGP marker packet.
//
if (o instanceof PGPEncryptedDataList) {enc = (PGPEncryptedDataList) o;}
else {enc = (PGPEncryptedDataList) pgpF.nextObject();}
//
// find the secret key
//
Iterator<PGPPublicKeyEncryptedData> it = enc.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData pbe = null;
while (sKey == null && it.hasNext()) {
pbe = it.next();
sKey = findPrivateKey(keyIn, pbe.getKeyID(), passwd);
}
if (sKey == null) {throw new IllegalArgumentException("Secret key for message not found.");}
InputStream clear = pbe.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object message = plainFact.nextObject();
if (message instanceof PGPCompressedData) {
PGPCompressedData cData = (PGPCompressedData) message;
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPObjectFactory pgpFact = new JcaPGPObjectFactory(cData.getDataStream());
message = pgpFact.nextObject();
}
if (message instanceof PGPLiteralData) {
PGPLiteralData ld = (PGPLiteralData) message;
InputStream unc = ld.getInputStream();
int ch;
while ((ch = unc.read()) >= 0) {out.write(ch);}
} else if (message instanceof PGPOnePassSignatureList) {
throw new PGPException("Encrypted message contains a signed message - not literal data.");
} else {
throw new PGPException("Message is not a simple encrypted file - type unknown.");
}
if (pbe.isIntegrityProtected()) {
if (!pbe.verify()) {throw new PGPException("Message failed integrity check");}
}
}
/**
* Load a secret key ring collection from keyIn and find the private key corresponding to
* keyID if it exists.
*
* #param keyIn input stream representing a key ring collection.
* #param keyID keyID we want.
* #param pass passphrase to decrypt secret key with.
* #return
* #throws IOException
* #throws PGPException
* #throws NoSuchProviderException
*/
public static PGPPrivateKey findPrivateKey(InputStream keyIn, long keyID, char[] pass)
throws IOException, PGPException, NoSuchProviderException
{
//1/26/15 added Jca prefix to avoid eclipse warning, also used https://www.bouncycastle.org/docs/pgdocs1.5on/index.html
PGPSecretKeyRingCollection pgpSec = new JcaPGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
return findPrivateKey(pgpSec.getSecretKey(keyID), pass);
}
/**
* Load a secret key and find the private key in it
* #param pgpSecKey The secret key
* #param pass passphrase to decrypt secret key with
* #return
* #throws PGPException
*/
public static PGPPrivateKey findPrivateKey(PGPSecretKey pgpSecKey, char[] pass)
throws PGPException
{
if (pgpSecKey == null) return null;
PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
return pgpSecKey.extractPrivateKey(decryptor);
}
The complete code of all three Java files can be found on a file sharing site by clicking on this link.
The complete stack trace for the error can be found by clicking on this link.
For reference, the GUI instructions for encryption by the remote Thunderbird sender are summarized in the following screen shot:
I have read many postings and links about this. In particular, this other SO posting looks similar, but is different. My Keys use RSA RSA, but the other posting does not.
EDIT#1
As per #DavidHook's suggestion, I have read SignedFileProcessor, and I am starting to read the much longer RFC 4880. However, I need actual working code to study in order to understand this. Most people who find this via google searches will also need working code to illustrate the examples.
For reference, the SignedFileProcessor.verifyFile() method recommended by #DavidHook is as follows. How should this be customized to fix the problems in the code above?
private static void verifyFile(InputStream in, InputStream keyIn) throws Exception {
in = PGPUtil.getDecoderStream(in);
PGPObjectFactory pgpFact = new PGPObjectFactory(in);
PGPCompressedData c1 = (PGPCompressedData)pgpFact.nextObject();
pgpFact = new PGPObjectFactory(c1.getDataStream());
PGPOnePassSignatureList p1 = (PGPOnePassSignatureList)pgpFact.nextObject();
PGPOnePassSignature ops = p1.get(0);
PGPLiteralData p2 = (PGPLiteralData)pgpFact.nextObject();
InputStream dIn = p2.getInputStream();
int ch;
PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
PGPPublicKey key = pgpRing.getPublicKey(ops.getKeyID());
FileOutputStream out = new FileOutputStream(p2.getFileName());
ops.initVerify(key, "BC");
while ((ch = dIn.read()) >= 0){
ops.update((byte)ch);
out.write(ch);
}
out.close();
PGPSignatureList p3 = (PGPSignatureList)pgpFact.nextObject();
if (ops.verify(p3.get(0))){System.out.println("signature verified.");}
else{System.out.println("signature verification failed.");}
}
EDIT#2
The SignedFileProcessor.verifyFile() method recommended by #DavidHook is almost identical to the PGPUtils.verifyFile() method in my code above, except that PGPUtils.verifyFile() makes a copy of extractContentFile and calls PGPOnePassSignature.init() instead of PGPOnePassSignature.initVerify(). This may be due to a version difference. Also, PGPUtils.verifyFile() returns a boolean, while SignedFileProcessor.verifyFile() gives SYSO for the two boolean values and returns void after the SYSO.
If I interpret #JRichardSnape's comments correctly, this means that the verifyFile() method might best be called upstream to confirm the signature of the incoming file using the sender's public key, and then, if the signature on the file is verified, using another method to decrypt the file using the recipient's private key. Is this correct? If so, how do I restructure the code to accomplish this?
if anyone is interested to know how to encrypt and decrypt gpg files using bouncy castle openPGP library, check the below java code:
The below are the 4 methods you going to need:
The below method will read and import your secret key from .asc file:
public static PGPSecretKey readSecretKeyFromCol(InputStream in, long keyId) throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(in, new BcKeyFingerprintCalculator());
PGPSecretKey key = pgpSec.getSecretKey(keyId);
if (key == null) {
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
return key;
}
The below method will read and import your public key from .asc file:
#SuppressWarnings("rawtypes")
public static PGPPublicKey readPublicKeyFromCol(InputStream in) throws IOException, PGPException {
in = PGPUtil.getDecoderStream(in);
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(in, new BcKeyFingerprintCalculator());
PGPPublicKey key = null;
Iterator rIt = pgpPub.getKeyRings();
while (key == null && rIt.hasNext()) {
PGPPublicKeyRing kRing = (PGPPublicKeyRing) rIt.next();
Iterator kIt = kRing.getPublicKeys();
while (key == null && kIt.hasNext()) {
PGPPublicKey k = (PGPPublicKey) kIt.next();
if (k.isEncryptionKey()) {
key = k;
}
}
}
if (key == null) {
throw new IllegalArgumentException("Can't find encryption key in key ring.");
}
return key;
}
The below 2 methods to decrypt and encrypt gpg files:
public void decryptFile(InputStream in, InputStream secKeyIn, InputStream pubKeyIn, char[] pass) throws IOException, PGPException, InvalidCipherTextException {
Security.addProvider(new BouncyCastleProvider());
PGPPublicKey pubKey = readPublicKeyFromCol(pubKeyIn);
PGPSecretKey secKey = readSecretKeyFromCol(secKeyIn, pubKey.getKeyID());
in = PGPUtil.getDecoderStream(in);
JcaPGPObjectFactory pgpFact;
PGPObjectFactory pgpF = new PGPObjectFactory(in, new BcKeyFingerprintCalculator());
Object o = pgpF.nextObject();
PGPEncryptedDataList encList;
if (o instanceof PGPEncryptedDataList) {
encList = (PGPEncryptedDataList) o;
} else {
encList = (PGPEncryptedDataList) pgpF.nextObject();
}
Iterator<PGPPublicKeyEncryptedData> itt = encList.getEncryptedDataObjects();
PGPPrivateKey sKey = null;
PGPPublicKeyEncryptedData encP = null;
while (sKey == null && itt.hasNext()) {
encP = itt.next();
secKey = readSecretKeyFromCol(new FileInputStream("PrivateKey.asc"), encP.getKeyID());
sKey = secKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass));
}
if (sKey == null) {
throw new IllegalArgumentException("Secret key for message not found.");
}
InputStream clear = encP.getDataStream(new BcPublicKeyDataDecryptorFactory(sKey));
pgpFact = new JcaPGPObjectFactory(clear);
PGPCompressedData c1 = (PGPCompressedData) pgpFact.nextObject();
pgpFact = new JcaPGPObjectFactory(c1.getDataStream());
PGPLiteralData ld = (PGPLiteralData) pgpFact.nextObject();
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
InputStream inLd = ld.getDataStream();
int ch;
while ((ch = inLd.read()) >= 0) {
bOut.write(ch);
}
//System.out.println(bOut.toString());
bOut.writeTo(new FileOutputStream(ld.getFileName()));
//return bOut;
}
public static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey) throws IOException, NoSuchProviderException, PGPException {
Security.addProvider(new BouncyCastleProvider());
ByteArrayOutputStream bOut = new ByteArrayOutputStream();
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
PGPUtil.writeFileToLiteralData(comData.open(bOut), PGPLiteralData.BINARY, new File(fileName));
comData.close();
PGPEncryptedDataGenerator cPk = new PGPEncryptedDataGenerator(new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.TRIPLE_DES).setSecureRandom(new SecureRandom()));
cPk.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encKey));
byte[] bytes = bOut.toByteArray();
OutputStream cOut = cPk.open(out, bytes.length);
cOut.write(bytes);
cOut.close();
out.close();
}
Now here is how to invoke/run the above:
try {
decryptFile(new FileInputStream("encryptedFile.gpg"), new FileInputStream("PrivateKey.asc"), new FileInputStream("PublicKey.asc"), "yourKeyPassword".toCharArray());
PGPPublicKey pubKey = readPublicKeyFromCol(new FileInputStream("PublicKey.asc"));
encryptFile(new FileOutputStream("encryptedFileOutput.gpg"), "fileToEncrypt.txt", pubKey);
} catch (PGPException e) {
fail("exception: " + e.getMessage(), e.getUnderlyingException());
}
It just means that content has been signed and then encrypted, the routine provided does not know how to deal with it, but at least tells you that. PGP protocol presents as a series of packets some of which can be wrapped in other ones (for example compressed data can also wrap signed data or simply literal data, these can be used to generate encrypted data as well, actual content always appears in literal data).
If you look at the verifyFile method in the SignedFileProcessor in the Bouncy Castle OpenPGP examples package you will see how to handle the signature data and get to the literal data containing the actual content.
I would also recommend looking at RFC 4880 so you have some idea of how the protocol works. The protocol is very loose and both GPG, BC, and the variety of products out there reflect this - that said the looseness does mean that if you try and cut and paste your way to a solution you'll end up with a disaster. It's not complicated, but understanding is required here as well.

How do I store and read PGP public keys as strings using Bouncycastle Java?

I have been trying to create, encode, store, retrieve, and decode a Bouncy Castle PGP public key. I get what appears to be the wrong output and an EOFException when I try to read the key back in. The key will be stored in a database as a string.
The original RSA encryption public key is extracted from the key ring as follows:
#SuppressWarnings("unchecked")
public PGPPublicKey getPublicKey() {
PGPPublicKey pk = null;
Iterator<PGPPublicKey> it = publicKeyRing.getPublicKeys();
while (pk == null && it.hasNext()) {
PGPPublicKey key = it.next();
if (key.isEncryptionKey()) {
pk = key;
}
}
return pk;
}
It is encoded, ASCII armored, and stored as a string as follows:
PGPPublicKey contactPK = realContact.getPublicKey();
ByteArrayOutputStream out = new ByteArrayOutputStream();
ArmoredOutputStream armored = new ArmoredOutputStream(out);
contactPK.encode(armored);
armored.close();
publicKey = new String(out.toByteArray(), Charset.forName("US-ASCII"));
This gets me a PGP message block where I would expect a PGP public key block:
-----BEGIN PGP MESSAGE-----\nVersion: BCPG v1.50\n\nuQINBFO8StkCEACQ4vrDnBTDjEvQkGwrAHuJSBZL8tNLxhZ9B74afhObhLVzW6ZB\nT3pk/5XcSPOTvcWd9k1yOKJUabCuF5ixFmMz+niFqUVQTtnl7aqOZ+GrDEzmoYmG\nNQROP0EiA1TWtm2+Ja0FqiJauXytt1sIF/Pr5L47FCjtmZKVoXTP8RVFfGLPB0kT\ndjOz53PaEE3GSValh85w24XIH2/gczURUnjphCX1bRwTFr14SfA9X/rFWqv9SqWQ\nV8OiIWrSiwNd5RLJ9q0B+viDzoxrjmnMJZikxhKiuNVKJCu2ccBdMrbW42iBM2w3\n
... (for brevity)
\n-----END PGP MESSAGE-----
When I try to read the string back in to recreate the public key, I get an EOFException:
// Import the public key.
ByteArrayInputStream in =
new ByteArrayInputStream(stored.publicKey.getBytes(
Charset.forName("US-ASCII")));
// Needed to read ASCII armored keys
InputStream decoded = PGPUtil.getDecoderStream(in);
BCPGInputStream bcpgIn = new BCPGInputStream(decoded);
RSAPublicBCPGKey bcpgKey = new RSAPublicBCPGKey(bcpgIn);
PublicKeyPacket pkPacket = new PublicKeyPacket(PublicKeyAlgorithmTags.RSA_ENCRYPT,
new Date(), bcpgKey);
publicKey = new PGPPublicKey(pkPacket, new BcKeyFingerprintCalculator());
The exception occurs when I create the RSAPublicBCPGKey.
I am doing something very wrong, but I can't figure out what it is. Can anyone help?
Say you already have your pgp public key (ascii armored) in a String str:
InputStream in=new ByteArrayInputStream(str.getBytes());
in = org.bouncycastle.openpgp.PGPUtil.getDecoderStream(in);
JcaPGPPublicKeyRingCollection pgpPub = new JcaPGPPublicKeyRingCollection(in);
in.close();
PGPPublicKey key = null;
Iterator<PGPPublicKeyRing> rIt = pgpPub.getKeyRings();
while (key == null && rIt.hasNext())
{
PGPPublicKeyRing kRing = rIt.next();
Iterator<PGPPublicKey> kIt = kRing.getPublicKeys();
while (key == null && kIt.hasNext())
{
PGPPublicKey k = kIt.next();
if (k.isEncryptionKey())
{
key = k;
}
}
}
return key;
Now, your variable key will have your PGPPublicKey.
I ran into this issue, and it turned out that the Public Key Ring was actually the public key itself. When I was running the iterator I was actually getting subkeys (which turns into that message block instead of the public key block). If I'm not explaining that well, check out this explanation.
Assuming realContact is a public key ring that only contains subkeys, you don't actually need to run it through getPublicKey().

Categories