PGP, OpenPGP, and GPG: Secure Communication Protocols
Understanding PGP
PGP (Pretty Good Privacy) is an encryption communication protocol designed to secure email communications and file transfers. It ensures data confidentiality, integrity, and authenticity through encryption, digital signatures, and compression techniques.
Originally developed by Phil Zimmermann, PGP utilizes a public-key encryption mechanism where information is encrypted with the recipient's public key and can only be decrypted with the corresponding private key. This ensures that only the intended recipient can access the message content.
While PGP was initially developed by Zimmermann, its roots trace back to work by Nick Szabo and Eric Hughes at MIT. The protocol has evolved over time, with more advanced standards like OpenPGP and GPG now being widely adopted.
PGP's primary applications extend beyond email to include file and data encryption. Its core features include:
- Encryption and Decryption: PGP combines symmetric and asymmetric encryption. Messages are encrypted with the recipient's public key, and decrypted with their private key. It also supports digital signatures to ensure data integrity and verify sender identity.
- Key Management: PGP uses key pairs (public and private keys) for encryption and decryption. Public keys are shared, while private keys remain confidential.
- Digital Signatures: Users can sign messages with their private keys, allowing recipients to verify the signature using the sender's public key.
- Trust Model: PGP employs a web-of-trust model where users establish trust through direct key exchanges, trust chains, or trusted third parties.
- Open Standard: PGP is an open standard, allowing anyone to implement and use the protocol without vendor restrictions.
OpenPGP: The Open Standard
OpenPGP is an open standard that defines protocols for encrypting and digitally signing data. It ensures interoperability between different encryption software, enabling users of various OpenPGP implementations to securely exchange encrypted information.
The OpenPGP standard is defined in RFC 4880, which specifies methods for generating, exchanging, and verifying public and private keys, as well as algorithms for encryption and signing.
GPG: The Popular Implementation
GPG (GNU Privacy Guard) is a widely-used implementation of the OpenPGP standard. Part of the GNU project and released under the GNU General Public License (GPL), GPG operates as a command-line tool across multiple operating systems including Linux, macOS, and Windows.
GPG provides functionality for creating and verifying digital signatures, encrypting files and emails, and securely exchanging keys. Its core components include:
- Keyring: Stores public and private keys.
- gpg: Command-line tool for encryption, decryption, signing, and verification operations.
- gpgconf: Configuration tool for GPG settings.
- gpg-agent: Daemon process for key management, encryption, and server functions.
Common use cases for GPG include:
- Secure exchange of emails and files.
- Verification of software integrity and origin.
- Protection of personal privacy and business confidential information.
How PGP Works
PGP operates through several key processes:
Key Generation Users generate a pair of cryptographic keys: a public key for encryption and a private key for decryption. The public key is shared with others, while the private key remains confidential.
Encryption The sender uses the recipient's public key to encrypt the message. Only the recipient, with their corresponding private key, can decrypt the message.
Digital Signing The sender uses their private key to create a digital signature of the message. The recipient uses the sender's public key to verify the signature, ensuring message integrity and sender authenticity.
Key Management Users manage and share public keys through key servers or direct exchange methods.
PGP Workflow
Key Exchange Parties exchange public keys through secure channels such as key servers, direct exchange, or other secure methods.
Message Encryption
- The sender selects the message to be sent.
- The sender encrypts the message using the recipient's public key.
- Optionally, the sender may use symetric encryption for the message content, then encrypt the symmetric key with the recipient's public key for efficiency.
- The encrypted message is sent to the recipient.
Message Decryption
- The recipient uses their private key to decrypt the received message.
- If the message includes a digital signature, the recipient verifies it using the sender's public key.
Digital Signature Verification
- The recipient uses the sender's public key to verify the digital signature, ensuring message integrity and sender authenticity.
Trust Management Users establish trust relationships to ensure the authenticity of other users' public keys. Trust can be established through direct key exchanges, trust chains, or trusted third parties.
Use Cases for PGP
PGP has three primary applications:
- Sending and receiving encrypted emails.
- Verifying the identity of message senders.
- Encrypting files for secure storage or transmission.
Case Study: Secure Communication Between Alice and Bob
Let's consider Alice and Bob, two users who want to communicate securely via email while protecting message confidentiality and integrity.
Process
Key Generation
- Alice and Bob each generate a pair of public and private keys.
Key Exchange
- Alice shares her public key with Bob, and Bob shares his public key with Alice through secure channels.
Message Encryption
- Alice decides to send an encrypted email to Bob.
- Alice encrypts the email content using Bob's public key.
- Alice may use symmetric encryption for the content, then encrypt the symmetric key with Bob's public key for efficiency.
Message Decryption
- Bob receives Alice's encrypted email and uses his private key to decrypt the content.
- If the email includes a digital signature, Bob verifies it using Alice's public key to ensure integrity and authenticity.
Digital Signature Verification
- If Alice included a digital signature, Bob uses her public key to verify it, ensuring the email's integrity and Alice's identity.
Trust Management
- Alice and Bob establish trust relationships through direct key exchange or trusted servers to ensure each other's public keys are authentic.
Through this process, Alice and Bob can securely exchange information, protected from unauthorized access or tampering by third parties.
Implementation in Java
Implementing the full PGP protocol in Java requires third-party libraries due to its complexity. The Bouncy Castle library is a popular choice for cryptographic operations in Java.
Dependencies For Maven-based projects, the following dependencies are required:
<dependencies>
<!-- JUnit for testing -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.13.2</version>
<scope>test</scope>
</dependency>
<!-- Bouncy Castle cryptographic provider -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpg-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>
<!-- Bouncy Castle PKIX extensions -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcpkix-jdk15on</artifactId>
<version>1.70</version>
<scope>compile</scope>
</dependency>
<!-- Lombok for reducing boilerplate code -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<!-- Commons IO for utility methods -->
<dependency>
<groupId>commons-io</groupId>
<artifactId>commons-io</artifactId>
<version>2.11.0</version>
</dependency>
</dependencies>
Encryption Utility
package com.security.pgp;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Getter;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.ArmoredOutputStream;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedDataGenerator;
import org.bouncycastle.openpgp.PGPEncryptedDataGenerator;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.operator.jcajce.JcePGPDataEncryptorBuilder;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyKeyEncryptionMethodGenerator;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Objects;
/**
* Utility class for PGP encryption operations
*/
@Getter
@Builder
@AllArgsConstructor
public class CryptoEncryptor {
/**
* Add Bouncy Castle provider to JVM
*/
static {
if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) {
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* Compression algorithm, default is ZIP
*/
@Builder.Default
private int compressionAlgorithm = CompressionAlgorithmTags.ZIP;
/**
* Symmetric key algorithm, default is AES-128
*/
@Builder.Default
private int symmetricKeyAlgorithm = SymmetricKeyAlgorithmTags.AES_128;
/**
* Whether to enable ASCII Armor, default is true
*/
@Builder.Default
private boolean armor = true;
/**
* Whether to enable integrity check, default is true
*/
@Builder.Default
private boolean withIntegrityCheck = true;
/**
* Buffer size, default is 65536 bytes
*/
@Builder.Default
private int bufferSize = 1 << 16;
/**
* Encrypt method that encrypts plaintext data using a public key
*
* @param encryptOut Output stream for encrypted data
* @param clearIn Input stream containing plaintext data
* @param length Length of the data to encrypt
* @param publicKeyIn Input stream containing the public key
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
public void encrypt(OutputStream encryptOut, InputStream clearIn, long length, InputStream publicKeyIn)
throws IOException, PGPException {
// Create compressed data generator
PGPCompressedDataGenerator compressedDataGenerator =
new PGPCompressedDataGenerator(compressionAlgorithm);
// Create PGP encrypted data generator
PGPEncryptedDataGenerator pgpEncryptedDataGenerator = new PGPEncryptedDataGenerator(
new JcePGPDataEncryptorBuilder(symmetricKeyAlgorithm)
.setWithIntegrityPacket(withIntegrityCheck)
.setSecureRandom(new SecureRandom())
.setProvider(BouncyCastleProvider.PROVIDER_NAME)
);
// Add public key
pgpEncryptedDataGenerator.addMethod(new JcePublicKeyKeyEncryptionMethodGenerator(
CryptoHelper.getPublicKey(publicKeyIn)));
// Create armored output if enabled
if (armor) {
encryptOut = new ArmoredOutputStream(encryptOut);
}
// Open encrypted output stream
OutputStream cipherOutStream = pgpEncryptedDataGenerator.open(encryptOut, new byte[bufferSize]);
// Copy plaintext data to encrypted output stream
CryptoHelper.copyAsLiteralData(compressedDataGenerator.open(cipherOutStream), clearIn, length, bufferSize);
// Close all streams
compressedDataGenerator.close();
cipherOutStream.close();
encryptOut.close();
}
/**
* Encrypt method that returns encrypted byte array
*
* @param clearData Plaintext data to encrypt
* @param publicKeyIn Input stream containing the public key
* @return Encrypted data as byte array
* @throws PGPException If a PGP-related error occurs
* @throws IOException If an I/O error occurs
*/
public byte[] encrypt(byte[] clearData, InputStream publicKeyIn) throws PGPException, IOException {
ByteArrayInputStream inputStream = new ByteArrayInputStream(clearData);
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
encrypt(outputStream, inputStream, clearData.length, publicKeyIn);
return outputStream.toByteArray();
}
/**
* Encrypt method that returns encrypted input stream
*
* @param clearIn Input stream containing plaintext data
* @param length Length of the data to encrypt
* @param publicKeyIn Input stream containing the public key
* @return Input stream containing encrypted data
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
public InputStream encrypt(InputStream clearIn, long length, InputStream publicKeyIn)
throws IOException, PGPException {
File tempFile = File.createTempFile("crypto-", "-encrypted");
encrypt(Files.newOutputStream(tempFile.toPath()), clearIn, length, publicKeyIn);
return Files.newInputStream(tempFile.toPath());
}
/**
* Encrypt method that uses public key string
*
* @param clearData Plaintext data to encrypt
* @param publicKeyStr String containing the public key
* @return Encrypted data as byte array
* @throws PGPException If a PGP-related error occurs
* @throws IOException If an I/O error occurs
*/
public byte[] encrypt(byte[] clearData, String publicKeyStr) throws PGPException, IOException {
return encrypt(clearData, IOUtils.toInputStream(publicKeyStr, Charset.defaultCharset()));
}
/**
* Encrypt method that returns encrypted input stream using public key string
*
* @param clearIn Input stream containing plaintext data
* @param length Length of the data to encrypt
* @param publicKeyStr String containing the public key
* @return Input stream containing encrypted data
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
public InputStream encrypt(InputStream clearIn, long length, String publicKeyStr) throws IOException, PGPException {
return encrypt(clearIn, length, IOUtils.toInputStream(publicKeyStr, Charset.defaultCharset()));
}
}
Decryption Utility
package com.security.pgp;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPEncryptedData;
import org.bouncycastle.openpgp.PGPEncryptedDataList;
import org.bouncycastle.openpgp.PGPException;
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.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePBESecretKeyDecryptorBuilder;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.nio.charset.Charset;
import java.security.Security;
import java.util.Iterator;
import java.util.Objects;
/**
* Utility class for PGP decryption operations
*/
public class CryptoDecryptor {
static {
// Add Bouncy Castle provider to JVM
if (Objects.isNull(Security.getProvider(BouncyCastleProvider.PROVIDER_NAME))) {
Security.addProvider(new BouncyCastleProvider());
}
}
/**
* Private key passphrase
*/
private final char[] passCode;
/**
* Secret key ring collection
*/
private final PGPSecretKeyRingCollection secretKeyRingCollection;
/**
* Constructor that initializes from private key input stream
*
* @param privateKeyIn Input stream containing the private key
* @param passCode Passphrase for the private key
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
public CryptoDecryptor(InputStream privateKeyIn, String passCode) throws IOException, PGPException {
this.passCode = passCode.toCharArray();
// Read PGP secret key ring collection from input stream
this.secretKeyRingCollection = new PGPSecretKeyRingCollection(PGPUtil.getDecoderStream(privateKeyIn)
, new JcaKeyFingerprintCalculator());
}
/**
* Constructor that initializes from private key string
*
* @param privateKeyStr String containing the private key
* @param passCode Passphrase for the private key
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
public CryptoDecryptor(String privateKeyStr, String passCode) throws IOException, PGPException {
this(IOUtils.toInputStream(privateKeyStr, Charset.defaultCharset()), passCode);
}
/**
* Find private key by key ID
*
* @param keyID The key ID to search for
* @return The corresponding private key
* @throws PGPException If a PGP-related error occurs
*/
private PGPPrivateKey findSecretKey(long keyID) throws PGPException {
// Get secret key from key ring
PGPSecretKey secretKey = secretKeyRingCollection.getSecretKey(keyID);
// Decrypt private key using passphrase
return secretKey == null ? null : secretKey.extractPrivateKey(new JcePBESecretKeyDecryptorBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(passCode));
}
/**
* Decrypt method that decrypts encrypted input stream to plaintext output stream
*
* @param encryptedIn Input stream containing encrypted data
* @param clearOut Output stream for plaintext data
* @throws PGPException If a PGP-related error occurs
* @throws IOException If an I/O error occurs
*/
public void decrypt(InputStream encryptedIn, OutputStream clearOut)
throws PGPException, IOException {
// Remove ASCII armor and return underlying binary encrypted stream
encryptedIn = PGPUtil.getDecoderStream(encryptedIn);
JcaPGPObjectFactory pgpObjectFactory = new JcaPGPObjectFactory(encryptedIn);
Object obj = pgpObjectFactory.nextObject();
// First object might be marker packet
PGPEncryptedDataList encryptedDataList = (obj instanceof PGPEncryptedDataList)
? (PGPEncryptedDataList) obj : (PGPEncryptedDataList) pgpObjectFactory.nextObject();
PGPPrivateKey privateKey = null;
PGPPublicKeyEncryptedData publicKeyEncryptedData = null;
Iterator<PGPEncryptedData> encryptedDataItr = encryptedDataList.getEncryptedDataObjects();
while (privateKey == null && encryptedDataItr.hasNext()) {
publicKeyEncryptedData = (PGPPublicKeyEncryptedData) encryptedDataItr.next();
privateKey = findSecretKey(publicKeyEncryptedData.getKeyID());
}
if (Objects.isNull(publicKeyEncryptedData)) {
throw new PGPException("Unable to create PGPPublicKeyEncryptedData object");
}
if (privateKey == null) {
throw new PGPException("Unable to extract private key");
}
CryptoHelper.decrypt(clearOut, privateKey, publicKeyEncryptedData);
}
/**
* Decrypt method that decrypts encrypted byte array to plaintext byte array
*
* @param encryptedBytes Encrypted data as byte array
* @return Decrypted data as byte array
* @throws PGPException If a PGP-related error occurs
* @throws IOException If an I/O error occurs
*/
public byte[] decrypt(byte[] encryptedBytes) throws PGPException, IOException {
ByteArrayInputStream encryptedIn = new ByteArrayInputStream(encryptedBytes);
ByteArrayOutputStream clearOut = new ByteArrayOutputStream();
// Call decrypt method
decrypt(encryptedIn, clearOut);
// Return decrypted byte array
return clearOut.toByteArray();
}
}
Crypto Helper Class
package com.security.pgp;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.openpgp.PGPCompressedData;
import org.bouncycastle.openpgp.PGPException;
import org.bouncycastle.openpgp.PGPLiteralData;
import org.bouncycastle.openpgp.PGPLiteralDataGenerator;
import org.bouncycastle.openpgp.PGPOnePassSignatureList;
import org.bouncycastle.openpgp.PGPPrivateKey;
import org.bouncycastle.openpgp.PGPPublicKey;
import org.bouncycastle.openpgp.PGPPublicKeyEncryptedData;
import org.bouncycastle.openpgp.PGPPublicKeyRing;
import org.bouncycastle.openpgp.PGPPublicKeyRingCollection;
import org.bouncycastle.openpgp.PGPUtil;
import org.bouncycastle.openpgp.jcajce.JcaPGPObjectFactory;
import org.bouncycastle.openpgp.operator.PublicKeyDataDecryptorFactory;
import org.bouncycastle.openpgp.operator.jcajce.JcaKeyFingerprintCalculator;
import org.bouncycastle.openpgp.operator.jcajce.JcePublicKeyDataDecryptorFactoryBuilder;
import java.io.BufferedInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.sql.Date;
import java.time.LocalDateTime;
import java.time.ZoneOffset;
import java.util.Arrays;
import java.util.Iterator;
import java.util.Optional;
/**
* Helper class for common PGP operations
*/
public class CryptoHelper {
/**
* Decrypt public key encrypted data using provided private key and write to output stream
*
* @param clearOut Output stream to write decrypted data to
* @param privateKey Private key instance
* @param publicKeyEncryptedData Public key encrypted data instance
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
static void decrypt(OutputStream clearOut, PGPPrivateKey privateKey, PGPPublicKeyEncryptedData publicKeyEncryptedData) throws IOException, PGPException {
PublicKeyDataDecryptorFactory decryptorFactory = new JcePublicKeyDataDecryptorFactoryBuilder()
.setProvider(BouncyCastleProvider.PROVIDER_NAME).build(privateKey);
InputStream decryptedCompressedIn = publicKeyEncryptedData.getDataStream(decryptorFactory);
JcaPGPObjectFactory decCompObjFac = new JcaPGPObjectFactory(decryptedCompressedIn);
PGPCompressedData pgpCompressedData = (PGPCompressedData) decCompObjFac.nextObject();
InputStream compressedDataStream = new BufferedInputStream(pgpCompressedData.getDataStream());
JcaPGPObjectFactory pgpCompObjFac = new JcaPGPObjectFactory(compressedDataStream);
Object message = pgpCompObjFac.nextObject();
if (message instanceof PGPLiteralData) {
PGPLiteralData pgpLiteralData = (PGPLiteralData) message;
InputStream decDataStream = pgpLiteralData.getInputStream();
IOUtils.copy(decDataStream, clearOut);
clearOut.close();
} else if (message instanceof PGPOnePassSignatureList) {
throw new PGPException("Encrypted message contains signed message rather than literal data");
} else {
throw new PGPException("Message is not a simple encrypted file - unknown type");
}
// Perform integrity check
if (publicKeyEncryptedData.isIntegrityProtected()) {
if (!publicKeyEncryptedData.verify()) {
throw new PGPException("Message failed integrity check");
}
}
}
/**
* Copy data from input stream to PGP literal data and write to provided output stream
*
* @param outputStream Output stream to write data to
* @param in Input stream to read data from
* @param Length of data to read
* @param bufferSize Buffer size for efficient copying
* @throws IOException If an I/O error occurs
*/
static void copyAsLiteralData(OutputStream outputStream, InputStream in, long length, int bufferSize) throws IOException {
PGPLiteralDataGenerator lData = new PGPLiteralDataGenerator();
OutputStream pOut = lData.open(outputStream, PGPLiteralData.BINARY, PGPLiteralData.CONSOLE,
Date.from(LocalDateTime.now().toInstant(ZoneOffset.UTC)), new byte[bufferSize]);
byte[] buff = new byte[bufferSize];
try {
int len;
long totalBytesWritten = 0L;
while (totalBytesWritten <= length && (len = in.read(buff)) > 0) {
pOut.write(buff, 0, len);
totalBytesWritten += len;
}
pOut.close();
} finally {
// Clear buffer
Arrays.fill(buff, (byte) 0);
// Close input stream
in.close();
}
}
/**
* Get public key from key input stream
*
* @param keyInputStream Key input stream
* @return PGPPublicKey instance
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
static PGPPublicKey getPublicKey(InputStream keyInputStream) throws IOException, PGPException {
PGPPublicKeyRingCollection publicKeyRings = new PGPPublicKeyRingCollection(
PGPUtil.getDecoderStream(keyInputStream), new JcaKeyFingerprintCalculator());
Iterator<PGPPublicKeyRing> keyRingIterator = publicKeyRings.getKeyRings();
while (keyRingIterator.hasNext()) {
PGPPublicKeyRing publicKeyRing = keyRingIterator.next();
Optional<PGPPublicKey> publicKey = extractPublicKeyFromRing(publicKeyRing);
if (publicKey.isPresent()) {
return publicKey.get();
}
}
throw new PGPException("Invalid public key");
}
private static Optional<PGPPublicKey> extractPublicKeyFromRing(PGPPublicKeyRing publicKeyRing) {
for (PGPPublicKey key : publicKeyRing) {
if (key.isEncryptionKey()) {
return Optional.of(key);
}
}
return Optional.empty();
}
}
Test Class
package com.security.pgp;
import org.apache.commons.io.IOUtils;
import org.bouncycastle.bcpg.CompressionAlgorithmTags;
import org.bouncycastle.bcpg.SymmetricKeyAlgorithmTags;
import org.bouncycastle.openpgp.PGPException;
import org.junit.AfterClass;
import org.junit.Before;
import org.junit.BeforeClass;
import org.junit.Test;
import org.junit.rules.TemporaryFolder;
import java.io.File;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URISyntaxException;
import java.net.URL;
import java.nio.charset.Charset;
import java.nio.file.Files;
import java.util.Optional;
import static org.junit.Assert.assertEquals;
/**
* Test class for PGP encryption and decryption utilities
*/
public class CryptoTest {
public static final TemporaryFolder tempFolder = new TemporaryFolder();
private CryptoEncryptor encryptor = null;
private CryptoDecryptor decryptor = null;
/**
* Helper method to load resource files
* @param resourcePath Path to the resource
* @return URL of the resource
*/
private static URL loadResource(String resourcePath) {
return Optional.ofNullable(CryptoTest.class.getResource(resourcePath))
.orElseThrow(() -> new IllegalArgumentException("Resource not found"));
}
private static final String passphrase = "testpass";
private final URL privateKey = loadResource("/test-private.asc");
private final URL publicKey = loadResource("/test-public.asc");
private final URL testFile = loadResource("/test-data.csv");
private static final String testString = "This text needs to be encrypted with PGP";
/**
* Create temporary folder before test class runs
* @throws IOException If an I/O error occurs
*/
@BeforeClass
public static void setup() throws IOException {
tempFolder.delete();
tempFolder.create();
}
/**
* Clean up temporary folder after test class runs
*/
@AfterClass
public static void cleanup() {
tempFolder.delete();
}
/**
* Initialize method that runs before each test method
*/
@Before
public void init() {
// Initialize encryption utility
encryptor = CryptoEncryptor.builder()
.armor(true)
.compressionAlgorithm(CompressionAlgorithmTags.ZIP)
.symmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.AES_128)
.withIntegrityCheck(true)
.build();
try {
// Initialize decryption utility
decryptor = new CryptoDecryptor(privateKey.openStream(), passphrase);
} catch (IOException | PGPException e) {
throw new RuntimeException(e);
}
}
/**
* Test byte array encryption
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
@Test
public void testByteEncryption() throws IOException, PGPException {
// Encrypt test byte array
byte[] encryptedBytes = encryptor.encrypt(testString.getBytes(Charset.defaultCharset()),
publicKey.openStream());
// Decrypt the encrypted byte array
byte[] decryptedBytes = decryptor.decrypt(encryptedBytes);
// Convert decrypted byte array to string and compare with original test string
assertEquals(testString, new String(decryptedBytes, Charset.defaultCharset()));
}
/**
* Test file encryption
* @throws IOException If an I/O error occurs
* @throws URISyntaxException If URI syntax is invalid
* @throws PGPException If a PGP-related error occurs
*/
@Test
public void testFileEncryption() throws IOException, URISyntaxException, PGPException {
// Create a PGP encrypted temporary file from the test file
File encryptedFile = tempFolder.newFile();
File originalFile = new File(testFile.toURI());
try (OutputStream fos = Files.newOutputStream(encryptedFile.toPath())) {
encryptor.encrypt(fos, Files.newInputStream(originalFile.toPath()), originalFile.length(),
publicKey.openStream());
}
// Decrypt the generated PGP encrypted temporary file and write to another temporary file
File decryptedFile = tempFolder.newFile();
decryptor.decrypt(Files.newInputStream(encryptedFile.toPath()), Files.newOutputStream(decryptedFile.toPath()));
// Compare original file content with decrypted file content
assertEquals(IOUtils.toString(Files.newInputStream(originalFile.toPath()), Charset.defaultCharset()),
IOUtils.toString(Files.newInputStream(decryptedFile.toPath()), Charset.defaultCharset()));
}
/**
* Test input stream encryption
* @throws IOException If an I/O error occurs
* @throws URISyntaxException If URI syntax is invalid
* @throws PGPException If a PGP-related error occurs
*/
@Test
public void testInputStreamEncryption() throws IOException, URISyntaxException, PGPException {
// Create a PGP encrypted input stream from the test file
File originalFile = new File(testFile.toURI());
InputStream encryptedIn = encryptor.encrypt(Files.newInputStream(originalFile.toPath()), originalFile.length(), publicKey.openStream());
// Decrypt the generated input stream and write to temporary file
File decryptedFile = tempFolder.newFile();
decryptor.decrypt(encryptedIn, Files.newOutputStream(decryptedFile.toPath()));
// Compare original file content with decrypted file content
assertEquals(IOUtils.toString(Files.newInputStream(originalFile.toPath()), Charset.defaultCharset()),
IOUtils.toString(Files.newInputStream(decryptedFile.toPath()), Charset.defaultCharset()));
}
/**
* Test byte array encryption with new configuration
* @throws IOException If an I/O error occurs
* @throws PGPException If a PGP-related error occurs
*/
@Test
public void testByteEncryptionWithNewConfig() throws IOException, PGPException {
encryptor = CryptoEncryptor.builder()
.armor(false)
.compressionAlgorithm(CompressionAlgorithmTags.BZIP2)
.symmetricKeyAlgorithm(SymmetricKeyAlgorithmTags.BLOWFISH)
.withIntegrityCheck(false)
.build();
// Encrypt test byte array
byte[] encryptedBytes = encryptor.encrypt(testString.getBytes(Charset.defaultCharset()),
publicKey.openStream());
// Decrypt the encrypted byte array
byte[] decryptedBytes = decryptor.decrypt(encryptedBytes);
// Convert decrypted byte array to string and compare with original test string
assertEquals(testString, new String(decryptedBytes, Charset.defaultCharset()));
}
}
Conclusion
When sending emails or files over the internet, we want their content to remain confidential, and we want to verify the sender's identity and ensure data integrity. This is where PGP (Pretty Good Privacy) comes into play.
Think of PGP as having a key with two parts: a public key and a private key.
- Public Key: This is like the key to your mailbox. You can share it with anyone. Others can use it to lock (encrypt) a file, but only you can unlock (decrypt) it with your private key.
- Private Key: This is like the key to your house. Only you have it. You use it to unlock files that others have locked with your public key.
When you want to send a private message to someone, you encrypt it with their public key. Only they can decrypt it with their private key. This ensures that even if the message is intercepted during transmission, it cannot be read by unauthorized parties.
Additionally, PGP can be used for digital signatures. Just like signing a letter, a digital signature proves the sender's identity and message integrity. The sender uses their private key to sign the message, and the recipient uses the sender's public key to verify the signature, ensuring the message hasn't been tampered with and actually comes from the claimed sender.
In summary, PGP is an encryption technology that protects email and file security by using public and private keys to encrypt and decrypt messages, and by using digital signatures to verify message origin and integrity.