PgpHelper.java
package com.hsbc.baas;
import lombok.NoArgsConstructor;
import lombok.extern.java.Log;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.*;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.*;
import org.bouncycastle.openpgp.operator.PBESecretKeyDecryptor;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.*;
import org.bouncycastle.util.io.Streams;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.security.*;
import java.util.ArrayList;
import java.util.Date;
import java.util.Iterator;
import java.util.List;
/**
* Created by 44024985 on 04/09/2018.
*/
@Log
public class PgpHelper {
private JcaKeyFingerprintCalculator keyFingerPrintCalculator;
private Provider bcp;
public PgpHelper() {
this.keyFingerPrintCalculator = new JcaKeyFingerprintCalculator();
this.bcp = new BouncyCastleProvider();
}
/**
* @param publicKeyInputStream bank/public key input stream.
* @return List of PGP Public Keys
* @throws IOException, PGPException
*/
public List<PGPPublicKey> readPublicKey(InputStream publicKeyInputStream) throws IOException, PGPException {
// public key file is decoded and used to generate a key ring object that stores a list of key rings. These contain the public keys.
// The fingerPrintCalculator is used to calculate the fingerprint of each key which is needed to verify key authenticity.
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(PGPUtil.getDecoderStream(publicKeyInputStream),
this.keyFingerPrintCalculator);
List<PGPPublicKey> keys = new ArrayList<>();
pgpPub.getKeyRings().forEachRemaining(keyRing -> keyRing.getPublicKeys().forEachRemaining(keys::add));
return keys;
}
/**
* @param pgpSecKey Secret Key.
* @param pass passphrase to decrypt secret key with.
* @return PGPPrivate key.
* @throws PGPException
*/
public PGPPrivateKey findSecretKey(PGPSecretKey pgpSecKey, char[] pass)
throws PGPException {
// Extract a private key from a PGPSecretKey object.
PBESecretKeyDecryptor decryptor = new JcePBESecretKeyDecryptorBuilder(
new JcaPGPDigestCalculatorProviderBuilder().setProvider(this.bcp).build()).setProvider(this.bcp).build(pass);
return pgpSecKey.extractPrivateKey(decryptor);
}
public List<PGPSecretKey> readSecretKey(InputStream input) throws IOException, PGPException {
// Secret key ring collection is generated from the private key file input stream.
PGPSecretKeyRingCollection pgpSec = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(input), this.keyFingerPrintCalculator);
// Loop through key rings, then through keys and add keys found to list.
List<PGPSecretKey> keys = new ArrayList<>();
pgpSec.getKeyRings().forEachRemaining(keyRing -> keyRing.getSecretKeys().forEachRemaining(keys::add));
return keys;
}
/**
* @param outf the output stream
* @param inputStream the input stream
* @param encKey the PGP Public key
* @param privateKey the private key
* @throws PGPException
*/
public void encryptAndSign(OutputStream outf, InputStream inputStream, PGPPublicKey encKey,
PGPPrivateKey privateKey) throws IOException {
OutputStream out = new ArmoredOutputStream(outf);
OutputStream lOut = null;
try {
// Encrypted Data Generator
PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256).setWithIntegrityPacket(true)
.setSecureRandom(new SecureRandom()).setProvider(this.bcp));
encGen.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(encKey).setProvider(this.bcp));
OutputStream encryptedOut = encGen.open(out, new byte[inputStream.available()]);
// Compressed Data Generator
PGPCompressedDataGenerator comData = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
OutputStream compressedData = comData.open(encryptedOut);
// Signature Generator
PGPSignatureGenerator sGen = new PGPSignatureGenerator(
new JcaPGPContentSignerBuilder(privateKey.getPublicKeyPacket().getAlgorithm(), HashAlgorithmTags.SHA512)
.setProvider(this.bcp));
sGen.init(PGPSignature.BINARY_DOCUMENT, privateKey);
// Get the user ID from the encryption key, and use this to generate a signed subpacket.
Iterator it = encKey.getUserIDs();
if (it.hasNext()) {
PGPSignatureSubpacketGenerator spGen = new PGPSignatureSubpacketGenerator();
spGen.setSignerUserID(false, (String) it.next());
sGen.setHashedSubpackets(spGen.generate());
}
// Write the encoded packet to compressed data stream.
sGen.generateOnePassVersion(false).encode(compressedData); // bOut
// Create a literal data output stream
PGPLiteralDataGenerator lGen = new PGPLiteralDataGenerator();
lOut = lGen.open(compressedData, PGPLiteralData.BINARY, "Sample-Data", new Date(),
new byte[inputStream.available()]);
// Write and sign data.
byte[] data = IOUtils.toByteArray(inputStream);
lOut.write(data);
sGen.update(data);
lGen.close();
// Generate signature for compressed data.
sGen.generate().encode(compressedData);
comData.close();
compressedData.close();
encryptedOut.close();
encGen.close();
out.close();
} catch (PGPException e) {
log.warning(e.getLocalizedMessage());
if (e.getUnderlyingException() != null) {
e.getUnderlyingException().printStackTrace();
}
} catch (Exception e) {
System.err.println(e);
} finally {
if (lOut != null) lOut.close();
}
}
// private Object parseMessage(Object message, PGPObjectFactory plainFact, PGPCompressedData compressedData, PGPOnePassSignatureList onePassSignatureList, PGPSignatureList signatureList, ByteArrayOutputStream actualOutput) throws IOException, PGPException {
// while (message != null) {
// if (message instanceof PGPCompressedData) {
// compressedData = (PGPCompressedData) message;
// plainFact = new PGPObjectFactory(compressedData.getDataStream(), this.keyFingerPrintCalculator);
// 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();
// }
// }
private boolean checkSignatureVerified(PGPOnePassSignatureList onePassSignatureList, PGPSignatureList signatureList, List<PGPPublicKey> publicKeys, byte[] output) throws PGPException {
for (int i = 0; i < onePassSignatureList.size(); i++) {
PGPOnePassSignature ops = onePassSignatureList.get(0);
if (publicKeys != null) {
for (PGPPublicKey publicKey : publicKeys) {
ops.init(new JcaPGPContentVerifierBuilderProvider().setProvider(this.bcp), publicKey);
ops.update(output);
PGPSignature signature = signatureList.get(i);
if (ops.verify(signature)) {
return(true);
}
}
}
}
return(false);
}
public void decryptStream(InputStream in, OutputStream out, List<PGPPrivateKey> keysIn,
List<PGPPublicKey> publicKeys) throws Exception {
PGPObjectFactory pgpF = new PGPObjectFactory(PGPUtil.getDecoderStream(in), this.keyFingerPrintCalculator);
PGPEncryptedDataList enc;
Object o = pgpF.nextObject();
// Parse first object, which might be a PGP marker packet.
if (o instanceof PGPEncryptedDataList) {
enc = (PGPEncryptedDataList) o;
} else {
enc = (PGPEncryptedDataList) pgpF.nextObject();
}
// Find the secret key
Iterator<PGPEncryptedData> it = enc.getEncryptedDataObjects();
PGPPublicKeyEncryptedData pbe = null;
while (it.hasNext())
{
PGPEncryptedData encryptedData = it.next();
if (encryptedData instanceof PGPPublicKeyEncryptedData)
{
pbe = (PGPPublicKeyEncryptedData)encryptedData;
}
}
PublicKeyDataDecryptorFactory b;
InputStream clear = null;
for (PGPPrivateKey keyIn : keysIn) {
if (keyIn.getKeyID() == pbe.getKeyID()) {
b = new JcePublicKeyDataDecryptorFactoryBuilder().setProvider(this.bcp).setContentProvider(this.bcp).build(keyIn);
clear = pbe.getDataStream(b);
break;
}
}
PGPObjectFactory plainFact = new PGPObjectFactory(clear, this.keyFingerPrintCalculator);
Object message = plainFact.nextObject();
PGPOnePassSignatureList onePassSignatureList = null;
PGPSignatureList signatureList = null;
PGPCompressedData compressedData;
ByteArrayOutputStream actualOutput = new ByteArrayOutputStream();
while (message != null) {
if (message instanceof PGPCompressedData) {
compressedData = (PGPCompressedData) message;
plainFact = new PGPObjectFactory(compressedData.getDataStream(), this.keyFingerPrintCalculator);
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();
byte[] output = actualOutput.toByteArray();
if (onePassSignatureList == null || signatureList == null) {
throw new PGPException("Poor PGP. Signatures not found.");
} else {
boolean signatureVerified = checkSignatureVerified(onePassSignatureList,signatureList,publicKeys,output);
if (!signatureVerified) {
throw new SignatureException("Signature verification failed");
}
}
if (pbe.isIntegrityProtected() && !pbe.verify()) {
throw new PGPDataValidationException("Data is integrity protected but integrity is lost.");
} else if (publicKeys == null || publicKeys.isEmpty()) {
throw new SignatureException("Signature not found");
} else {
out.write(output);
out.flush();
out.close();
}
}
}