mirror of
https://github.com/pgpainless/pgpainless.git
synced 2024-09-27 10:09:33 +02:00
289 lines
15 KiB
Java
289 lines
15 KiB
Java
// SPDX-FileCopyrightText: 2021 Paul Schaub <vanitasvitae@fsfe.org>
|
|
//
|
|
// SPDX-License-Identifier: Apache-2.0
|
|
|
|
package org.pgpainless.example;
|
|
|
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
|
|
|
import java.io.ByteArrayInputStream;
|
|
import java.io.ByteArrayOutputStream;
|
|
import java.io.IOException;
|
|
import java.nio.charset.StandardCharsets;
|
|
|
|
import org.bouncycastle.openpgp.PGPException;
|
|
import org.bouncycastle.openpgp.PGPPublicKeyRing;
|
|
import org.bouncycastle.openpgp.PGPSecretKeyRing;
|
|
import org.bouncycastle.util.io.Streams;
|
|
import org.junit.jupiter.api.Test;
|
|
import org.pgpainless.PGPainless;
|
|
import org.pgpainless.algorithm.DocumentSignatureType;
|
|
import org.pgpainless.decryption_verification.ConsumerOptions;
|
|
import org.pgpainless.decryption_verification.DecryptionStream;
|
|
import org.pgpainless.decryption_verification.MessageMetadata;
|
|
import org.pgpainless.encryption_signing.EncryptionOptions;
|
|
import org.pgpainless.encryption_signing.EncryptionStream;
|
|
import org.pgpainless.encryption_signing.ProducerOptions;
|
|
import org.pgpainless.encryption_signing.SigningOptions;
|
|
import org.pgpainless.key.protection.SecretKeyRingProtector;
|
|
import org.pgpainless.util.Passphrase;
|
|
|
|
public class Encrypt {
|
|
|
|
private static final String ALICE_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
|
"Version: PGPainless\n" +
|
|
"Comment: 12E3 4F04 C66D 2B70 D16C 960D ACF2 16F0 F93D DD20\n" +
|
|
"Comment: alice@pgpainless.org\n" +
|
|
"\n" +
|
|
"lFgEYksu1hYJKwYBBAHaRw8BAQdAIhUpRrs6zFTBI1pK40jCkzY/DQ/t4fUgNtlS\n" +
|
|
"mXOt1cIAAP4wM0LQD/Wj9w6/QujM/erj/TodDZzmp2ZwblrvDQri0RJ/tBRhbGlj\n" +
|
|
"ZUBwZ3BhaW5sZXNzLm9yZ4iPBBMWCgBBBQJiSy7WCRCs8hbw+T3dIBYhBBLjTwTG\n" +
|
|
"bStw0WyWDazyFvD5Pd0gAp4BApsBBRYCAwEABAsJCAcFFQoJCAsCmQEAAOOTAQDf\n" +
|
|
"UsRQSAs0d/Nm4YIrq+gU7gOdTJuf33f/u/u1nGM1fAD/RY7I3gQoZ0lWbvXVkRAL\n" +
|
|
"Cu9cUJdvL7kpW1oYtYg21QucXQRiSy7WEgorBgEEAZdVAQUBAQdA60F84k6MY/Uy\n" +
|
|
"BCZe4/WP8JDw/Efu5/Gyk8hcd3HzHFsDAQgHAAD/aC8DOOkK0XNVz2hkSVczmNoJ\n" +
|
|
"Umog0PfQLRujpOTqonAQKIh1BBgWCgAdBQJiSy7WAp4BApsMBRYCAwEABAsJCAcF\n" +
|
|
"FQoJCAsACgkQrPIW8Pk93SCd6AD/Y3LF2RvgbEaOBtAvH6w0ZBPorB3rk6dx+Ae0\n" +
|
|
"GvW4E8wA+QHmgNo0pdkDxTl0BN1KC7BV1iRFqe9Vo7fW2LLfhlEEnFgEYksu1hYJ\n" +
|
|
"KwYBBAHaRw8BAQdAPtqap21/zmVzxOHk++891/EZSNikwWkq9t0pmYjhtJ8AAP9N\n" +
|
|
"m/G6nbiEB8mu/TkNnb7vdhSmLddL9kdKh0LzWD95LBF0iNUEGBYKAH0FAmJLLtYC\n" +
|
|
"ngECmwIFFgIDAQAECwkIBwUVCgkIC18gBBkWCgAGBQJiSy7WAAoJEOEz2Vo79Yyl\n" +
|
|
"zN0A/iZAVklSJsfQslshR6/zMBufwCK1S05jg/5Ydaksv3QcAQC4gsxdFFne+H4M\n" +
|
|
"mos4atad6hMhlqr0/Zyc71ZdO5I/CAAKCRCs8hbw+T3dIGhqAQCIdVtCus336cDe\n" +
|
|
"Nug+E9v1PEM3F/dt6GAqSG8LJqdAGgEA8cUXdUBooOo/QBkDnpteke8Z3IhIGyGe\n" +
|
|
"dc8OwJyVFwc=\n" +
|
|
"=ARAi\n" +
|
|
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
|
private static final String ALICE_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
|
"Version: PGPainless\n" +
|
|
"Comment: 12E3 4F04 C66D 2B70 D16C 960D ACF2 16F0 F93D DD20\n" +
|
|
"Comment: alice@pgpainless.org\n" +
|
|
"\n" +
|
|
"mDMEYksu1hYJKwYBBAHaRw8BAQdAIhUpRrs6zFTBI1pK40jCkzY/DQ/t4fUgNtlS\n" +
|
|
"mXOt1cK0FGFsaWNlQHBncGFpbmxlc3Mub3JniI8EExYKAEEFAmJLLtYJEKzyFvD5\n" +
|
|
"Pd0gFiEEEuNPBMZtK3DRbJYNrPIW8Pk93SACngECmwEFFgIDAQAECwkIBwUVCgkI\n" +
|
|
"CwKZAQAA45MBAN9SxFBICzR382bhgiur6BTuA51Mm5/fd/+7+7WcYzV8AP9Fjsje\n" +
|
|
"BChnSVZu9dWREAsK71xQl28vuSlbWhi1iDbVC7g4BGJLLtYSCisGAQQBl1UBBQEB\n" +
|
|
"B0DrQXziToxj9TIEJl7j9Y/wkPD8R+7n8bKTyFx3cfMcWwMBCAeIdQQYFgoAHQUC\n" +
|
|
"Yksu1gKeAQKbDAUWAgMBAAQLCQgHBRUKCQgLAAoJEKzyFvD5Pd0gnegA/2Nyxdkb\n" +
|
|
"4GxGjgbQLx+sNGQT6Kwd65OncfgHtBr1uBPMAPkB5oDaNKXZA8U5dATdSguwVdYk\n" +
|
|
"RanvVaO31tiy34ZRBLgzBGJLLtYWCSsGAQQB2kcPAQEHQD7amqdtf85lc8Th5Pvv\n" +
|
|
"PdfxGUjYpMFpKvbdKZmI4bSfiNUEGBYKAH0FAmJLLtYCngECmwIFFgIDAQAECwkI\n" +
|
|
"BwUVCgkIC18gBBkWCgAGBQJiSy7WAAoJEOEz2Vo79YylzN0A/iZAVklSJsfQslsh\n" +
|
|
"R6/zMBufwCK1S05jg/5Ydaksv3QcAQC4gsxdFFne+H4Mmos4atad6hMhlqr0/Zyc\n" +
|
|
"71ZdO5I/CAAKCRCs8hbw+T3dIGhqAQCIdVtCus336cDeNug+E9v1PEM3F/dt6GAq\n" +
|
|
"SG8LJqdAGgEA8cUXdUBooOo/QBkDnpteke8Z3IhIGyGedc8OwJyVFwc=\n" +
|
|
"=GUhm\n" +
|
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
|
|
|
private static final String BOB_KEY = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
|
|
"Version: PGPainless\n" +
|
|
"Comment: A0D2 F316 0F6B 2CE5 7A50 FF32 261E 5081 9736 C493\n" +
|
|
"Comment: bob@pgpainless.org\n" +
|
|
"\n" +
|
|
"lFgEYksu1hYJKwYBBAHaRw8BAQdAXTBT1OKN1GAvGC+fzuy/k34BK+d5Saa87Glb\n" +
|
|
"iQgIxg8AAPwMI5DGqADFfl6H3Nxj3NxEZLasiFDpwEszluLVRy0jihGbtBJib2JA\n" +
|
|
"cGdwYWlubGVzcy5vcmeIjwQTFgoAQQUCYksu1gkQJh5QgZc2xJMWIQSg0vMWD2ss\n" +
|
|
"5XpQ/zImHlCBlzbEkwKeAQKbAQUWAgMBAAQLCQgHBRUKCQgLApkBAADvrAD/cWBW\n" +
|
|
"mRkSfoCbEl22s59FXE7NPENrsJK8jxmWsWX3jbEA/AyXMCjwH6IhDgdgO7wH2z1r\n" +
|
|
"cUb/hokiCcCaJs6hjKcInF0EYksu1hIKKwYBBAGXVQEFAQEHQCeURSBi9brhisUH\n" +
|
|
"Dz0xN1NCgU5yeirx53xrQDFFx+d6AwEIBwAA/1GHX9+4Rg0ePsXGm1QIWL+C4rdf\n" +
|
|
"AReCTYoS3EBiZVdADoyIdQQYFgoAHQUCYksu1gKeAQKbDAUWAgMBAAQLCQgHBRUK\n" +
|
|
"CQgLAAoJECYeUIGXNsST8c0A/1dEIO9gsFB15UWDlTzN3S0TXQNN8wVzIMdW7XP2\n" +
|
|
"7c6bAQCB5ChqQA9AB1020DLr28BAbSjI7mPdIWg2PpE7B1EXC5xYBGJLLtYWCSsG\n" +
|
|
"AQQB2kcPAQEHQKP5NxT0ZhmRbrl3S6uwrUN248g1TEUR0DCVuLgyGSLpAAEA6bMa\n" +
|
|
"GaUf3S55rkFDjFC4Cv72zc8E5ex2RKgbpxXxqhYQN4jVBBgWCgB9BQJiSy7WAp4B\n" +
|
|
"ApsCBRYCAwEABAsJCAcFFQoJCAtfIAQZFgoABgUCYksu1gAKCRDJLjPCA2NIfylD\n" +
|
|
"AP4tNFV23FBlrC57iesHVc+TTfNJ8rd+U7mbJvUgykcSNAEAy64tKPuVj+aA1bpm\n" +
|
|
"gHxfqdEJCOko8UhVVP6ltiDUcAoACgkQJh5QgZc2xJP9TQEA1DNgFno3di+xGDEN\n" +
|
|
"pwe9lmz8d/RWy/kuBT9S/3CMJjQBAKNBhHPuFfvk7RFbsmMrHsSqDFqIuUfGqq39\n" +
|
|
"VzmiMp8N\n" +
|
|
"=LpkJ\n" +
|
|
"-----END PGP PRIVATE KEY BLOCK-----\n";
|
|
private static final String BOB_CERT = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
|
|
"Version: PGPainless\n" +
|
|
"Comment: A0D2 F316 0F6B 2CE5 7A50 FF32 261E 5081 9736 C493\n" +
|
|
"Comment: bob@pgpainless.org\n" +
|
|
"\n" +
|
|
"mDMEYksu1hYJKwYBBAHaRw8BAQdAXTBT1OKN1GAvGC+fzuy/k34BK+d5Saa87Glb\n" +
|
|
"iQgIxg+0EmJvYkBwZ3BhaW5sZXNzLm9yZ4iPBBMWCgBBBQJiSy7WCRAmHlCBlzbE\n" +
|
|
"kxYhBKDS8xYPayzlelD/MiYeUIGXNsSTAp4BApsBBRYCAwEABAsJCAcFFQoJCAsC\n" +
|
|
"mQEAAO+sAP9xYFaZGRJ+gJsSXbazn0VcTs08Q2uwkryPGZaxZfeNsQD8DJcwKPAf\n" +
|
|
"oiEOB2A7vAfbPWtxRv+GiSIJwJomzqGMpwi4OARiSy7WEgorBgEEAZdVAQUBAQdA\n" +
|
|
"J5RFIGL1uuGKxQcPPTE3U0KBTnJ6KvHnfGtAMUXH53oDAQgHiHUEGBYKAB0FAmJL\n" +
|
|
"LtYCngECmwwFFgIDAQAECwkIBwUVCgkICwAKCRAmHlCBlzbEk/HNAP9XRCDvYLBQ\n" +
|
|
"deVFg5U8zd0tE10DTfMFcyDHVu1z9u3OmwEAgeQoakAPQAddNtAy69vAQG0oyO5j\n" +
|
|
"3SFoNj6ROwdRFwu4MwRiSy7WFgkrBgEEAdpHDwEBB0Cj+TcU9GYZkW65d0ursK1D\n" +
|
|
"duPINUxFEdAwlbi4Mhki6YjVBBgWCgB9BQJiSy7WAp4BApsCBRYCAwEABAsJCAcF\n" +
|
|
"FQoJCAtfIAQZFgoABgUCYksu1gAKCRDJLjPCA2NIfylDAP4tNFV23FBlrC57iesH\n" +
|
|
"Vc+TTfNJ8rd+U7mbJvUgykcSNAEAy64tKPuVj+aA1bpmgHxfqdEJCOko8UhVVP6l\n" +
|
|
"tiDUcAoACgkQJh5QgZc2xJP9TQEA1DNgFno3di+xGDENpwe9lmz8d/RWy/kuBT9S\n" +
|
|
"/3CMJjQBAKNBhHPuFfvk7RFbsmMrHsSqDFqIuUfGqq39VzmiMp8N\n" +
|
|
"=1MqZ\n" +
|
|
"-----END PGP PUBLIC KEY BLOCK-----\n";
|
|
|
|
/**
|
|
* In this example, Alice is sending a signed and encrypted message to Bob.
|
|
* She signs the message using her key and then encrypts the message to both bobs certificate and her own.
|
|
*
|
|
* Bob subsequently decrypts the message using his key and verifies that the message was signed by Alice using
|
|
* her certificate.
|
|
*/
|
|
@Test
|
|
public void encryptAndSignMessage() throws PGPException, IOException {
|
|
// Prepare keys
|
|
PGPSecretKeyRing keyAlice = PGPainless.readKeyRing().secretKeyRing(ALICE_KEY);
|
|
PGPPublicKeyRing certificateAlice = PGPainless.readKeyRing().publicKeyRing(ALICE_CERT);
|
|
SecretKeyRingProtector protectorAlice = SecretKeyRingProtector.unprotectedKeys();
|
|
|
|
PGPSecretKeyRing keyBob = PGPainless.readKeyRing().secretKeyRing(BOB_KEY);
|
|
PGPPublicKeyRing certificateBob = PGPainless.readKeyRing().publicKeyRing(BOB_CERT);
|
|
SecretKeyRingProtector protectorBob = SecretKeyRingProtector.unprotectedKeys();
|
|
|
|
// plaintext message to encrypt
|
|
String message = "Hello, World!\n";
|
|
ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
|
|
// Encrypt and sign
|
|
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
|
.onOutputStream(ciphertext)
|
|
.withOptions(ProducerOptions.signAndEncrypt(
|
|
// we want to encrypt communication (affects key selection based on key flags)
|
|
EncryptionOptions.encryptCommunications()
|
|
.addRecipient(certificateBob)
|
|
.addRecipient(certificateAlice),
|
|
new SigningOptions()
|
|
.addInlineSignature(protectorAlice, keyAlice, DocumentSignatureType.CANONICAL_TEXT_DOCUMENT)
|
|
).setAsciiArmor(true)
|
|
);
|
|
|
|
// Pipe data trough and CLOSE the stream (important)
|
|
Streams.pipeAll(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)), encryptor);
|
|
encryptor.close();
|
|
String encryptedMessage = ciphertext.toString();
|
|
|
|
// Decrypt and verify signatures
|
|
DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
|
|
.onInputStream(new ByteArrayInputStream(encryptedMessage.getBytes(StandardCharsets.UTF_8)))
|
|
.withOptions(new ConsumerOptions()
|
|
.addDecryptionKey(keyBob, protectorBob)
|
|
.addVerificationCert(certificateAlice)
|
|
);
|
|
|
|
ByteArrayOutputStream plaintext = new ByteArrayOutputStream();
|
|
|
|
Streams.pipeAll(decryptor, plaintext);
|
|
decryptor.close();
|
|
|
|
// Check the metadata to see how the message was encrypted/signed
|
|
MessageMetadata metadata = decryptor.getMetadata();
|
|
assertTrue(metadata.isEncrypted());
|
|
assertTrue(metadata.isVerifiedSignedBy(certificateAlice));
|
|
assertEquals(message, plaintext.toString());
|
|
}
|
|
|
|
/**
|
|
* This example demonstrates how to encrypt and decrypt a message using a passphrase.
|
|
* This method can be combined with public key based encryption and signing.
|
|
*/
|
|
@Test
|
|
public void encryptUsingPassphrase() throws PGPException, IOException {
|
|
String message = "Hello, World!";
|
|
ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
|
|
// Encrypt
|
|
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
|
.onOutputStream(ciphertext)
|
|
.withOptions(ProducerOptions
|
|
.encrypt(EncryptionOptions.encryptCommunications()
|
|
.addPassphrase(Passphrase.fromPassword("p4ssphr4s3"))
|
|
).setAsciiArmor(true)
|
|
);
|
|
|
|
Streams.pipeAll(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)), encryptor);
|
|
encryptor.close();
|
|
|
|
String asciiCiphertext = ciphertext.toString();
|
|
|
|
// Decrypt
|
|
DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
|
|
.onInputStream(new ByteArrayInputStream(asciiCiphertext.getBytes(StandardCharsets.UTF_8)))
|
|
.withOptions(new ConsumerOptions().addDecryptionPassphrase(Passphrase.fromPassword("p4ssphr4s3")));
|
|
|
|
ByteArrayOutputStream plaintext = new ByteArrayOutputStream();
|
|
Streams.pipeAll(decryptor, plaintext);
|
|
|
|
decryptor.close();
|
|
|
|
assertEquals(message, plaintext.toString());
|
|
}
|
|
|
|
/**
|
|
* In this example, Alice is sending a signed and encrypted message to Bob.
|
|
* She encrypts the message to both bobs certificate and her own.
|
|
* A multiline comment header is added using the fluent ProducerOption syntax.
|
|
*
|
|
* Bob subsequently decrypts the message using his key.
|
|
*/
|
|
@Test
|
|
public void encryptWithCommentHeader() throws PGPException, IOException {
|
|
// Prepare keys
|
|
PGPPublicKeyRing certificateAlice = PGPainless.readKeyRing().publicKeyRing(ALICE_CERT);
|
|
|
|
PGPSecretKeyRing keyBob = PGPainless.readKeyRing().secretKeyRing(BOB_KEY);
|
|
PGPPublicKeyRing certificateBob = PGPainless.readKeyRing().publicKeyRing(BOB_CERT);
|
|
SecretKeyRingProtector protectorBob = SecretKeyRingProtector.unprotectedKeys();
|
|
|
|
// plaintext message to encrypt
|
|
String message = "Hello, World!\n";
|
|
String[] comments = {
|
|
"This comment was added using options.",
|
|
"And it has three lines.",
|
|
" ",
|
|
"Empty lines are skipped."
|
|
};
|
|
String comment = comments[0] + "\n" + comments[1] + "\n" + comments[2] + "\n" + comments[3];
|
|
ByteArrayOutputStream ciphertext = new ByteArrayOutputStream();
|
|
// Encrypt and sign
|
|
EncryptionStream encryptor = PGPainless.encryptAndOrSign()
|
|
.onOutputStream(ciphertext)
|
|
.withOptions(ProducerOptions.encrypt(
|
|
// we want to encrypt communication (affects key selection based on key flags)
|
|
EncryptionOptions.encryptCommunications()
|
|
.addRecipient(certificateBob)
|
|
.addRecipient(certificateAlice)
|
|
).setAsciiArmor(true)
|
|
.setComment(comment)
|
|
);
|
|
|
|
// Pipe data trough and CLOSE the stream (important)
|
|
Streams.pipeAll(new ByteArrayInputStream(message.getBytes(StandardCharsets.UTF_8)), encryptor);
|
|
encryptor.close();
|
|
String encryptedMessage = ciphertext.toString();
|
|
|
|
// check that comment header was added after "BEGIN PGP" and "Version:"
|
|
assertEquals(encryptedMessage.split("\n")[2].trim(), "Comment: " + comments[0]);
|
|
assertEquals(encryptedMessage.split("\n")[3].trim(), "Comment: " + comments[1]);
|
|
assertEquals(encryptedMessage.split("\n")[4].trim(), "Comment: " + comments[3]);
|
|
|
|
// also test, that decryption still works...
|
|
|
|
// Decrypt and verify signatures
|
|
DecryptionStream decryptor = PGPainless.decryptAndOrVerify()
|
|
.onInputStream(new ByteArrayInputStream(encryptedMessage.getBytes(StandardCharsets.UTF_8)))
|
|
.withOptions(new ConsumerOptions()
|
|
.addDecryptionKey(keyBob, protectorBob)
|
|
.addVerificationCert(certificateAlice)
|
|
);
|
|
|
|
ByteArrayOutputStream plaintext = new ByteArrayOutputStream();
|
|
|
|
Streams.pipeAll(decryptor, plaintext);
|
|
decryptor.close();
|
|
|
|
// Check the metadata to see how the message was encrypted/signed
|
|
MessageMetadata metadata = decryptor.getMetadata();
|
|
assertTrue(metadata.isEncrypted());
|
|
assertEquals(message, plaintext.toString());
|
|
}
|
|
}
|