Using PGP Encryption with Nodejs
Starter guide on Pretty Good Privacy(PGP) with Nodejs. PGP, a cryptographic process used to encrypt and decrypt information.


Learn How to Master Digital Trust

The State of Consumer Digital ID 2024

Top CIAM Platform 2024
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:
- The recipient generates public and private keys.
- The recipient sends its public key to the sender.
- The sender encrypts the message using the given public key.
- The sender sends the encrypted message to the recipient.
- 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 --saveNote: 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.txtHere, 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.
