Using PGP Encryption with Nodejs

Starter guide on Pretty Good Privacy(PGP) with Nodejs. PGP, a cryptographic process used to encrypt and decrypt information.
profile
Andy YeungFirst published: 2020-11-10Last updated: 2025-06-25
using-pgp-encryption-with-nodejs

What is PGP?

PGP (Pretty Good Privacy) is a cryptographic process used to encrypt and decrypt information. It combines concepts from symmetric and asymmetric key encryption, maintaining some of the best security and usability aspects of both.

One way PGP can be used is to protect the confidentiality of information. Once the information is encrypted, nobody will be able to decrypt it unless they have the right key. In practice, PGP is commonly used in sending and receiving emails, sharing information on the Dark Web, and others. This is because both on and off the Internet, there are ways to intercept information being sent, making encryption using PGP or similar critical.

On a high-level the process between a sender and receiver looks like this:

  1. The recipient generates public and private keys.
  2. The recipient sends its public key to the sender.
  3. The sender encrypts the message using the given public key.
  4. The sender sends the encrypted message to the recipient.
  5. The recipient decrypts the message using its private key.

PGP Examples in Node.js

Now, let's go over some examples in Node.js using the openpgp library.

  • OpenPGP is a protocol that defines the standards for PGP. OpenPGP.js implements the OpenPGP protocol in JavaScript.

We'll go over some basic examples and show how to encrypt & decrypt large files using Node.js streams.

First, set up your Node.js project and install openpgp.js:

1mkdir pgp-tutorial && cd pgp-tutorial && npm init 2npm i openpgp --save

Note: examples use openpgp v4.10.8

Generating keys

When generating private and public PGP keys with OpenPGP, you can define which curve to use in Elliptic-curve cryptography. In this example, we use Ed25519 for its performance and small key size. For the full list of curves, you can choose from, refer to OpenPGP.js docs.

You also need to define a passphrase used to decrypt files and the private key. In practice, this should be a strong, randomized secret generated for a single-use.

1// generate-keys.js 2const openpgp = require("openpgp"); 3generate(); 4async function generate() { 5const { privateKeyArmored, publicKeyArmored } = await openpgp.generateKey({ 6userIds: [{ name: "person", email: "person@somebody.com" }], 7curve: "ed25519", 8passphrase: "qwerty", 9}); 10console.log(privateKeyArmored); 11console.log(publicKeyArmored); 12}

Running the above gives us our private key:

1-----BEGIN PGP PRIVATE KEY BLOCK----- 2Version: OpenPGP.js v4.10.8 3Comment: https://openpgpjs.org 4xYYEX6iKVxYJKwYBBAHaRw8BAQdANJ6JIXuMMZV3NIlwq0POS7xsF2N7+kAE 57KQjAtfIuqj+CQMI4CUgW9jPsGPgJvQnnCWFf1s7lO/5+D5ZQ9JK25fUtmQo 6WyHX0Ja1ryOoFnvq7u+7fUC0+RAzt8S1xv3eDzazfgNuLtEmufwMyR6wMi78 7Kc0ccGVyc29uIDxwZXJzb25Ac29tZWJvZHkuY29tPsKPBBAWCgAgBQJfqIpX 8BgsJBwgDAgQVCAoCBBYCAQACGQECGwMCHgEAIQkQVrbGpNEnCPUWIQQb8YRJ 9hw7DjekU68lWtsak0ScI9UM7AQDv4YRbIdU2ErPf8MobreeLiXXjYZ6fas8E 10zW0KoTZWEQD+NHDY2YYByMF1mWusPkdPDpyBzqMJrlMeihMzZ+PE8AfHiwRf 11qIpXEgorBgEEAZdVAQUBAQdARY37/Vys4Sj6DvwN6TRjxrIqiMIngxQgvOb6 12wi+tQzEDAQgH/gkDCJ2xNZ1OXxv94E8fTLQ3gYHFQuebn/PSijD8CqlvHNB/ 13/Z9sIxSFt7rzorW+9v6Awfe+pQwXW5iEyJkdiGu3BM91GMwMvMmZ+rBNlBvq 14iX7CeAQYFggACQUCX6iKVwIbDAAhCRBWtsak0ScI9RYhBBvxhEmHDsON6RTr 15yVa2xqTRJwj17W0BAI5MuCWHrqjSRcdjLTwxa++jYv+Yxq4tODj8oh27T86v 16AQCfb3lij9JGlIMNDQgceeougl+Lw4Gb0kQCnsNQRggTDw== 17=yzT4 18-----END PGP PRIVATE KEY BLOCK-----

And the public key:

1-----BEGIN PGP PUBLIC KEY BLOCK----- 2Version: OpenPGP.js v4.10.8 3Comment: https://openpgpjs.org 4xjMEX6iKVxYJKwYBBAHaRw8BAQdANJ6JIXuMMZV3NIlwq0POS7xsF2N7+kAE 57KQjAtfIuqjNHHBlcnNvbiA8cGVyc29uQHNvbWVib2R5LmNvbT7CjwQQFgoA 6IAUCX6iKVwYLCQcIAwIEFQgKAgQWAgEAAhkBAhsDAh4BACEJEFa2xqTRJwj1 7FiEEG/GESYcOw43pFOvJVrbGpNEnCPVDOwEA7+GEWyHVNhKz3/DKG63ni4l1 842Gen2rPBM1tCqE2VhEA/jRw2NmGAcjBdZlrrD5HTw6cgc6jCa5THooTM2fj 9xPAHzjgEX6iKVxIKKwYBBAGXVQEFAQEHQEWN+/1crOEo+g78Dek0Y8ayKojC 10J4MUILzm+sIvrUMxAwEIB8J4BBgWCAAJBQJfqIpXAhsMACEJEFa2xqTRJwj1 11FiEEG/GESYcOw43pFOvJVrbGpNEnCPXtbQEAjky4JYeuqNJFx2MtPDFr76Ni 12/5jGri04OPyiHbtPzq8BAJ9veWKP0kaUgw0NCBx56i6CX4vDgZvSRAKew1BG 13CBMP 14=C6S6 15-----END PGP PUBLIC KEY BLOCK-----

File Encryption

Now we can start encrypting information.

Create a text file:

1echo 'This file contains secret information' > secrets.txt

Here, we act as the sender who received a public key from the intended recipient. We use their public key to encrypt the confidential information:

1// encrypt-file.js 2const openpgp = require("openpgp"); 3const fs = require("fs"); 4const publicKeyArmored = <PUBLIC KEY GIVEN BY RECIPIENT> 5encrypt(); 6async function encrypt() { 7const plainData = fs.readFileSync("secrets.txt"); 8const encrypted = await openpgp.encrypt({ 9message: openpgp.message.fromText(plainData), 10publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, 11}); 12fs.writeFileSync("encrypted-secrets.txt", encrypted.data); 13}

In the newly created encrypted-secrets.txt file, we have the contents encrypted like so:

1-----BEGIN PGP MESSAGE----- 2Version: OpenPGP.js v4.10.8 3Comment: https://openpgpjs.org 4wV4DUsPKVnc3UHMSAQdAey4TJiEOrZQIrx6q2zBLgmPkbnhPMt1WR+jCWX5x 5Gn8wEim8W4OhDVMwfhtgVIClBCGPhvdeZ1zvVUAJGDdl8+S+DUynKhPNcN8m 6Kb9TRGYs0sAlAaXcTChBHSS5kDHV/8Hgjcn0OIs6v2mbCkz/bHs/shwf8WMI 7ov711iEkgcXnXIX+ZDGyDFnAKftoygzAf0aZy82g7ejAD9SX13wNmO6TK8Gw 8wr9Xj8F6XBV0yHvdsm2uzRY9W03tTSqAf0anEs+ZWyVR/ha9ddnZJPFKtUbC 9BEF4AMavsIN0CcqpA4q69I3E6GEtkAzgBWfJOOO8mQsNQ1vJWcJocinryBE6 10Kbhznoe+R69qmUaJXPpe5scF6tfCYuQtPz4uhOljT+OUP6qss5Nz4zBs4JLq 11nUlyynLLSSgdVr4Hvg== 12=5tyF 13-----END PGP MESSAGE-----

Now, as the sender, we can send the encrypted file to the recipient.

File Decryption

Here, we act as the reciever. To decrypt the encrypted-secrets.txt file, we use our private key and passphrase:

1// decrypt-file.js 2const openpgp = require("openpgp"); 3const fs = require("fs"); 4const privateKeyArmored = <PRIVATE KEY> 5const passphrase = <PASS PHRASE>; 6decrypt(); 7async function decrypt() { 8const privateKey = (await openpgp.key.readArmored([privateKeyArmored])).keys[0]; 9await privateKey.decrypt(passphrase); 10const encryptedData = fs.readFileSync("encrypted-secrets.txt"); 11const decrypted = await openpgp.decrypt({ 12message: await openpgp.message.readArmored(encryptedData), 13privateKeys: [privateKey], 14}); 15console.log(decrypted.data); 16}

Which logs the decrypted file contents:

1This file contains secret information.

Using Streams for Large Files

If you plan on encrypting or decrypting large files, you won't be able to fit the entire file contents in memory. In this case, you can use Node.js streams.

Here, we encrypt a large file called dataset-1mill.json using streams:

1encrypt(); 2async function encrypt() { 3 const encrypted = await openpgp.encrypt({ 4 message: openpgp.message.fromText(fs.createReadStream("dataset-1mill.json")), 5 publicKeys: (await openpgp.key.readArmored(publicKeyArmored)).keys, 6 }); 7let readStream = encrypted.data; 8let writeStream = fs.createWriteStream("encrypted-dataset.txt", { flags: "a" }); 9readStream.pipe(writeStream); 10readStream.on("end", () => console.log("done!")); 11}

And then, we decrypt the newly created encrypted-dataset.txt using streams:

  • Notice that we set the flag allow_unauthenticated_stream to true, which allows streaming data before the message integrity has been checked. This is because, in our case, our OpenPGP message only has a single integrity tag at the end. This means the entire message gets loaded into memory, and we get a heap out of memory error since our file is too large to fit into memory at once.
1openpgp.config.allow_unauthenticated_stream = true; 2decrypt(); 3async function decrypt() { 4const privateKey = (await openpgp.key.readArmored([privateKeyArmored])).keys[0]; 5await privateKey.decrypt(passphrase); 6const decrypted = await openpgp.decrypt({ 7message: await openpgp.message.readArmored(fs.createReadStream("encrypted-dataset.txt")), 8privateKeys: [privateKey], 9}); 10let readStream = decrypted.data; 11let writeStream = fs.createWriteStream("decrypted-dataset.json", { flags: "a" }); 12readStream.pipe(writeStream); 13readStream.on("end", () => console.log("done!")); 14}

Now, decrypted-dataset.json will have the same contents as our original dataset-1mill.json file.

Andy Yeung
By Andy YeungSoftware Developer at LoginRadius with an interest in big data and basketball..