JSEncrypt's 1024-bit default and the RSA block it cannot get past
A tiny (18.5 kB gzip), zero-dependency, Javascript library to perform OpenSSL RSA Encryption, Decryption, and Key Generation.
At a glance
- What is it?
- JSEncrypt is a zero-dependency JavaScript library that runs RSA encryption, decryption and key generation in Node.js and the browser from one 18.5 kB bundle. It is small enough to ship anywhere and narrow enough that a single encrypt call will not carry a real payload.
- Who is it for?
- JSEncrypt is the right choice when you need RSA somewhere a native OpenSSL binding will not go: a browser bundle, a web worker, a small Node service that wants no transitive dependencies. Its 18.5 kB footprint, its empty dependencies block and its OpenSSL compatible PEM handling are real advantages inside that narrow case, and the README gives you a working install and first round trip in about ten lines.
- Can I use it commercially?
- Check first. The repository uses a licence we do not classify automatically, so read its LICENSE file before any commercial use.
- Is it still maintained?
- Activity is slowing. The repository last received commits 7 months 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 November 14, 2026, and from our analysis. They are not legal advice.
Star history
- travist/jsencrypt6,806 ★ · +1
| Repository | 2026-09-23 | 2026-11-14 |
|---|---|---|
| travist/jsencrypt | 6.8k | 6.8k |
1 Stars in the last 52 days, measured from the daily GitHub snapshot. Compare with other projects
Editorial analysis
An empty dependencies block is the whole design statement
JSEncrypt does OpenSSL compatible RSA encryption, decryption and key generation in Node.js and in the browser, from one class, and the `dependencies` object in package.json is literally empty. Nothing is pulled in at install time, so there is no transitive tree to audit and no version drift between your app and a cryptography package you did not pick.
Underneath, the arithmetic comes from Tom Wu's jsbn, and the README is explicit that the core algorithms are used without modification. The big number work, the modular exponentiation and the primality checks, is code written for a different library and wrapped rather than rewritten. The version declared in package.json is 3.5.4.
The shipped surface is declared as narrowly as the dependency list. The `files` array contains exactly `bin` and `lib`, so everything under `src`, `test`, `docs` and `assets` stays out of the npm tarball. TypeScript, mocha, chai, webpack, ts-node and fs-jetpack all sit in `devDependencies`, which is where a build chain belongs.
The practical consequence is that nothing else arrives behind the 18.5 kB gzipped bundle. For a script tag in a page that already loads a framework, or a serverless function with a cold start budget, that is the argument for the library.
require, import, and the CDN global resolve to three different files
Getting the constructor is where first contact usually goes wrong, because the package hands out three different shapes depending on how you ask for it.
The `exports` map is explicit. `require` resolves to `./bin/jsencrypt.min.js`, the webpack bundle. `import` and `browser` both resolve to `./lib/index.js`, the compiled output, and `types` points at `lib/index.d.ts`. The top level fields agree with it: `main` is `bin/jsencrypt.min.js`, while `module` and `browser` are both `lib/index.js`.
A CommonJS reader therefore gets a minified bundle whose exported value is the constructor itself, and an ES module reader gets a namespace object with a named export. The README documents `const JSEncrypt = require('jsencrypt');` for the first case and `import { JSEncrypt } from 'jsencrypt';` for the second. Reaching for the constructor on the wrong one of those two objects is what produces a not a constructor error rather than a missing function, because the namespace object is there and the constructor is one property inside it.
The third shape is the browser global. The CDN tag in the README points at `https://cdn.jsdelivr.net/npm/jsencrypt@latest/bin/jsencrypt.min.js`, the same minified bundle that `require` targets, and the browser example then calls `new JSEncrypt()` with no import at all. That is the shape most likely to look undefined, because nothing named `JSEncrypt` exists until that script has run and attached its global. Note what `@latest` means for a package with no GitHub releases: the tag always resolves to whatever the registry dist-tag says, and the repository gives you no tagged history to pin a version against.
openssl genrsa, then setPrivateKey and decrypt
Installing is one command. In an existing project:
npm install jsencryptyarn takes the same package through `yarn add jsencrypt`. To skip the bundler entirely in a plain HTML page, one script tag is enough:
<script src="https://cdn.jsdelivr.net/npm/jsencrypt@latest/bin/jsencrypt.min.js"></script>The README points key generation at OpenSSL for two reasons it states plainly: OpenSSL draws on cryptographically secure random sources with better entropy, and its implementations are audited. Generate the pair before you touch the library.
# Generate a 2048-bit private key
openssl genrsa -out private.pem 2048
# Extract the public key
openssl rsa -pubout -in private.pem -out public.pemThe public half is the one you encrypt with and the one you can hand to a client. The private half decrypts. Feed the PEM strings to a fresh instance and you have a full round trip:
const JSEncrypt = require('jsencrypt');
const crypt = new JSEncrypt();
crypt.setPublicKey(publicKeyString);
const encrypted = crypt.encrypt('secret message');
crypt.setPrivateKey(privateKeyString);
const decrypted = crypt.decrypt(encrypted);The README walks both directions on a single instance, which is fine for a demo but worth splitting in real code: `setPublicKey` and `setPrivateKey` write to the same object, so holding both halves in one process means the private key stays reachable on that object for as long as it lives. Two instances cost one extra line.
getPrivateKey defaults to 1024 bits and borrows the runtime's random source
JSEncrypt can also make keys in JavaScript instead of reading them from OpenSSL, and the two paths are not equivalent. `getPrivateKey()` and `getPublicKey()` return a pair with nothing to paste in, which makes them useful for tests, demos and anything that only needs a key that will never leave the process. The size comes from a constructor option named `default_key_size`, and the README is plain that the default is 1024 bits.
The list it walks through runs 512, 1024, 2048 and 4096, and the library accepts each one. The README attaches a verdict to each: 512 for testing, 1024 as the basic default, 2048 as the recommended production minimum, 4096 for higher security at a slower cost. Nothing in the documented surface refuses a 512-bit key. The security boundary here is a warning in prose, so a demo configuration and a production configuration look identical at the call site.
The part worth slowing down on is entropy. The README states that JavaScript key generation draws on the browser or Node.js random number generators, and that these may carry less entropy than dedicated cryptographic tools. That is a real constraint inside a container, an embedded runtime or anything else with a thin random source, and the library has no way to detect it. If you take the JavaScript path, treat the resulting key as disposable unless you have read your runtime's own random source documentation.
There is a second-order effect too. Anything encrypted with a browser generated key has to be decrypted with it, so the weak key and the ciphertext sit inside the same trust boundary. The cleaner shape the README points at is the simple one: generate once with OpenSSL, distribute the public half, and keep `getKey` out of production code.
getKey(callback) for larger keys, and what it does not promise
Key generation is the one operation here that can take long enough to be worth thinking about. `getKey` takes a callback and invokes it once the key pair exists, and the README recommends that form over the synchronous calls for larger keys. The callback reads the keys off the instance the same way the synchronous version does:
const crypt = new JSEncrypt({ default_key_size: 2048 });
crypt.getKey(() => {
const privateKey = crypt.getPrivateKey();
const publicKey = crypt.getPublicKey();
});That is the asynchronous form the README shows.
The silence matters as much as the API. The one number the project actually commits to is the bundle size, 18.5 kB gzipped, which says nothing about how long the arithmetic takes.
The encrypt path stops at one RSA block
Here is the constraint that decides whether this library fits your use case at all. RSA encrypts a block smaller than its modulus, and nothing in JSEncrypt changes that. A 2048-bit key has a 256-byte modulus, and padding takes a slice of it, so a single `encrypt` call handles less than that.
That is enough for a token, a short secret, a password reset code. It is nowhere near enough for a file, a JSON document of any size, or anything you would naturally call a message. If you need to go past one block, splitting and reassembly become a format you have to keep compatible forever.
The limit reappears on the way back in. `decrypt` consumes one ciphertext, so a payload you split has to be rejoined before it can be decrypted, and any length prefix or index you add to make that work is now part of your own envelope. There is no framing in the library to lean on.
The other gap is named just as plainly. JSEncrypt does RSA, and the README introduces digital signatures with a SHA-256 signing call named `signSha256` and a verification step with the public key. For bulk data the normal shape is a symmetric cipher for the payload with RSA protecting that cipher's key, and JSEncrypt supplies only the second half of it.
A Jekyll docs site, a 3.5.4 version, and no tagged releases
The documentation is a Jekyll site, not a package. The repository root carries a Gemfile, a Gemfile.lock, `_config.yml` and `_config.build.yml`, an `assets` directory and `index.md`. The published site sits at travistidwell.com/jsencrypt, with a separate interactive demo page at /jsencrypt/demo. That matters when you go looking for the docs after installing: the npm tarball contains only `bin` and `lib`, so none of it travels with the module.
Versioning is the other loose end. package.json declares 3.5.4. The repository has no GitHub releases, so there is no changelog, no upgrade note and no record of what changed between versions. Both `package-lock.json` and `yarn.lock` sit at the root, so the project is developed against two package managers while publishing to one. When you upgrade, the only signal you get is a version number moving, with no migration guide on the docs site to check it against.
The last commit on the default branch, master, landed on 2026-04-08. A library this size can sit quiet for months without anything breaking, and the surface here has not visibly churned. The same quiet also means an issue you file about a key size or a bundle problem may go unanswered, which is worth factoring in if the library is carrying something you depend on.
Two licence details are easy to miss. The repository carries a LICENSE.txt and the README links the MIT licence text, but the GitHub repository metadata reports NOASSERTION, so any tooling that reads that metadata instead of the file will not resolve it. And since the README states the code is built on Tom Wu's jsbn without modifying the core algorithms, an attribution review has to cover jsbn as well as LICENSE.txt.
Editorial conclusion
JSEncrypt is the right choice when you need RSA somewhere a native OpenSSL binding will not go: a browser bundle, a web worker, a small Node service that wants no transitive dependencies. Its 18.5 kB footprint, its empty dependencies block and its OpenSSL compatible PEM handling are real advantages inside that narrow case, and the README gives you a working install and first round trip in about ten lines. It is the wrong choice for anything larger than a short secret, because one RSA block is all the encrypt path will take, and for anyone who needs a symmetric cipher or a key derivation function, since the README presents JSEncrypt as an RSA library. Before you adopt it, check two things: whether your runtime's random source is good enough for getKey, and whether you can pin a version you have actually read, given that the repository publishes no releases to compare against.
Frequently asked questions
How do I use JSEncrypt in Node.js?
Run `npm install jsencrypt`, then `require('jsencrypt')`, which resolves to the prebuilt bundle at `bin/jsencrypt.min.js` rather than the `lib/index.js` file an `import` hands you. Create a `JSEncrypt` instance, call `setPublicKey` or `setPrivateKey` with a PEM string generated by OpenSSL, then `encrypt` or `decrypt`.
Why do I get a ReferenceError saying jsencrypt is not defined?
The browser global only exists after the script tag has run and attached it: the README's CDN snippet loads `https://cdn.jsdelivr.net/npm/jsencrypt@latest/bin/jsencrypt.min.js` and then calls `new JSEncrypt()` with no import. In a bundler setup, drop the script tag and use `import { JSEncrypt } from 'jsencrypt';` instead, which is the ES module path the package exposes.
Why does JSEncrypt say it is not a constructor?
The package ships two shapes and they are not interchangeable. `require('jsencrypt')` gives you the constructor as the module value itself, while `import { JSEncrypt } from 'jsencrypt'` gives you a module namespace object with the constructor on a named export. Calling `new` on the namespace object, or calling the named export without `new`, lands you on this error.
Official sources
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.
[](https://hysenlabs.com/projects/travist-jsencrypt)Star history chart for your README
[](https://hysenlabs.com/projects/travist-jsencrypt)Was this useful?