In this post I want to share how easy it is to use OpenPGP using the Stateless OpenPGP Protocol (SOP).
I talked about the SOP specification and its purpose and benefits already in past blog posts. This time I want to give some in-depth examples of how the API can be used in your application.
There are SOP API implementations available in different languages like Java and Rust. They have in common, that they are based around the Stateless OpenPGP Command Line Specification, so they are very similar in form and function.
For Java-based systems, the SOP API was defined in the
sop-java library. This module merely contains interface definitions. It is up to the user to choose a library that provides an implementation for those interfaces. Currently the only known implementation is
pgpainless-sop based on PGPainless.
The single entry point to the SOP API is the
SOP interface (obviously). It provides methods for OpenPGP actions. All we need to get started is an instantiation of this interface:
// This is an ideal candidate for a dependency injection framework! SOP sop = new SOPImpl(); // provided by pgpainless-sop
Let’s start by generating a secret key for the user Alice:
byte key = sop.generateKey() .userId("Alice <email@example.com>") .generate() .getBytes();
The resulting byte array now contains our OpenPGP secret key. Next, lets extract the public key certificate, so that we can share it with out contacts.
// public key byte cert = sop.extractCert() .key(key) // secret key .getBytes();
There we go! Both byte arrays contain the key material in ASCII armored form (which we could disable by calling
.noArmor()), so we can simply share the certificate with our contacts.
Let’s actually create an encrypted, signed message. We obviously need our secret key from above, as well as the certificate of our contact Bob.
// get bobs certificate byte bobsCert = ... byte message = "Hello, World!\n".getBytes(StandardCharsets.UTF_8); byte encryptedAndSigned = sop.encrypt() .signWith(key) // sign with our key .withCert(cert) // encrypt for us, so that we too can decrypt .withCert(bobsCert) // encrypt for Bob .plaintext(message) .getBytes();
Again, by default this message is ASCII armored, so we can simply share it as a
String with Bob.
We can decrypt and verify Bobs reply like this:
// Bobs answer byte bobsEncryptedSignedReply = ... ByteArrayAndResult<DecryptionResult> decrypted = sop.decrypt() .verifyWithCert(bobsCert) // verify bobs signature .withKey(key) // decrypt with our key .ciphertext(bobsEncryptedSignedReply) .toByteArrayAndResult(); // Bobs plaintext reply byte message = decrypted.getBytes(); // List of signature verifications List<Verification> verifications = decrypted.getResult().getVerifications();
Easy! Signing messages and verifying signed-only messages basically works the same, so I’ll omit examples for it in this post.
As you can see, performing basic OpenPGP operations using the Stateless OpenPGP Protocol is as simple as it gets. And the best part is that the API is defined as an interface, so swapping the backend can simply be done by replacing the
SOP object with an implementation from another library. All API usages stay the same.
I want to use this opportunity to encourage YOU the reader: If there is no SOP API available for your language of choice, consider creating one! Take a look at the specification and an API definition like
sop-java or the
sop Rust crate to get an idea of how to design the API. If you keep your SOP API independent from any backend library it will be easy to swap backends out for another library later in the process.
Let’s make OpenPGP great again! Happy Hacking!