Library / SDK
openpgpjs/openpgpjs avatar
openpgpjs/openpgpjs

OpenPGP.js: RFC 9580 Encryption Inside JavaScript Runtimes

OpenPGP implementation for JavaScript

5,967 stars825 forksJavaScriptLGPL-3.0

At a glance

What is it?
OpenPGP.js brings the OpenPGP protocol to Node.js and the browser as a JavaScript library, with a Web Crypto backend and a v6 API that expects Web Streams. It fits applications that must encrypt or sign in-process, not command-line GPG users.
Who is it for?
Adopt OpenPGP.js when your application must encrypt, decrypt, sign or verify inside a JavaScript runtime and you control the runtime: Node.js v22 or later, or a recent browser in a secure context. Do not adopt it if you need a command-line tool, if you must support older browsers without loading the Web Streams polyfill yourself, or if you expect AEAD interoperability with other OpenPGP implementations today.
Can I use it commercially?
Yes, with conditions. LGPL-3.0 is a weak copyleft licence: you can use it inside commercial and closed-source software, but if you distribute changes to its own files, you must publish those changes under the same licence.
Is it still maintained?
Yes. The repository last received commits 5 days ago.
What is it written in?
Mainly JavaScript, according to GitHub's language statistics.

Answers come from the project's GitHub data, last synced on September 27, 2026, and from our analysis. They are not legal advice.

DEEP OPEN-SOURCE ANALYSIS

What OpenPGP.js solves, and who it is for

OpenPGP.js is a JavaScript implementation of the OpenPGP protocol, and the README states that it implements RFC 9580, which supersedes RFC 4880 and RFC 4880bis. The problem it addresses is narrow and concrete: you have application data, keys, or signatures in a JavaScript process, and you do not want to shell out to a separate binary or run a key server to get OpenPGP semantics. The library exposes encryption, decryption, signing, verification, key generation and key revocation as JavaScript calls.

The audience is application developers rather than system administrators. If your deployment is a Node.js service that stores user files, a browser application that must sign a payload before submitting it, or a build tool that verifies detached signatures on downloaded artifacts, the library keeps the whole operation inside the runtime. A person who wants to encrypt a file from a terminal is not the target reader; the README's own examples are all JavaScript snippets, and the package is published to npm under the name openpgp.

The mechanism: Web Crypto, Web Streams, and where the code runs

The architecture splits by runtime. The package.json exports map points the browser condition at dist/openpgp.min.mjs, the import condition at dist/node/openpgp.mjs, and the require condition at dist/node/openpgp.min.cjs. A separate ./lightweight export resolves to dist/lightweight/openpgp.min.mjs in the browser. That layout means bundlers and Node's resolver pick a different artifact depending on how you import the package, without you writing conditional code.

Underneath, the library delegates cryptographic work to the platform. The README says the native Web Crypto API is used for performance, and that on Node.js the native crypto module is also used where it offers additional functionality. Support for SubtleCrypto is required; in browsers it only exists in secure contexts, while in supported Node.js versions it is always available. The library ships its own fallback paths for curves the platform does not handle, which is why the curve table lists NodeCrypto and WebCrypto availability per curve rather than a single yes.

Streaming is the sharpest change in v6. Web Streams support is required, and the README is explicit that OpenPGP.js v6 no longer accepts native Node Readable streams as input, expecting and producing Node's Web Streams instead. The library also stopped polyfilling Web Streams automatically; that task is left to the user because of polyfill side effects. The README warns that loading the polyfill overwrites the global ReadableStream if one exists, and suggests keeping a reference to the native constructor before loading it if you need it later, for example when building a Response object.

Installing OpenPGP.js from npm and encrypting a first string

The README's getting-started section is organized by runtime: Node.js, Deno (marked experimental), browser with webpack, and browser with plain files. For Node.js the package requires Node v22 or later, and package.json sets engines.node to ">= 22.0.0". Install it with npm.

bash
npm install openpgp

After installation, the default entry point for an import in Node.js is the dist/node/openpgp.min.mjs bundle, so a plain import resolves without a path. The README's first code example encrypts and decrypts Uint8Array data with a password. The shape of that example is a call to encrypt with a message and a passwords array, then a call to decrypt with the resulting message and the same password, awaited because both return promises. When the decrypt call resolves, the decrypted data is available on the data property of the result.

The second documented example does the same round trip with PGP keys instead of a password, using publicKeys for encryption and privateKeys for decryption, and it works on String data. That is the pattern most applications want: the sender holds only the public key, and the private key stays where decryption happens. The README also documents generating a new key pair, revoking a key, signing and verifying cleartext messages, creating and verifying detached signatures, and streaming variants of encryption and signing. Streaming matters when the payload does not fit comfortably in memory, and it is the area where the v6 Web Streams requirement bites hardest.

AEAD is behind a flag, and it can break interoperability

The README states that the library implements authenticated encryption as per RFC 9580 using AES-GCM, OCB, or EAX, and that this makes symmetric encryption faster on platforms with native implementations. The same paragraph says the specification is very recent and other OpenPGP implementations are in the process of adopting it, so the feature sits behind a flag.

The flag is a config property, and the README gives it directly: set openpgp.config.aeadProtect = true. The warning attached to it is unambiguous, in bold in the original: activating this setting can break compatibility with other OpenPGP implementations which have yet to implement the feature. The README also notes that this setting behaves differently from the same-named setting in OpenPGP.js v5, which implemented a different specification. If you are upgrading and you had that flag set, do not assume the old meaning carries over.

This is the clearest example of a trade-off in the project. You can get faster symmetric encryption and the newer authenticated-encryption construction, but you accept that the recipient may not be able to read the message. For a closed system where both ends run OpenPGP.js v6, enabling it is a reasonable choice. For anything that has to interoperate with the wider OpenPGP ecosystem, leaving it off is the safer default, and the README's framing supports that reading.

Curve support depends on the runtime, not on the library alone

The performance section lists a curve table with three columns that matter: NodeCrypto, WebCrypto, and Constant-Time. curve25519 supports ECDH for encryption but has no signature entry, and it is marked No for both NodeCrypto and WebCrypto, with the constant-time column reading "Algorithmically". ed25519 supports EdDSA signatures, is not available through NodeCrypto, is available through WebCrypto where present, and is constant-time only if the native implementation is. The NIST curves nistP256, nistP384 and nistP521 support both ECDH and ECDSA and are available through both native backends where present. The brainpool curves and secp256k1 are available through NodeCrypto where present and not through WebCrypto.

The practical consequence is that the same code can take different paths on a server and in a browser. A key type that works well in Node.js may fall back to a JavaScript implementation in the browser, and the constant-time guarantee is conditional on the native implementation being present and constant-time, as the table's footnotes say. If your threat model depends on constant-time execution, the table is the document to read before choosing a curve, and the answer is not the same for every deployment target. The README does not claim constant-time behavior for the pure-JavaScript fallback paths.

Where OpenPGP.js is the wrong tool

The first limitation is environmental. SubtleCrypto is required, and in browsers it is only available in secure contexts. An application served over plain HTTP cannot use the library in the browser at all, regardless of how the code is written. On the Node.js side, the package requires Node v22 or later, so a service pinned to an older LTS release cannot upgrade the library without upgrading the runtime first.

The second is the stream interface change. Code written against earlier OpenPGP.js versions that passes Node's native Readable streams will not work in v6; the README says the library now expects and outputs Node's Web Streams. Node v17 and later include utilities to convert between the two, and the README points at Node's stream documentation for that, but the conversion is work you have to do. Older browsers that lack TransformStream need a polyfill loaded manually, with the global-overwrite caveat described above.

The third is the shape of the tool. OpenPGP.js is a library, not a command-line program and not a key management service. It does not give you a keyring on disk, a trust database, or a user interface. If what you actually need is to encrypt a file from a shell, or to manage keys for people who do not write code, this is the wrong layer. The README's own table of contents is a list of API examples, and there is no CLI section in it.

How it compares with GnuPG and OpenPGP.js v5

The most common alternative is GnuPG, the gpg command-line tool. The difference is architectural rather than a matter of features. GnuPG is a standalone process with its own keyring, configuration directory and trust model; you invoke it, or call it from a library binding, and it manages key storage for you. OpenPGP.js is a dependency inside your application: no process boundary, no separate keyring, and no external binary to install on the host. That also means key storage, passphrase handling and key lifecycle are your application's problem, not the library's.

The second comparison is with OpenPGP.js v5, and the README devotes a section to it under the heading about updating from older versions. The two changes that surface immediately are Web Streams replacing native Node streams and the different meaning of the AEAD setting. Anyone upgrading should read that section before changing a version number, because both changes can pass a build and fail at runtime.

A third point of comparison is the lightweight build. The exports map includes an ./lightweight subpath resolving to dist/lightweight/openpgp.min.mjs in the browser, which suggests a reduced bundle for browser use. The README excerpt does not describe what is excluded from that build, so treat the size and feature trade-off as something to confirm against the project's own documentation before relying on it.

Maintenance, licensing, and upgrade cost

The repository is not archived, and the last push was on 2026-09-22. The most recent release listed is v6.3.1 from 2026-06-04, preceded by v6.3.0 in 2025-12-09 and v6.2.2 in 2025-09-02. That is a steady release cadence on the 6.x line rather than a burst of activity.

On licensing, package.json declares the license as LGPL-3.0+ and the repository's LICENSE file is present at the top level. The README has a License section. LGPL is a copyleft licence with a linking exception relative to the GPL, and the practical question for a closed-source product is how the library is linked and whether the licence's relinking requirements can be met. That is a question for your own legal review; nothing here resolves it, and the repository does not offer an alternative permissive licence.

The upgrade cost is concentrated in two places. Moving to v6 means auditing every place you pass a stream into the library, because native Node Readable streams are no longer accepted. It also means re-checking the AEAD configuration, since the v5 and v6 settings are not equivalent. Beyond those two, the README's platform table is the checklist: Node v22 or later, SubtleCrypto available, Web Streams available. The repository also ships a SECURITY.md and a docs directory, and the README has a Security Audit section, so security reporting has a documented path.

Editorial conclusion

Adopt OpenPGP.js when your application must encrypt, decrypt, sign or verify inside a JavaScript runtime and you control the runtime: Node.js v22 or later, or a recent browser in a secure context. Do not adopt it if you need a command-line tool, if you must support older browsers without loading the Web Streams polyfill yourself, or if you expect AEAD interoperability with other OpenPGP implementations today. Before committing, verify three things in a scratch project: that key generation and a round trip through encryptMessage and decryptMessage work on your target runtime, that your stream inputs are Web Streams rather than Node's native Readable, and whether your recipients can read messages produced with openpgp.config.aeadProtect enabled.

Frequently asked questions

What is OpenPGP.js used for?

It is a JavaScript implementation of the OpenPGP protocol that lets an application encrypt and decrypt data, sign and verify messages, and generate or revoke keys without calling an external program. The README's examples cover password-based encryption, key-based encryption of strings, cleartext signing, detached signatures, and streaming variants of each.

Is OpenPGP.js free to use?

The package is published under the LGPL-3.0+ licence, and the repository carries a LICENSE file plus a License section in the README. Because LGPL is copyleft, how you link the library matters for closed-source products, so that question belongs with your own legal review.

Is OpenPGP.js safe?

The library requires SubtleCrypto and uses native Web Crypto and Node crypto implementations where available, and the curve table marks constant-time behaviour as conditional on the native implementation being present and constant-time. The README also notes that the AEAD feature is behind a flag and that enabling it can break compatibility with other implementations. The repository has a SECURITY.md and a Security Audit section.

What is OpenPGP.js?

It is a JavaScript implementation of the OpenPGP protocol, published on npm as openpgp, that implements RFC 9580, which supersedes RFC 4880 and RFC 4880bis. It ships separate bundles for the browser and for Node.js.

Official sources

  1. License: LGPL-3.0
  2. openpgpjs/openpgpjs on GitHub
  3. Project website
  4. README
  5. Releases
For maintainers

Add this badge to your README

If you maintain this project, the badge below links readers to this analysis and shows its maintenance status from the daily GitHub snapshot. Paste the markdown into your README; add ?metric=license or ?metric=stars to the image URL for a different field.

Add this badge to your README

markdown
[![Hysen Labs](https://hysenlabs.com/badge/openpgpjs-openpgpjs.svg)](https://hysenlabs.com/projects/openpgpjs-openpgpjs)
Community notes

Community notes