Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,12 @@
<artifactId>bcprov-jdk18on</artifactId>
<version>1.81</version>
</dependency>

<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk18on</artifactId>
<version>1.81</version>
</dependency>

<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
Expand Down
4 changes: 4 additions & 0 deletions src/main/java/de/usd/cstchef/Utils.java
Original file line number Diff line number Diff line change
Expand Up @@ -87,6 +87,8 @@
import de.usd.cstchef.operations.encryption.AesEncryption;
import de.usd.cstchef.operations.encryption.DesDecryption;
import de.usd.cstchef.operations.encryption.DesEncryption;
import de.usd.cstchef.operations.encryption.PgpDecryption;
import de.usd.cstchef.operations.encryption.PgpEncryption;
import de.usd.cstchef.operations.encryption.RsaDecryption;
import de.usd.cstchef.operations.encryption.RsaEncryption;
import de.usd.cstchef.operations.encryption.SM4Decryption;
Expand Down Expand Up @@ -139,6 +141,7 @@
import de.usd.cstchef.operations.setter.LineSetter;
import de.usd.cstchef.operations.signature.JWTDecode;
import de.usd.cstchef.operations.signature.JWTSign;
import de.usd.cstchef.operations.signature.PgpSignature;
import de.usd.cstchef.operations.signature.RsaSignature;
import de.usd.cstchef.operations.signature.SM2Signature;
import de.usd.cstchef.operations.signature.SoapMultiSignature;
Expand Down Expand Up @@ -404,6 +407,7 @@ public static Class<? extends Operation>[] getOperationsDev() {
NoOperation.class, NumberCompare.class,
Prefix.class, PlainRequest.class,
RandomNumber.class, RandomUUID.class, ReadFile.class, RegexExtractor.class, RegexMatch.class, RequestBuilder.class, Reverse.class,
PgpDecryption.class, PgpEncryption.class, PgpSignature.class,
Replace.class, RIPEMD.class, RsaDecryption.class, RsaEncryption.class, RsaSignature.class, RemoveWhitespace.class, RequestToResponse.class,
SM2Signature.class, SM3.class, SM4Encryption.class, SM4Decryption.class, SoapMultiSignature.class, StopOperation.class,
SetIfEmpty.class, SHA1.class, SHA2.class, SHA3.class, Skein.class, SplitAndSelect.class,
Expand Down
160 changes: 160 additions & 0 deletions src/main/java/de/usd/cstchef/operations/encryption/PgpDecryption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,160 @@
package de.usd.cstchef.operations.encryption;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.util.Iterator;

import javax.swing.JComboBox;

import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPSecretKey;
import org.bouncycastle.openpgp.PGPSecretKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPBESecretKeyDecryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPGPDigestCalculatorProvider;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyDataDecryptorFactory;
import org.bouncycastle.util.encoders.Base64;
import org.bouncycastle.util.encoders.Hex;

import burp.api.montoya.core.ByteArray;
import de.usd.cstchef.operations.Operation;
import de.usd.cstchef.operations.Operation.OperationInfos;
import de.usd.cstchef.operations.OperationCategory;
import de.usd.cstchef.view.ui.FormatTextField;
import de.usd.cstchef.view.ui.VariableTextArea;

@OperationInfos(name = "PGP Decrypt", category = OperationCategory.ENCRYPTION, description = "Decrypt a PGP message with an OpenPGP private key.")
public class PgpDecryption extends Operation {

private static String[] outModes = new String[] { "Raw", "Hex", "Base64" };

private VariableTextArea privateKeyTextArea;
private FormatTextField passphraseField;
private JComboBox<String> outputMode;

@Override
public void createUI() {
this.privateKeyTextArea = new VariableTextArea();
this.addUIElement("Private Key", this.privateKeyTextArea);

this.passphraseField = new FormatTextField();
this.addUIElement("Passphrase", this.passphraseField);

this.outputMode = new JComboBox<>(outModes);
this.addUIElement("Output", this.outputMode);
}

protected String getPrivateKey() {
return this.privateKeyTextArea.getText();
}

protected String getPassphrase() throws Exception {
return this.passphraseField.getText().toString();
}

protected String getOutputMode() {
return (String) this.outputMode.getSelectedItem();
}

@Override
protected ByteArray perform(ByteArray input) throws Exception {
String privateKey = getPrivateKey();
if (privateKey == null || privateKey.trim().isEmpty()) {
throw new IllegalArgumentException("No private key available.");
}

byte[] decrypted = decrypt(input.getBytes(), privateKey, getPassphrase());

String mode = getOutputMode();
if ("Hex".equals(mode)) {
decrypted = Hex.encode(decrypted);
} else if ("Base64".equals(mode)) {
decrypted = Base64.encode(decrypted);
}

return factory.createByteArray(decrypted);
}

/**
* Decrypts an armored (or binary) PGP message {@code message} using {@code armoredPrivateKey}
* (an ASCII-armored OpenPGP private key) unlocked with {@code passphrase}. Returns the raw
* decrypted bytes; integrity (MDC) is verified when present. Uses BouncyCastle's lightweight
* operators so it does not depend on a signed JCE provider.
*/
protected byte[] decrypt(byte[] message, String armoredPrivateKey, String passphrase) throws Exception {
PGPSecretKeyRingCollection secretKeys = new PGPSecretKeyRingCollection(
PGPUtil.getDecoderStream(new ByteArrayInputStream(armoredPrivateKey.getBytes())),
new BcKeyFingerprintCalculator());

InputStream in = PGPUtil.getDecoderStream(new ByteArrayInputStream(message));
JcaPGPObjectFactory pgpFact = new JcaPGPObjectFactory(in);

Object firstObject = pgpFact.nextObject();
PGPEncryptedDataList encList;
if (firstObject instanceof PGPEncryptedDataList) {
encList = (PGPEncryptedDataList) firstObject;
} else {
encList = (PGPEncryptedDataList) pgpFact.nextObject();
}

PGPPrivateKey privateKey = null;
PGPPublicKeyEncryptedData encryptedData = null;
Iterator<PGPEncryptedData> it = encList.getEncryptedDataObjects();
while (it.hasNext()) {
PGPEncryptedData data = it.next();
if (data instanceof PGPPublicKeyEncryptedData) {
PGPPublicKeyEncryptedData pubKeyData = (PGPPublicKeyEncryptedData) data;
PGPSecretKey secretKey = secretKeys.getSecretKey(pubKeyData.getKeyID());
if (secretKey != null) {
privateKey = secretKey.extractPrivateKey(new BcPBESecretKeyDecryptorBuilder(
new BcPGPDigestCalculatorProvider()).build(passphrase.toCharArray()));
encryptedData = pubKeyData;
break;
}
}
}

if (privateKey == null) {
throw new IllegalArgumentException("No matching private key found for the encrypted message.");
}

InputStream clear = encryptedData.getDataStream(new BcPublicKeyDataDecryptorFactory(privateKey));

JcaPGPObjectFactory plainFact = new JcaPGPObjectFactory(clear);
Object messageObject = plainFact.nextObject();

if (messageObject instanceof PGPCompressedData) {
PGPCompressedData compressed = (PGPCompressedData) messageObject;
plainFact = new JcaPGPObjectFactory(compressed.getDataStream());
messageObject = plainFact.nextObject();
}

if (!(messageObject instanceof PGPLiteralData)) {
throw new PGPException("Unexpected PGP packet; message does not contain literal data.");
}

PGPLiteralData literalData = (PGPLiteralData) messageObject;
ByteArrayOutputStream out = new ByteArrayOutputStream();
InputStream literalIn = literalData.getInputStream();
byte[] buffer = new byte[4096];
int len;
while ((len = literalIn.read(buffer)) > 0) {
out.write(buffer, 0, len);
}

if (encryptedData.isIntegrityProtected() && !encryptedData.verify()) {
throw new PGPException("Message failed integrity check.");
}

return out.toByteArray();
}
}
108 changes: 108 additions & 0 deletions src/main/java/de/usd/cstchef/operations/encryption/PgpEncryption.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
package de.usd.cstchef.operations.encryption;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.OutputStream;
import java.security.SecureRandom;
import java.util.Date;
import java.util.Iterator;

import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.operator.bc.BcKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.bc.BcPGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.bc.BcPublicKeyKeyEncryptionMethodGenerator;

import burp.api.montoya.core.ByteArray;
import de.usd.cstchef.operations.Operation;
import de.usd.cstchef.operations.Operation.OperationInfos;
import de.usd.cstchef.operations.OperationCategory;
import de.usd.cstchef.view.ui.VariableTextArea;

@OperationInfos(name = "PGP Encrypt", category = OperationCategory.ENCRYPTION, description = "Encrypt input with an OpenPGP public key (armored output).")
public class PgpEncryption extends Operation {

private VariableTextArea publicKeyTextArea;

@Override
public void createUI() {
this.publicKeyTextArea = new VariableTextArea();
this.addUIElement("Public Key", this.publicKeyTextArea);
}

protected String getPublicKey() {
return this.publicKeyTextArea.getText();
}

@Override
protected ByteArray perform(ByteArray input) throws Exception {
String publicKey = getPublicKey();
if (publicKey == null || publicKey.trim().isEmpty()) {
throw new IllegalArgumentException("No public key available.");
}
return factory.createByteArray(encrypt(input.getBytes(), publicKey));
}

/**
* Encrypts {@code data} to {@code armoredPublicKey} (an ASCII-armored OpenPGP public key)
* using an AES-256 session key with integrity protection (MDC) and returns an armored
* PGP message block. Uses BouncyCastle's lightweight operators so it does not depend on a
* signed JCE provider (the assembled extension jar is unsigned).
*/
protected byte[] encrypt(byte[] data, String armoredPublicKey) throws Exception {
PGPPublicKey encryptionKey = readPublicKey(armoredPublicKey);

ByteArrayOutputStream out = new ByteArrayOutputStream();
ArmoredOutputStream armoredOut = new ArmoredOutputStream(out);

PGPEncryptedDataGenerator encGen = new PGPEncryptedDataGenerator(
new BcPGPDataEncryptorBuilder(SymmetricKeyAlgorithmTags.AES_256)
.setWithIntegrityPacket(true)
.setSecureRandom(new SecureRandom()));
encGen.addMethod(new BcPublicKeyKeyEncryptionMethodGenerator(encryptionKey));

OutputStream encOut = encGen.open(armoredOut, new byte[4096]);

PGPCompressedDataGenerator compGen = new PGPCompressedDataGenerator(CompressionAlgorithmTags.ZIP);
OutputStream compOut = compGen.open(encOut);

PGPLiteralDataGenerator litGen = new PGPLiteralDataGenerator();
OutputStream litOut = litGen.open(compOut, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE, data.length, new Date());
litOut.write(data);
litGen.close();

compGen.close();
encGen.close();
armoredOut.close();

return out.toByteArray();
}

private PGPPublicKey readPublicKey(String armoredPublicKey) throws Exception {
PGPPublicKeyRingCollection pgpPub = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(new ByteArrayInputStream(armoredPublicKey.getBytes())),
new BcKeyFingerprintCalculator());

Iterator<PGPPublicKeyRing> ringIt = pgpPub.getKeyRings();
while (ringIt.hasNext()) {
PGPPublicKeyRing ring = ringIt.next();
Iterator<PGPPublicKey> keyIt = ring.getPublicKeys();
while (keyIt.hasNext()) {
PGPPublicKey key = keyIt.next();
if (key.isEncryptionKey()) {
return key;
}
}
}
throw new IllegalArgumentException("No encryption key found in the provided public key.");
}
}
Loading