Elliptic Curve Cryptography with OpenPGP.js

Elliptic Curve Cryptography

Elliptic curve cryptography (ECC in short) brings asymmetric encryption with smaller keys. In other words, you can encrypt your data faster and with an equivalent level of security, using comparatively smaller encryption keys.

As you may know, public-key cryptography works with algorithms that you can easily process in one direction. But you cannot process those as easily the other way around. For example, RSA multiplies two large prime numbers and obtains a very large resulting number. However, even when you know that result, it’s really REALLY hard to find back the two primes that could produce it! That algorithmic property makes a good cryptosystem: multiplying is easy, factoring (finding back the original prime numbers) is hard. The larger the gap between the two, the better.

However RSA can also be very slow, as you may have noticed by playing around with my OpenPGP integration tutorial. To keep on the safe side you should use at minimum 2048 bits long keys… or larger. And that makes RSA encryption slower. But then some other algorithms are becoming more efficient at finding back the original prime numbers. This means that, in the long term, the gap between the easy direction and the hard one will become smaller. As machines get faster, bigger key sizes will bring less factorization difficulty (and thus security) for more time-consuming multiplications.

Elliptic Curve Cryptography

Elliptic curve cryptography proposes another sort of trapdoor function which improves on the factoring problem.

I’ll be honest: I’m no mathematician. Though this Ars Technica article absolutely fascinated me, I could not wrap my head around all those mathematical properties that make ECC that good. Luckily some very intelligent persons have demonstrated the advantages and eventual downsides of it. And we can trust OpenPGP.js to provide the safest implementation you can find. They maintain the library and built ProtonMail with it. They should know a thing or two about safely encrypting data!

So let’s get started!

Setting up and running the demo code

I always try to provide a working piece of code when useful, and this post makes no exception. You can find it in this GitHub repository for your convenience!

This time I went for a simple static web application. You can simply run npm install from the project root to install the dependencies.

From there, just type npm start to run a local http-server. Open your favorite browser and access http://localhost:8080. Now you can make Alice and Bob exchange encrypted messages!

ECC keys generation

Let’s skip the obvious bits and focus on that index.js file. It contains the logic to generate keys, encrypt and decrypt messages.

We generate the keys for Alice and Bob based on their User ID. There’s nothing spectacular there, except how fast OpenPGP.js generates them. Compared to RSA, the library provides ECC keys in a blink!

openpgp.generateKey({
    userIds: alice.userId,
    curve: "ed25519",
    passphrase: alice.passphrase
}).then(function (key) {
    alice.keys.publicArmored = key.publicKeyArmored;
    alice.keys.privateArmored = key.privateKeyArmored;
    $('#alice-com').text("Alice's keys generated");
});

The code builds the keys with an “ed25519” curve, which ProtonMail also uses for its email encryption.
Now, if you look at this table you may notice something odd. It says that the “ed25519” curve is used for signing only, while “curve25519” takes care of encryption tasks, but not signatures. Bummer!
There is actually a very good reason for this, which I’m not going to discuss here. I would be unable to, really. Don’t worry though: OpenPGP.js has you covered. It automatically generates a primary “ed25519” key for signatures and a “curve25519” subkey for encryption. So you’re good to go!

Encrypting stuff

Now that we have the keys, let’s see how we encrypt a text message.

var encrypt = function (sender, message, receiver) {
    var senderPrivateKeys, receiverPublicKeys;

    return new Promise(function (resolve) {
        openpgp.key.readArmored(sender.keys.privateArmored).then(function (readKeys) {
            senderPrivateKeys = readKeys;

            return senderPrivateKeys.keys[0].decrypt(sender.passphrase);
        }).then(function () {

            return openpgp.key.readArmored(receiver.keys.publicArmored);
        }).then(function (readKeys) {
            receiverPublicKeys = readKeys;

            return openpgp.encrypt({
                armor: true,
                compression: openpgp.enums.compression.zlib,
                message: openpgp.message.fromText(message),
                publicKeys: receiverPublicKeys.keys,
                privateKeys: senderPrivateKeys.keys
            });
        }).then(function (encrypted) {

            resolve(encrypted.data);
        });
    });
};

This bit of code differs from OpenPGP.js’ examples: mine uses ye old Promise object instead of async.

To sum itup, we first read the sender’s private and receiver’s public armored keys. We must decrypt the private key with the sender’s passphrase.
We then invoke OpenPGP’s encrypt function. It takes a “Message” object which we build from our text. We request to armor and compress the resulting encrypted message.
We obviously specify the receiver’s public keys to encrypt our text. However we also indicate the sender’s private keys in order to sign the message.

The result of that includes our encrypted data. That was easy, was it!

Decrypting stuff

It is as easy to decrypt and verify the encrypted message.

var decrypt = function (receiver, encryptedArmoredMessage, sender) {
    var receiverPrivateKeys, senderPublicKeys;

    return new Promise(function (resolve) {
        openpgp.key.readArmored(receiver.keys.privateArmored).then(function (readKeys) {
            receiverPrivateKeys = readKeys;

            return receiverPrivateKeys.keys[0].decrypt(receiver.passphrase);
        }).then(function () {

            return openpgp.key.readArmored(sender.keys.publicArmored);
        }).then(function (readKeys) {
            senderPublicKeys = readKeys;

            return openpgp.message.readArmored(encryptedArmoredMessage);
        }).then(function (readMessage) {

            return openpgp.decrypt({
                message: readMessage,
                privateKeys: receiverPrivateKeys.keys,
                publicKeys: senderPublicKeys.keys
            });
        }).then(function (decrypted) {

            resolve(decrypted.data);
        });
    });
};

Again, we simply read the armored keys and message. Then we invoke the decrypt function. In the process, we check the message’s signature by passing the sender’s public keys. The returned result contains our unencrypted message.

Conclusion

In the near future, I suspect most of what will travel on the web will be encrypted beyond SSL. Since you are reading this post, I guess you can feel that too! Hopefully I have just shown how you can easily ECC encrypt your data thanks to OpenPGP.js.

I wonder how hard it would be to do the same in Java…

Until next time,

Cheers!

2 comments

  1. Serhat Reply
    27/07/2019 at 21:51

    Hello,

    “It automatically generates a primary “ed25519” key for signatures and a “curve25519” subkey for encryption. ”
    thanks for making this clear.

    By the way, in OpenPGP.js github page (https://github.com/openpgpjs/openpgpjs#encrypt-and-decrypt-string-data-with-pgp-keys), it says:
    “Encryption will use the algorithm preferred by the public key (defaults to aes256 for keys generated in OpenPGP.js)”

    is the AES256 used for encrypting/decrypting the private key with the passphrase? if not, how it is used in public-key cryptography?

    Thanks.

    • Diego Reply
      02/08/2019 at 16:47

      Hello Serhat,

      Sorry for the late reply, but I was taking a few days “off-the-grid” with the family 😉
      Your question is a very interesting one! I think what the OpenPGP.js page refers to is the symmetric key being used for the actual encryption and decryption.

      Elliptic curve cryptography is a hybrid cryptosystem: the private key is not used to encrypt the text itself, but rather to protect the symmetric key that encrypts the content being exchanged. Why? Because when doing RSA for example, encrypting a whole text ends up being very slow. So instead we encrypt the symmetric key (AES, for example) that encrypts/decrypts the exchanged content. Decrypting content means decrypting the secret key which, used with AES, can decrypt the content.

      Same goes for ECC, except that ECC itself does not directly encrypt stuff… Essentially, ECDH (Elliptic Curve Diffie-Hellman) is what allows exchanging a shared secret key, and that shared secret key is then used with an agreed algorithm (by default AES256 for OpenPGP.js) to perform the actual encryption and decryption work.

      Here’s the link to a somewhat more complex (but still very accessible) article, which describes how one could implement ECC cryptography in Python, step-by-step: https://cryptobook.nakov.com/asymmetric-key-ciphers/ecc-encryption-decryption If you can read Python, you can see where AES intervenes in the process. Kudos to Svetlin Nakov for his work on that cryptobook!

Leave A Comment

Please be polite. We appreciate that. Your email address will not be published and required fields are marked

This site uses Akismet to reduce spam. Learn how your comment data is processed.