DEV Community

wennan xu
wennan xu

Posted on

How to use pgpainless-core

Document

https://pgpainless.readthedocs.io/en/latest/quickstart.html

Setup

// If you use Gradle
...
dependencies {
    ...
    implementation "org.pgpainless:pgpainless-core:1.7.2"
    ...
}

// If you use Maven
...
<dependencies>
    ...
    <dependency>
        <groupId>org.pgpainless</groupId>
        <artifactId>pgpainless-core</artifactId>
        <version>1.7.2</version>
    </dependency>
    ...
</dependencies>
Enter fullscreen mode Exit fullscreen mode

What's ASCII Armor

ASCII armor is a layer of radix64 encoding that can be used to wrap binary OpenPGP data in order to make it save to transport via text-based channels (e.g. email bodies).

The way in which ASCII armor can be applied depends on the type of data that you want to protect. The easies way to ASCII armor an OpenPGP key or certificate is by using PGPainlessโ€™ asciiArmor() method:

PGPPublicKey certificate = ...;
String asciiArmored = PGPainless.asciiArmor(certificate);
Enter fullscreen mode Exit fullscreen mode

How to Setup the ProducerOptions when Encrypt

  • Suppose you have a FileInputStream or ByteArrayInputStream. Then also you need to have a OutputStream, like
OutputStream outputStream =
                        Files.newOutputStream(outputFileLocation, StandardOpenOption.CREATE_NEW);
Enter fullscreen mode Exit fullscreen mode
  • Before you call the Painless.encryptAndOrSign(), you need to build up the ProducerOptions. It will be like
/** The secret key ring used for decrypting and signing. */
private final PGPSecretKeyRing secretKeyRing = ...;

/** The public key ring used for encrypting. */
private final PGPPublicKeyRing publicKeyRing = ...;

/** Stores the password securely, required to access the secret key. */
private final SecretKeyRingProtector secretKeyRingProtector = ...;

SigningOptions signOptions = SigningOptions.get().addInlineSignature(secretKeyRingProtector, secretKeyRing);
Enter fullscreen mode Exit fullscreen mode
  • when you try to build your secretKeyRing and secretKeyRingProtector, you probably will get it from your properties like
this.secretKeyRing = armorStringKeyringLoader.load(properties.getSecretKey(), PGPSecretKeyRing.class)
Enter fullscreen mode Exit fullscreen mode
this.publicKeyRing = armorStringKeyringLoader.load(properties.getPublicKey(), PGPPublicKeyRing.class)
Enter fullscreen mode Exit fullscreen mode

In order to load the key, all you need to do is to get the BufferedInputStream, then

protected <T extends PGPKeyRing> T readKeyRing(BufferedInputStream keyInputStream, Class<T> clazz) throws IOException, KeyRingLoaderException {
    if (clazz.isAssignableFrom(PGPSecretKeyRing.class)) {
        return clazz.cast(PGPainless.readKeyRing().secretKeyRing(keyInputStream));
    } else if (clazz.isAssignableFrom(PGPPublicKeyRing.class)) {
        return clazz.cast(PGPainless.readKeyRing().publicKeyRing(keyInputStream));
    } throw new KeyRingLoaderException(
        String.format("Unsupported key ring type [class=%s]", clazz.getSimpleName()));
    }
}
Enter fullscreen mode Exit fullscreen mode

Create that BufferedInputStream from string can be like

public BufferedInputStream getBufferedInputStreamForKey(String armorKey) throws Exception {
        // return a new BufferedInputStream with the key as the content.
        ByteSource byteSource = ByteSource.wrap(armorKey.getBytes());
        return new BufferedInputStream(byteSource.openStream());
    }
Enter fullscreen mode Exit fullscreen mode

Or create that BufferedInputStream from file can be like

public BufferedInputStream getBufferedInputStreamForKey(String armorKeyfileLocation) throws IOException {
    // Loads the ascii armor key file from the file system.
    return (BufferedInputStream) Files.asByteSource(new File(armorKeyfileLocation)).openBufferedStream();
}
Enter fullscreen mode Exit fullscreen mode
  • Now lets build the encryptionOptions. You only need to add the recipient's public key as the
PGPPublicKey certificate = ...;
EncryptionOptions encOptions = EncryptionOptions.get().addRecipient(certificate);
Enter fullscreen mode Exit fullscreen mode

Same philosophy to load the PGPPublicKey via PGPainless.readKeyRing().publicKeyRing(keyInputStream).

  • So now we can put both of them into the ProducerOptions options like
ProducerOptions options = ProducerOptions.signAndEncrypt(signingOptions, encryptionOptions);
Enter fullscreen mode Exit fullscreen mode

If you want to ASCII armor ciphertext, you can enable ASCII armoring during encrypting/signing by requesting PGPainless to armor the result:

producerOptions.setAsciiArmor(true); // enable armoring
Enter fullscreen mode Exit fullscreen mode

File Association: When you encrypt data, you might want to include the original file name so that the recipient knows what the encrypted content represents. The setFileName method allows you to set this file name.
Metadata: Including the file name as metadata can help the recipient understand the context of the encrypted data, especially if multiple files are being encrypted and sent together. So you can do like:
producerOptions.setFileName(outputFileLocation.toString()).

Encrypt with the OutputStream

EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
        .onOutputStream(outputStream)
        .withOptions(producerOptions);
Enter fullscreen mode Exit fullscreen mode

Encrypt the file data

If you already have an existing file, you can encrypt it to another file location like

InputStream plaintext = ...; // The data that will be encrypted and/or signed
ByteArrayInputStream contentToEncrypt = ...; // can also be the ByteArrayInputStream 
OutputStream ciphertext = Files.newOutputStream(Path outputFileLocation, StandardOpenOption.CREATE_NEW); // Destination for the ciphertext

EncryptionStream encryptionStream = PGPainless.encryptAndOrSign()
        .onOutputStream(ciphertext)
        .withOptions(producerOptions); // pass in the options object

Streams.pipeAll(plaintext, encryptionStream); // pipe the data through
encryptionStream.close(); // important! Close the stream to finish encryption/signing

EncryptionResult result = encryptionStream.getResult(); // metadata
Enter fullscreen mode Exit fullscreen mode

Use the EncryptionStream in Spring batch OutputStreamWriter.

As EncryptionStream is also a sub-class of OutputStream, so we can wrap it into the Spring batch writer like

new OutputStreamWriter(EncryptionStream, StandardCharsets.UTF_8);
Enter fullscreen mode Exit fullscreen mode

So now we will get a Encrypted output stream, we can write the encrypted data into the output stream in Spring batch.

How to Setup the ConsumerOptions when Decrypt

new ConsumerOptions()
.addDecryptionKey(SecretKeyRing, SecretKeyRingProtector)
.addVerificationCert(PublicKeyRing)
Enter fullscreen mode Exit fullscreen mode

Build the DecryptionStream

// this is the file we are going to decrypt
InputStream inputStream = Files.newInputStream(inputFileLocation, StandardOpenOption.CREATE_NEW);

DecryptionStream decryptionStream = PGPainless.decryptAndOrVerify()
    .onInputStream(inputStream)
    .withOptions(ConsumerOptions)
Enter fullscreen mode Exit fullscreen mode

Reuse this DecryptionStream in Spring batch reader

new InputStreamWriter(decryptionStream, StandardCharsets.UTF_8);
Enter fullscreen mode Exit fullscreen mode

Decrypt the file

if you have an existing encrypted file, and you want to decrypt it to another file location. You can initialize the ByteArrayOutputStream which will receive the decrypted data.

ByteArrayOutputStream decryptedContentOutput;
Streams.pipeAll(decryptionStream, decryptedContentOutput);
// must close before we can get the metadata.
decryptionStream.close();
MessageMetadata messageMetadata = decryptionStream.getMetadata();
logCompleted(messageMetadata, inputFileLocation);
Enter fullscreen mode Exit fullscreen mode

Top comments (0)