当前位置: 首页 > 工具软件 > OpenPGP.js > 使用案例 >

OpenPGP Integration (Java and JavaScript)

长孙淳
2023-12-01

这是一组OpenPGP应用的文章,很值得学习了解。对于高强度的安全要求的环境还是很值的应用的。
这一组文章由四部分构成:

Java keys generation

If you’ve read my previous post, you probably already know how much I appreciate OpenPGP. This standard brings structure to a complex subject, that is the secure encryption of our data. Considering the current wave of concern on privacy and data confidentiality, why not learning one more approach to secure data and communications?

Here’s what I’m proposing:

We will start by learning how to generate OpenPGP-compliant keys using Java.
We will then examine one excellent library makes encryption on the server side a breeze!
Consequently we will have a quick look at the sublime OpenPGP.js library.
Finally, we will exchange encrypted messages between Java and JavaScript. How cool is that!
I cannot go too much into specifics regarding how exactly OpenPGP works. However I recommend you to read this excellent article by Zachary Voase: it should get you started.

The code for this series of tutorials is available on GitHub.

So buckle up: here we go!

Why generating OpenPGP keys on server side?

Before we can encrypt, we must generate our encryption key pair: one public, one private. One usually generates GPG encryption keys with the help of Gnu Privacy Guard. However the command line tool requires a minimum level of understanding of PGP, which your end users might not have. Even with front end tools to facilitate the process, GPG is not everybody’s cup of tea!

Since we’re great developers, we can certainly write something to generate these keys for our users. With a web front end, we could leverage OpenPGP.js, and excellent library which will be discussed in a forthcoming post. But we may not always have that option! In those situations we can have our server generate the keys for our clients.

For one specific case, I needed the client to be able to send data to the server in a very secure way. My solution was to have the server generate and cache its own key ring, and have the public key sent to the web clients. With those public keys, the web clients would encrypt their data with the OpenPGP.js library, using the server’s public key, before sending it over.

Setting up the project

If you haven’t done it yet, I’d advise you to head to my GitHub repository and check out the code for this tutorial.

The project uses Spring Boot for the global set up, resulting in a very concise pom.xml file. It imports one important library: BouncyCastle, the de facto Java standard for the encryption-hungry programmers! The OpenPGP class implements the whole logic and is built as a utility library which you should be able to reuse for your own projects. Obviously, the OpenPGPTest class ensures that everything works as expected…

We start by registering BouncyCastle as the security provider:

static {
    if (Security.getProvider(BouncyCastleProvider.PROVIDER_NAME) == null) {
        Security.addProvider(new BouncyCastleProvider());
    }
}

We also implement a simple ArmoredKeyPair class to encapsulate the notion of armored key pairs throughout the code. As a reminder: armored keys use a text format that looks like this:

-----BEGIN PGP PUBLIC KEY BLOCK-----
Version: BCPG v1.59
...
-----END PGP PUBLIC KEY BLOCK-----

OpenPGP Java keys generation

The entry-point method provides the key generation process’ outline:

public ArmoredKeyPair generateKeys(int keySize, String userIdName, String userIdEmail, String passphrase) throws PGPException {
    Date now = new Date();
    RSAKeyPairGenerator keyPairGenerator = keyPairGenerator(keySize);
    PGPKeyPair encryptionKeyPair = encryptionKeyPair(now, keyPairGenerator);
    PGPSignatureSubpacketVector encryptionKeySignature = encryptionKeySignature();
    PGPKeyPair signingKeyPair = signingKeyPair(keyPairGenerator, now);
    PGPSignatureSubpacketVector signingKeySignature = signingKeySignature();
    PGPKeyRingGenerator keyRingGenerator = new PGPKeyRingGenerator(
            PGPSignature.POSITIVE_CERTIFICATION,
            signingKeyPair,
            userIdName + " <" + userIdEmail + ">",
            new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1),
            signingKeySignature,
            null,
            new BcPGPContentSignerBuilder(signingKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1),
            secretKeyEncryptor(passphrase)
    );
    keyRingGenerator.addSubKey(encryptionKeyPair, encryptionKeySignature, null);
    try {
        return ArmoredKeyPair.of(
                generateArmoredSecretKeyRing(keyRingGenerator),
                generateArmoredPublicKeyRing(keyRingGenerator));
    } catch (IOException e) {
        throw new RuntimeException(e);
    }
}

We use the RSAKeyPairGenerator from BouncyCastle to build our encryption and signature RSA keys.
Those keys are in turn “signed” with sub-packet vectors. We essentially flag the keys to describe they roles: we sign one as being the “encryption key”…

private PGPSignatureSubpacketVector encryptionKeySignature() {
    PGPSignatureSubpacketGenerator encryptionKeySignatureGenerator = new PGPSignatureSubpacketGenerator();
    encryptionKeySignatureGenerator.setKeyFlags(false, KeyFlags.ENCRYPT_COMMS | KeyFlags.ENCRYPT_STORAGE);
    return encryptionKeySignatureGenerator.generate();
}

… and the other as being the “signing key”:

private PGPSignatureSubpacketVector signingKeySignature() {
    PGPSignatureSubpacketGenerator signingKeySignatureGenerator = new PGPSignatureSubpacketGenerator();
    signingKeySignatureGenerator.setKeyFlags(false, KeyFlags.SIGN_DATA | KeyFlags.CERTIFY_OTHER); 
    signingKeySignatureGenerator.setPreferredSymmetricAlgorithms(false, new int[]{SymmetricKeyAlgorithmTags.AES_256});
    signingKeySignatureGenerator.setPreferredHashAlgorithms(false, new int[]{HashAlgorithmTags.SHA512});
    signingKeySignatureGenerator.setFeature(false, Features.FEATURE_MODIFICATION_DETECTION);
    return signingKeySignatureGenerator.generate();
}

We can now proceed to building a key ring for our keys.

Keeping our keys together

A PGP key ring keeps your keys together. We normally want two key rings. One stores our public keys and the public keys of whoever we trust and exchange messages with. The other, the secret key ring, regroups our private keys.

In practice we will use BouncyCastle to build a global “key ring generator”, from which we can extract our public and secret key rings:

PGPKeyRingGenerator keyRingGenerator = new PGPKeyRingGenerator(
        PGPSignature.POSITIVE_CERTIFICATION,
        signingKeyPair,
        userIdName + " <" + userIdEmail + ">",
        new BcPGPDigestCalculatorProvider().get(HashAlgorithmTags.SHA1),
        signingKeySignature,
        null,
        new BcPGPContentSignerBuilder(signingKeyPair.getPublicKey().getAlgorithm(), HashAlgorithmTags.SHA1),
        secretKeyEncryptor(passphrase)
);
keyRingGenerator.addSubKey(encryptionKeyPair, encryptionKeySignature, null);

Now that we have our key ring generator, we can generate our public and private key rings:

try {
    return ArmoredKeyPair.of(
            generateArmoredSecretKeyRing(keyRingGenerator),
            generateArmoredPublicKeyRing(keyRingGenerator));
} catch (IOException e) {
    throw new RuntimeException(e);
}
private String generateArmoredSecretKeyRing(PGPKeyRingGenerator keyRingGenerator) throws IOException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try (
            ArmoredOutputStream armoredOutputStream = new ArmoredOutputStream(outputStream);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(armoredOutputStream)
    ) {
        keyRingGenerator.generateSecretKeyRing().encode(bufferedOutputStream);
    }
    return outputStream.toString(StandardCharsets.UTF_8.name());
}
private String generateArmoredPublicKeyRing(PGPKeyRingGenerator keyRingGenerator) throws IOException {
    ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
    try (
            ArmoredOutputStream armoredOutputStream = new ArmoredOutputStream(outputStream);
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(armoredOutputStream)
    ) {
        keyRingGenerator.generatePublicKeyRing().encode(bufferedOutputStream, true);
    }
    return outputStream.toString(StandardCharsets.UTF_8.name());
}

And voilà!

The full code for generating the keys as well as the test class that makes it run (OpenPGPTest) available on the GitHub repo. I have successfully used the keys generated by this code to exchange messages with a front end client using OpenPGP.js. But I’ll get back to that in a forthcoming post 

As you know, many of these posts are written by learning from the best out there.
The code I’ve described here is largely inspired from BouncyCastle’s PGP Cookbook. And these articles and posts do a great job at explaining the principles of PGP encryption in a simple, accessible way. I heartily recommend them:

Java PGP encryption

Previously on Codes And Notes:

– … But honey, we don’t have the key to that door!
– Don’t worry mi Amor, I have studied the ancient Art of PGP Key Generation. I have made a pair for you and tie them on a ring.
– A ring? Hay Querida, I love your style! Will you marry me?
Will Pedro and Asunción manage to escape from this dreaded place and live to get married? You will find out on the next episode…

… ahem…

Anyway, after generating PGP key pairs in Java we are now going to see how to use those to encrypt messages. As always, you can find the code on GitHub with tests and all, ready to be grabbed and leveraged for your own projects!

Java PGP Encryption

Traditionally the Java developer would grab the seminal BouncyCastle library and prepare himself/herself to hack away for days to obtain an encrypted PGP message… And discover that other PGP-based applications could not decrypt the message anyway. Therefore a few valiant developers got to work and created libraries to simplify the process.

Bouncy GPG is one of those libraries. Its author, Jens Neuhalfen, has put a lot of time and effort to come up with a flexible, easy-to-use solution. Frankly, I’ve been unable to find anything better than this. The only thing missing is programmatic PGP generation. But guess what: we have implemented that on the previous episode!

So let’s see how we use Bouncy GPG with our generated keys.

The Bouncy Identity

We start by importing bouncy-pgp into our pom.xml file.

<dependency>
    <groupId>name.neuhalfen.projects.crypto.bouncycastle.openpgp</groupId>
    <artifactId>bouncy-gpg</artifactId>
    <version>${bouncygpg.version}</version>
</dependency>

Bouncy GPG requires us to declare an access to Bintray’s repository:

<repositories>
    <repository>
        <id>bintray</id>
        <name>bintray</name>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
        <url>http://jcenter.bintray.com</url>
    </repository>
</repositories>

For our tests’ purpose, we will define two profiles, two identities: “java” and “avaj”. Each of these identities includes a user ID name, a user ID email, a passphrase and a pair of armored keys (one public and one private). Remember: armored keys use a very recognizable text format that starts with -----BEGIN PGP PUBLIC/PRIVATE KEY BLOCK-----.
The full Identities class can be found on the GitHub repository:

class Identities {
    static final int KEY_SIZE = 2048;
    /****************************************/
    static final String JAVA_USER_ID_NAME = "java";
    static final String JAVA_USER_ID_EMAIL = "java@codesandnotes.be";
    static final String JAVA_PASSPHRASE = "java passphrase";
    static final String JAVA_PRIVATE_KEYS = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
            "Version: BCPG v1.59\n" +
            "\n" +
            ...
            "-----END PGP PRIVATE KEY BLOCK-----\n";
    static final String JAVA_PUBLIC_KEYS = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
            "Version: BCPG v1.59\n" +
            "\n" +
            ...
            "-----END PGP PUBLIC KEY BLOCK-----\n";
    /****************************************/
    static final String AVAJ_USER_ID_NAME = "avaj";
    static final String AVAJ_USER_ID_EMAIL = "avaj@codesandnotes.be";
    static final String AVAJ_PASSPHRASE = "avaj passphrase";
    static final String AVAJ_PRIVATE_KEYS = "-----BEGIN PGP PRIVATE KEY BLOCK-----\n" +
            "Version: BCPG v1.59\n" +
            "\n" +
            ...
            "-----END PGP PRIVATE KEY BLOCK-----\n";
    static final String AVAJ_PUBLIC_KEYS = "-----BEGIN PGP PUBLIC KEY BLOCK-----\n" +
            "Version: BCPG v1.59\n" +
            "\n" +
            ...
            "-----END PGP PUBLIC KEY BLOCK-----\n";
}

The ring of fire
Now that we have all the necessary properties. We can move back to the OpenPGP class and implement our methods to encrypt and decrypt PGP messages. We will also sign those messages to make sure that the sender of the encrypted message is who he says he is.

For “java” to send a message to “avaj”, he needs a key ring that contains his PGP keys as well as”avaj”‘s public key. Bouncy GPG provides all the tools needed to easily build such a ring:

private InMemoryKeyring keyring(String passphrase,
                                ArmoredKeyPair armoredKeyPair,
                                String... recipientsArmoredPublicKey)
        throws IOException, PGPException {
    InMemoryKeyring keyring = KeyringConfigs.forGpgExportedKeys(KeyringConfigCallbacks.withPassword(passphrase));
    keyring.addSecretKey(armoredKeyPair.privateKey().getBytes(UTF_8));
    keyring.addPublicKey(armoredKeyPair.publicKey().getBytes(UTF_8));
    for (String recipientArmoredPublicKey : recipientsArmoredPublicKey) {
        keyring.addPublicKey(recipientArmoredPublicKey.getBytes(UTF_8));
    }
    return keyring;
}

The KeyrinConfigs class provides static methods to build a ring, whether the keys are provided as files, as a resource, etc. Since we store the keys a String objects, we’re going to build our ring from the String’s bytes and produce an InMemoryKeyring implementation of a ring.
It is then just a matter of adding the sender’s public and private keys (provided, for clarity, as a custom value object named ArmoredKeyPair), and then registering the recipient’s (“avaj”) armored public key.

Encrypting and signing

Good, we have a key ring! We can now proceed to encrypt and sign “java”‘s message to “avaj”. Here’s the full method implementation as found on the repository:

public String encryptAndSign(String unencryptedMessage,
                             String senderUserIdEmail,
                             String senderPassphrase,
                             ArmoredKeyPair senderArmoredKeyPair,
                             String receiverUserId,
                             String receiverArmoredPublicKey)
        throws IOException, PGPException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException {
    InMemoryKeyring keyring = keyring(senderPassphrase, senderArmoredKeyPair, receiverArmoredPublicKey);
    ByteArrayOutputStream encryptedOutputStream = new ByteArrayOutputStream();
    try (
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(encryptedOutputStream);
            OutputStream bouncyGPGOutputStream = BouncyGPG.encryptToStream()
                    .withConfig(keyring)
                    .withStrongAlgorithms()
                    .toRecipient(receiverUserId)
                    .andSignWith(senderUserIdEmail)
                    .armorAsciiOutput()
                    .andWriteTo(bufferedOutputStream)
    ) {
        Streams.pipeAll(new ByteArrayInputStream(unencryptedMessage.getBytes()), bouncyGPGOutputStream);
    }
    return encryptedOutputStream.toString(UTF_8.name());
}

The BouncyGPG.encryptToStream()call creates an output stream to which we can feed the data to encrypt. We specify all the required elements through chaining methods on that stream: the key ring, the algorithms to use, the recipient’s and the signer’s user ID emails and the output format we want (in this case, we’ll generate an armored ASCII output).
We also indicate where the encrypted data must be written to: in this example we pipe the data to a BufferedOutputStream feeding a ByteArrayOutputStream, from which we’ll be able to obtain our encrypted bytes.

The data to encrypt must be a stream too, so we build a ByteArrayInputStream of the data’s bytes. We then feed our input stream to Bouncy GPG’s output stream using BouncyCastle's Streams.pipeAll()utility method. In the end, we obtain our resulting stream of bytes in our “encryptedOutputStream” ByteArrayOutputStream.

In the end, we convert the encrypted output stream into an UTF-8 string and… voilà!

Decrypting and verifying the signature

Of course “avaj” needs to decrypt what “java” sent him. Here’s the method to decrypt a PGP message and verify its signature:

public String decryptAndVerify(String encryptedMessage,
                               String receiverPassphrase,
                               ArmoredKeyPair receiverArmoredKeyPair,
                               String senderUserIdEmail,
                               String senderArmoredPublicKey)
        throws IOException, PGPException, NoSuchProviderException {
    InMemoryKeyring keyring = keyring(receiverPassphrase, receiverArmoredKeyPair, senderArmoredPublicKey);
    ByteArrayOutputStream unencryptedOutputStream = new ByteArrayOutputStream();
    try (
            BufferedOutputStream bufferedOutputStream = new BufferedOutputStream(unencryptedOutputStream);
            InputStream bouncyGPGInputStream = BouncyGPG
                    .decryptAndVerifyStream()
                    .withConfig(keyring)
                    .andRequireSignatureFromAllKeys(senderUserIdEmail)
                    .fromEncryptedInputStream(new ByteArrayInputStream(encryptedMessage.getBytes(UTF_8)))
    ) {
        Streams.pipeAll(bouncyGPGInputStream, bufferedOutputStream);
    }
    return unencryptedOutputStream.toString(UTF_8.name());
}

As you can see, the approach resembles the one used for the encryption method. Bouncy GPG provides the initial stream to decrypt and verify. We specify the required details on that stream: the receiver’s key ring, the sender’s user ID email for signature verification. In the end we obtain a stream of unencrypted bytes, courtesy of some “java.io” plumbing. Those bytes easily become an UTF-8 string of the message sent by “java”.

OpenPGP at work

To see all this at work, just head to OpenPGPTest and run the encryption and decryption test:

@Test
public void encryptSignedMessageAsJavaAndDecryptItAsAvaj() throws IOException, PGPException, NoSuchAlgorithmException, SignatureException, NoSuchProviderException {
    String unencryptedMessage = "Message from java to avaj: you're all backwards!";
    String encryptedMessage = openPgp.encryptAndSign(
            unencryptedMessage,
            JAVA_USER_ID_EMAIL,
            JAVA_PASSPHRASE,
            OpenPGP.ArmoredKeyPair.of(JAVA_PRIVATE_KEYS, JAVA_PUBLIC_KEYS),
            AVAJ_USER_ID_EMAIL,
            AVAJ_PUBLIC_KEYS);
    assertThat(encryptedMessage).isNotEmpty();
    LOGGER.info("java's encrypted message to avaj:\n" + encryptedMessage);
    String messageDecryptedByAvaj = openPgp.decryptAndVerify(
            encryptedMessage,
            AVAJ_PASSPHRASE,
            OpenPGP.ArmoredKeyPair.of(AVAJ_PRIVATE_KEYS, AVAJ_PUBLIC_KEYS),
            JAVA_USER_ID_EMAIL,
            JAVA_PUBLIC_KEYS);
    assertThat(messageDecryptedByAvaj).isEqualTo(unencryptedMessage);
}

Now we can encrypt and decrypt PGP messages in Java. Next time we will see how we can do that in JavaScript, courtesy of OpenPGP.js.

OpenPGP.js

Thinking of it, the OpenPGP.js library is quite an amazing feat.

You probably know that ProtonMail is now the primary maintainer of this JavaScript implementation of the OpenPGP standard. Tankred Hase, co-founder of the unfortunately now-defunct Whiteout Mail, developed the library before that. Other projects such as Mailvelope also use OpenPGP.js at their core.

Although intimidating at first (and slightly under-documented, IMHO) OpenPGP.js provides one of the easiest and most reliable ways to perform PGP encryption on the front side. Let’s see how!

Quick reminder: this tutorial’s previous posts are available here:

Generate key pairs

OpenPGP can very easily generate key pairs, including ECC keys since version 3. We shall stick to RSA keys for this tutorial, though.
So, having imported the openpgp library we can now obtain our RSA key pairs as follows:

<script>
  const javascriptUserId = {name: 'javascript', email: 'javascript@codesandnotes.be'};
  const javascriptPassphrase = 'javascript passphrase';
  const javascriptNumBits = 2048;
  // Initialize openpgp
  let openpgp = window.openpgp;
  openpgp.initWorker({path: 'node_modules/openpgp/dist/openpgp.worker.min.js'});
  // Generate some keys!
  openpgp.generateKey({
    userIds: javascriptUserId,
    numBits: javascriptNumBits,
    passphrase: javascriptPassphrase
  }).then(keyPair => {
    console.info(keyPair.publicKeyArmored);
    console.info(keyPair.privateKeyArmored);
  });
</script>

Make sure you choose a good passphrase, and also use the same number of bits than the Java RSA keys.

Great, we now have our JavaScript-generated key pairs. But we also have our Java, server-side generated keys from our previous tutorial. How about using both to perform some front end PGP encryption and decryption tasks?

Front end PGP encryption and decryption

Say Ms JavaScript wants to send an encrypted message to Mr Java using a front end client.
We know we need Mr Java’s public key to encrypt the message.
However we also must use Ms JavaScript’s private key to sign that message! Since the armored private key is protected by passphrase, the code starts by decrypting it. This must be performed once, as long as we have a hold on the unencrypted private key.

One we have all the required elements, we proceed on encrypting the message addressed to Mr Java:

let javascriptPrivateKeys = openpgp.key.readArmored(javascriptPrivateKeysArmored);
let javascriptUnencryptedPrivateKeysPromises = javascriptPrivateKeys.keys.map(javascriptPrivateKey => {
  return javascriptPrivateKey.decrypt(javascriptPassphrase);
});
Promise.all(javascriptUnencryptedPrivateKeysPromises).then(() => {
  return openpgp.encrypt({
    armor: true,
    compression: openpgp.enums.compression.zlib,
    data: 'A message encrypted by javascript',
    privateKeys: javascriptPrivateKeys.keys,
    publicKeys: openpgp.key.readArmored(javaPublicKeysArmored).keys
  });
}).then(encryptedMessage => {
  console.info(encryptedMessage.data);
});

Now let’s assume Mr Java receives that encrypted message and wants to decrypt it. We start by decrypting Mr Java’s armored private key if not done already. Then we use that key to decrypt the message sent by Ms JavaScript. Since we want confirmation that the message really comes from Ms JavaScript we also need her public key:

let javaPrivateKeys = openpgp.key.readArmored(javaPrivateKeysArmored);
let javaUnencryptedPrivateKeysPromises = javaPrivateKeys.keys.map(javaPrivateKey => {
  return javaPrivateKey.decrypt(javaPassphrase);
});
Promise.all(javaUnencryptedPrivateKeysPromises).then(() => {
  
  return openpgp.decrypt({
    message: openpgp.message.readArmored(encryptedMessage.data),
    publicKeys: openpgp.key.readArmored(javascriptPublicKeysArmored).keys,
    privateKeys: javaPrivateKeys.keys
  });
  
}).then(unencryptedMessage => {
  console.info(unencryptedMessage.data);
});

And that’s basically all there is to it.

Running the tutorial code

You can also perform this the other way around, obviously: having Mr Java encrypt a message to Ms JavaScript. In fact, you can find the whole tutorial code on this GitHub repository.
For simplicity, I have set up a Spring Boot web application. Just run the Application class and point your browser to http://localhost:8080 . A simple index HTML page will give you links to run the test code we discussed here.

So far, we’ve been able to generate PGP key pairs on both sides. We also performed encryption and decryption tasks in Java and in JavaScript, using key pairs generated by one side or the other. That’s encouraging!

We’re one step from claiming OpenPGP integration between the two environments, and that’s having JavaScript decrypt a PGP message sent by the Java server.
Don’t worry though: the hardest part is behind us now.

PGP exchange between Java and JavaScript

Finally, we get to the last step of our mission: implementing a working PGP exchange between Java and JavaScript. To be honest, we have already completed all the necessary steps in this tutorial’s previous posts. We now simply need to put it all together.

PGP exchange

We start by adding some values to our Identities class, purely for convenience. We specify the user ID details, keys and passphrase of our “JavaScript” user. Remember, we generated those details using OpenPGP.js during this tutorial’s previous post.

We will need those details for two reasons:

  1. We want to test the OpenPGP.js-generated keys and see if Java is able to use them for encryption / decryption work. Please note that in a normal “exchange” situation we do NOT need the sender’s private keys or passphrase, but only its public keys to verify the received message’s signature!
  2. We want to test whether a message encrypted by “JavaScript” can be decrypted by “Java”.

So how shall we proceed?

Java – JavaScript artifacts compatibility

Let’s start by checking if we can encrypt and decrypt messages using “JavaScript”‘s keys.

In the OpenPGPTest.java file, the test named encryptMessageAsJavascriptAndDecryptItAsJava()does exactly that. It uses “JavaScript”‘s artifacts to encrypt a message to Java:

String encryptedMessage = openPgp.encryptAndSign(
    unencryptedMessage,
    JAVASCRIPT_USER_ID_EMAIL,
    JAVASCRIPT_PASSPHRASE,
    OpenPGP.ArmoredKeyPair.of(JAVASCRIPT_PRIVATE_KEYS, JAVASCRIPT_PUBLIC_KEYS),
    JAVA_USER_ID_EMAIL,
    JAVA_PUBLIC_KEYS);

We then make “Java” decrypt the message, using “JavaScript”‘s public keys to verify the message’s signature.

String messageDecryptedByJava = openPgp.decryptAndVerify(
    encryptedMessage,
    JAVA_PASSPHRASE,
    OpenPGP.ArmoredKeyPair.of(JAVA_PRIVATE_KEYS, JAVA_PUBLIC_KEYS),
    JAVASCRIPT_USER_ID_EMAIL,
    JAVASCRIPT_PUBLIC_KEYS);

As you probably noticed, we leverage that OpenPGP class we wrote for this previous post. Easy-lazy!

We can of course try this the other way around, encrypting the message as “Java” and decrypting it as “JavaScript”. You can find the test for that in the encryptMessageAsJavaAndDecryptItAsJavascript()method.

Exchanging those messages

And so we swiftly move to our final milestone: make Java decrypt messages sent using OpenPGP.js and vice-versa.

Remember that, in this post, “JavaScript” encrypted a message addressed to “Java”, using “Java”‘s public keys? Well let’s start by copying the generated message into our OpenPGPTest class:

private static final String JAVASCRIPT_ENCRYPTED_MESSAGE_TO_JAVA = 
    "-----BEGIN PGP MESSAGE-----\n" +
    "Version: OpenPGP.js v3.0.11\n" +
    ...

We can now attempt to decrypt that message with Java, as described here:

@Test
public void decryptJavascriptMessageToJava() throws IOException, PGPException, NoSuchProviderException {
    String decryptedMessage = openPgp.decryptAndVerify(
        JAVASCRIPT_ENCRYPTED_MESSAGE_TO_JAVA,
        JAVA_PASSPHRASE,
        OpenPGP.ArmoredKeyPair.of(JAVA_PRIVATE_KEYS, JAVA_PUBLIC_KEYS),
        JAVASCRIPT_USER_ID_EMAIL,
        JAVASCRIPT_PUBLIC_KEYS
    );
    assertThat(decryptedMessage).isEqualTo(JAVASCRIPT_UNENCRYPTED_MESSAGE_TO_JAVA);
}

Go ahead and run that test: if all went well, it should be green. That means we have successfully managed to decrypt in Java a message that was encrypted in JavaScript!

But does it work the other way around? To verify that, we copy the message we generated in encryptMessageAsJavaAndDecryptItAsJavascript()into a decrypt-java-to-javascript.html file and attempt to decrypt it using OpenPGP.js and “JavaScript”‘s keys:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Encrypt</title>
    <script src="node_modules/openpgp/dist/openpgp.min.js"></script>
</head>
<body>
    <h3>Unencrypted message:</h3>
    <div id="unencryptedMessage"></div>
    <script>
        const javaUserId = {name: 'java', email: 'java@codesandnotes.be'};
        const javaPassphrase = 'java passphrase';
        const javaNumBits = 2048;
        javaPublicKeysArmored = `...`;
        const javascriptUserId = {name: 'javascript', email: 'javascript@codesandnotes.be'};
        const javascriptPassphrase = 'javascript passphrase';
        const javascriptNumBits = 2048;
        const javascriptPublicKeysArmored = `...`;
        const javascriptPrivateKeysArmored = `...`;
        const javaMessageToJavascript = `-----BEGIN PGP MESSAGE-----
lots of characters here...
-----END PGP MESSAGE-----
`;
        // Initialize openpgp
        let openpgp = window.openpgp;
        openpgp.initWorker({path: 'node_modules/openpgp/dist/openpgp.worker.min.js'});
        // Decrypt
        let javascriptPrivateKeys = openpgp.key.readArmored(javascriptPrivateKeysArmored);
        let javascriptUnencryptedPrivateKeysPromises = javascriptPrivateKeys.keys.map(javascriptPrivateKey => {
            return javascriptPrivateKey.decrypt(javascriptPassphrase);
        });
        Promise.all(javascriptUnencryptedPrivateKeysPromises).then(() => {
            return openpgp.decrypt({
                message: openpgp.message.readArmored(javaMessageToJavascript),
                publicKeys: openpgp.key.readArmored(javaPublicKeysArmored).keys,
                privateKeys: javascriptPrivateKeys.keys
            });
        }).then((unencryptedMessage) => {
            document.getElementById('unencryptedMessage').innerHTML = unencryptedMessage.data;
            console.info(unencryptedMessage.data);
        });
    </script>
</body>
</html>

Now access that page with your browser and watch our message unfold.

Hurrah !!!

Yes, we did it: we managed to have Java and JavaScript exchange PGP-encrypted messages!

The whole code that sums up this tutorial is available on GitHub, so feel free to check it out and adapt it as you deem fit. Be patient though! Encryption is a sensitive beast that requires patience and… well, lots of unit tests !

 类似资料:

相关阅读

相关文章

相关问答