How can I decrypt and verify a file encrypted with PGP with the BouncyCastle Java API?
Encryption Code:
private static void encryptFile(OutputStream out, String fileName, PGPPublicKey encKey, PGPSecretKey pgpSec, boolean armor, boolean withIntegrityCheck, char[] pass) throws IOException, NoSuchProviderException {
if (armor) {
out = new ArmoredOutputStream(out);
}
try {
PGPEncryptedDataGenerator encGen =
new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(PGPEncryptedData.CAST5).setWithIntegrityPacket(withIntegrityCheck).setSecureRandom(
new SecureRandom())
.setProvider("BC"));
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider("BC"));
OutputStream encryptedOut = encGen.open(out, new byte[BUFFER_SIZE]);
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(PGPCompressedData.ZIP);
OutputStream compressedData = comData.open(encryptedOut);
//OutputStream compressedData = encryptedOut;
PGPPrivateKey pgpPrivKey = pgpSec.extractPrivateKey(
new JcePBESecretKeyDecryptorBuilder().setProvider("BC").build(pass));
PGPSignatureGenerator sGen = new PGPSignatureGenerator(new JcaPGPContentSignerBuilder(
pgpSec.getPublicKey().getAlgorithm(), PGPUtil.SHA1).setProvider("BC"));
sGen.init(PGPSignature.BINARY_DOCUMENT, pgpPrivKey);
Iterator it = pgpSec.getPublicKey().getUserIDs();
if (it.hasNext()) {
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, (String) it.next());
sGen.setHashedSubpackets(spGen.generate());
}
//BCPGOutputStream bOut = new BCPGOutputStream(compressedData);
sGen.generateOnePassVersion(false).encode(compressedData); // bOut
File file = new File(fileName);
PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
OutputStream lOut = lGen.open(compressedData, PGPLiteralData.BINARY, file.getName(), new Date(),
new byte[BUFFER_SIZE]); //bOut
FileInputStream fIn = new FileInputStream(file);
int ch;
while ((ch = fIn.read()) >= 0) {
lOut.write(ch);
sGen.update((byte) ch);
}
fIn.close();
lOut.close();
lGen.close();
sGen.generate().encode(compressedData);
//bOut.close();
comData.close();
compressedData.close();
encryptedOut.close();
encGen.close();
if (armor) {
out.close();
}
} catch (PGPException e) {
System.err.println(e);
if (e.getUnderlyingException() != null) {
e.getUnderlyingException().printStackTrace();
}
} catch (SignatureException e) {
System.err.println(e);
}
}
Decryption Code:
public static void decryptFile(InputStream in, InputStream keyIn, char[] passwd, OutputStream fOut, InputStream publicKeyIn) throws IOException, NoSuchProviderException, SignatureException,
PGPException {
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();
}
//
// 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 = PGPTools.findSecretKey(pgpSec, pbe.getKeyID(), passwd);
}
if (sKey == null) {
throw new IllegalArgumentException("secret key for message not found.");
}
InputStream clear = pbe.getDataStream(
new JcePublicKeyDataDecryptorFactoryBuilder().setProvider("BC").build(sKey));
PGPObjectFactory plainFact = new PGPObjectFactory(clear);
Object message = null;
PGPOnePassSignatureList onePassSignatureList = null;
PGPSignatureList signatureList = null;
PGPCompressedData compressedData = null;
message = plainFact.nextObject();
ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
while (message != null) {
log.trace(message.toString());
if (message instanceof PGPCompressedData) {
compressedData = (PGPCompressedData) message;
plainFact = new PGPObjectFactory(compressedData.getDataStream());
message = plainFact.nextObject();
}
if (message instanceof PGPLiteralData) {
// have to read it and keep it somewhere.
Streams.pipeAll(((PGPLiteralData) message).getInputStream(), actualOutput);
} else if (message instanceof PGPOnePassSignatureList) {
onePassSignatureList = (PGPOnePassSignatureList) message;
} else if (message instanceof PGPSignatureList) {
signatureList = (PGPSignatureList) message;
} else {
throw new PGPException("message unknown message type.");
}
message = plainFact.nextObject();
}
actualOutput.close();
PGPPublicKey publicKey = null;
byte[] output = actualOutput.toByteArray();
if (onePassSignatureList == null || signatureList == null) {
throw new PGPException("Poor PGP. Signatures not found.");
} else {
for (int i = 0; i < onePassSignatureList.size(); i++) {
PGPOnePassSignature ops = onePassSignatureList.get(0);
log.trace("verifier : " + ops.getKeyID());
PGPPublicKeyRingCollection pgpRing = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(publicKeyIn));
publicKey = pgpRing.getPublicKey(ops.getKeyID());
if (publicKey != null) {
ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider("BC"), publicKey);
ops.update(output);
PGPSignature signature = signatureList.get(i);
if (ops.verify(signature)) {
Iterator<?> userIds = publicKey.getUserIDs();
while (userIds.hasNext()) {
String userId = (String) userIds.next();
log.trace("Signed by {}", userId);
}
log.trace("Signature verified");
} else {
throw new SignatureException("Signature verification failed");
}
}
}
}
if (pbe.isIntegrityProtected() && !pbe.verify()) {
throw new PGPException("Data is integrity protected but integrity is lost.");
} else if (publicKey == null) {
throw new SignatureException("Signature not found");
} else {
fOut.write(output);
fOut.flush();
fOut.close();
}
}
For reference, this is what PGPTools.findSecretKey
does:
public static PGPPrivateKey findSecretKey(InputStream keyIn, long keyID, char[] pass)
throws IOException, PGPException {
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(keyIn));
PGPSecretKey pgpSecKey = pgpSec.getSecretKey(keyID);
if (pgpSecKey == null) return null;
PBESecretKeyDecryptor decryptor = new BcPBESecretKeyDecryptorBuilder(new BcPGPDigestCalculatorProvider()).build(pass);
return pgpSecKey.extractPrivateKey(decryptor);
}