NodeRSA
NodeRSA is a library that provides easy-to-use methods for RSA encryption and decryption. It supports Node.js and browser (web) with the same API. Generate RSA key pairs, encrypt and decrypt strings with public and private keys. Ideal for secure data transmission, authentication systems, and any application requiring cryptographic security.
Installation
npm install encrypt-rsa
# OR
yarn add encrypt-rsa
Breaking changes (vs 3.x)
If you are upgrading from 3.x, the 4.x line introduced breaking changes that remain in 5.x:
- Async API – All crypto methods now return Promises (sync → async). You must use
awaitor.then().- Before (3.x):
const encrypted = nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey }); - After (4.x):
const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey });
- Before (3.x):
- Entry points – The package now has separate Node and Web builds.
main/module/typespoint to the Node build; thebrowserfield and conditionalexportspoint to the Web build. If you required a specific path (e.g.encrypt-rsa/build/index.js), update to the new entry points or use the package rootencrypt-rsa. - Buffer methods –
decryptBufferWithRsaPrivateKeynow returnsPromise<Uint8Array>(type). In Node the runtime value is still aBuffer(extendsUint8Array). PreferUint8Arrayin types; avoid relying oninstanceof Bufferin shared code.
See the changelog for the full list of changes.
Node and Web (browser)
One package, two environments:
- Node.js: Uses the built-in
cryptomodule. All crypto methods return Promises (async API). - Browser: Uses the Web Crypto API. Same async API; bundlers resolve the web build via
exports/browserfield.
You use the same import; the correct implementation is chosen at build/runtime:
import NodeRSA from 'encrypt-rsa';
- In Node (or when your bundler targets Node), you get the Node build.
- When your bundler targets the browser, you get the web build.
Browser note: In the browser build, encrypt(privateKey) and decrypt(publicKey) are not supported (Web Crypto does not support that flow) and will throw. Use encryptStringWithRsaPublicKey / decryptStringWithRsaPrivateKey for encryption and decryption.
Same interface (Node and Web)
Both the Node and Web builds expose the same class and method signatures (they implement the shared INodeRSA interface). You write the same code; only the resolved implementation changes:
- Same class:
NodeRSA - Same constructor:
(publicKey?: string, privateKey?: string, modulusLength?: number) - Same methods:
encryptStringWithRsaPublicKey,decryptStringWithRsaPrivateKey,encryptLarge,decryptLarge,encrypt,decrypt,createPrivateAndPublicKeys,encryptBufferWithRsaPublicKey,decryptBufferWithRsaPrivateKey - Same parameter and return types: All crypto methods return
Promise<...>; buffer methods useUint8Array(in Node,BufferextendsUint8Arrayso it works as well).
See the feature parity guide for a feature-by-feature comparison of Node vs Web (including the browser limitation for encrypt/decrypt with private/public key).
Usage
Example: Node.js
// Node (CommonJS or ESM)
import NodeRSA from 'encrypt-rsa';
const nodeRSA = new NodeRSA();
const { publicKey, privateKey } = await nodeRSA.createPrivateAndPublicKeys(2048);
const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({
text: 'Secret message',
publicKey,
});
console.log('Encrypted:', encrypted);
const decrypted = await nodeRSA.decryptStringWithRsaPrivateKey({
text: encrypted,
privateKey,
});
console.log('Decrypted:', decrypted);
Example: Browser (Web)
// Browser (ESM or bundled) – same API
import NodeRSA from 'encrypt-rsa';
const nodeRSA = new NodeRSA();
const { publicKey, privateKey } = await nodeRSA.createPrivateAndPublicKeys(2048);
const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({
text: 'Secret message',
publicKey,
});
console.log('Encrypted:', encrypted);
const decrypted = await nodeRSA.decryptStringWithRsaPrivateKey({
text: encrypted,
privateKey,
});
console.log('Decrypted:', decrypted);
When your bundler targets the browser, it resolves the web build; the code above is unchanged.
Creating an instance
const nodeRSA = new NodeRSA(publicKey?, privateKey?, modulusLength?);
Generating RSA key pairs
All crypto methods return Promises. Use await or .then():
const { publicKey, privateKey } = await nodeRSA.createPrivateAndPublicKeys(modulusLength);
console.log('Public Key:', publicKey);
console.log('Private Key:', privateKey);
Encrypting and decrypting strings
Encrypt with public key, decrypt with private key
const text = 'Hello, World!';
const encryptedString = await nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey });
console.log('Encrypted:', encryptedString);
const decryptedString = await nodeRSA.decryptStringWithRsaPrivateKey({
text: encryptedString,
privateKey,
});
console.log('Decrypted:', decryptedString);
Encrypt any-length data (hybrid) — encryptLarge / decryptLarge
Plain RSA can only encrypt a small amount of data (214 bytes for a 2048-bit RSA-OAEP/SHA-1 key). For anything larger — long tokens, JSON, files — use encryptLarge, which has no size limit:
const longText = 'EAA...a long Facebook token or any large string...';
const encrypted = await nodeRSA.encryptLarge({ text: longText, publicKey });
const decrypted = await nodeRSA.decryptLarge({ text: encrypted, privateKey });
console.log(decrypted === longText); // true
Encrypt with private key, decrypt with public key (Node only)
In the browser build, these methods throw. In Node, they work:
const encryptedString = await nodeRSA.encrypt({ text, privateKey });
console.log('Encrypted with Private Key:', encryptedString);
const decryptedString = await nodeRSA.decrypt({ text: encryptedString, publicKey });
console.log('Decrypted with Public Key:', decryptedString);
Buffer encryption (same interface: Uint8Array)
Both Node and Web use Uint8Array in the method signature. In Node, Buffer extends Uint8Array, so you can pass a Buffer as well. Return type is Promise<Uint8Array> in both environments.
// Node
const buffer = Buffer.from('This is some binary data');
// Browser (or shared code)
const buffer = new TextEncoder().encode('This is some binary data');
const encryptedBuffer = await nodeRSA.encryptBufferWithRsaPublicKey(buffer, publicKey);
const decryptedBuffer = await nodeRSA.decryptBufferWithRsaPrivateKey(
encryptedBuffer,
privateKey
);
// Node: decryptedBuffer is Buffer
// Browser: decryptedBuffer is Uint8Array
console.log(
decryptedBuffer instanceof Uint8Array
? new TextDecoder().decode(decryptedBuffer)
: decryptedBuffer.toString()
);
API
NodeRSA class
Constructor
constructor(publicKey?: string, privateKey?: string, modulusLength?: number)
publicKey: Optional. RSA public key (PEM).privateKey: Optional. RSA private key (PEM).modulusLength: Optional. Modulus length in bits (default 2048).
Methods (all crypto methods return Promise<...>)
| Method | Returns | Description |
|---|---|---|
createPrivateAndPublicKeys(modulusLength?) |
Promise<{ publicKey, privateKey }> |
Generate RSA key pair (PEM). |
encryptStringWithRsaPublicKey(args) |
Promise<string> |
Encrypt with public key. |
decryptStringWithRsaPrivateKey(args) |
Promise<string> |
Decrypt with private key. |
encryptLarge(args) |
Promise<string> |
Encrypt any-length text (hybrid AES-256-GCM + RSA-OAEP). No RSA size limit. |
decryptLarge(args) |
Promise<string> |
Decrypt a value produced by encryptLarge. |
encrypt(args) |
Promise<string> |
Encrypt with private key. Node only. |
decrypt(args) |
Promise<string> |
Decrypt with public key. Node only. |
encryptBufferWithRsaPublicKey(buffer, publicKey?) |
Promise<string> |
Encrypt buffer; returns base64 string. |
decryptBufferWithRsaPrivateKey(encryptedText, privateKey?) |
Promise<Uint8Array> |
Decrypt to buffer (same type in Node and Web). |
Parameter types
parametersOfEncrypt:{ text: string; publicKey?: string }parametersOfDecrypt:{ text: string; privateKey?: string }parametersOfEncryptPrivate:{ text: string; privateKey?: string }parametersOfDecryptPublic:{ text: string; publicKey?: string }returnCreateKeys:{ publicKey: string; privateKey: string }
Algorithm and key format
- RSA-OAEP with SHA-1 for encrypt/decrypt with public/private key (cross-compatible between Node and browser).
- Keys are PEM (SPKI for public, PKCS#8 for private). Keys generated on one side work on the other.
Use cases
Secure data transmission
// Sender
const encryptedMessage = await nodeRSA.encryptStringWithRsaPublicKey({
text: 'Sensitive data',
publicKey: recipientPublicKey,
});
// Send encryptedMessage to the recipient
// Recipient
const decryptedMessage = await nodeRSA.decryptStringWithRsaPrivateKey({
text: encryptedMessage,
privateKey: recipientPrivateKey,
});
console.log('Decrypted Message:', decryptedMessage);
Authentication
const encryptedCredentials = await nodeRSA.encryptStringWithRsaPublicKey({
text: 'username:password',
publicKey: serverPublicKey,
});
const decryptedCredentials = await nodeRSA.decryptStringWithRsaPrivateKey({
text: encryptedCredentials,
privateKey: serverPrivateKey,
});
console.log('Decrypted Credentials:', decryptedCredentials);
Getting started with examples
We provide practical examples for both Node.js and browser environments to help you get started quickly:
- Node.js example – Command-line demo showing key generation, encryption, and decryption
- Browser example – Interactive web interface with a UI for testing encryption/decryption
- Examples README – Detailed guide for running and understanding the examples
Build the package before running local examples:
npm run build
To run the Node.js example:
node examples/node-basic.js
To view the browser example, serve the repository over localhost and open examples/browser-basic.html:
python3 -m http.server 8080
Then open http://localhost:8080/examples/browser-basic.html. Web Crypto requires a secure context such as HTTPS or localhost.
Data size limitations
RSA encryption with OAEP padding has inherent size limitations based on the key size:
Maximum message sizes
| Key Size | Max Bytes |
|---|---|
| 2048-bit | 214 bytes |
| 4096-bit | 470 bytes |
Why the limitation?
RSA with OAEP padding requires overhead:
- OAEP padding scheme:
2 * hash_size + 2bytes - With SHA-1 (20 bytes):
2 * 20 + 2 = 42bytes overhead - Formula:
max_bytes = key_size_bytes - 42
Solutions for larger data
For data larger than the RSA limit, use the built-in encryptLarge / decryptLarge methods. They use hybrid encryption — the data is encrypted with a one-time AES-256-GCM key, and only that small key is wrapped with RSA-OAEP — so there is no size limit:
const text = '...a Facebook token or any long string...';
// Encrypt with the public key, decrypt with the private key.
const encrypted = await nodeRSA.encryptLarge({ text, publicKey });
const decrypted = await nodeRSA.decryptLarge({ text: encrypted, privateKey });
console.log(decrypted === text); // true
The output is a single base64 string (encKey:iv:tag:ciphertext) you can store in one field. It is interoperable between the Node and Web builds (same keypair). AES-GCM is authenticated, so tampering with the payload causes decryption to fail.
Under the hood this is the standard hybrid scheme:
- Generate a random AES-256 key and IV
- Encrypt the data with AES-256-GCM (no size limit)
- Wrap the AES key with RSA-OAEP (fits well within the RSA limit)
- Package
encKey:iv:tag:ciphertextinto one string
Error handling & troubleshooting
Common errors and solutions
| Error | Cause | Solution |
|---|---|---|
"data too large for key size" (ERR_OSSL_RSA_DATA_TOO_LARGE_FOR_KEY_SIZE) |
Message exceeds RSA capacity (214 bytes for 2048-bit RSA-OAEP/SHA-1 keys) | Use encryptLarge / decryptLarge (built-in hybrid encryption — no size limit), or a larger key |
| "Invalid public key format" | PEM key is malformed or wrong type | Verify key starts with -----BEGIN PUBLIC KEY----- |
| "Invalid private key format" | PEM key is malformed or wrong type | Verify key starts with -----BEGIN PRIVATE KEY----- |
| "Decryption failed" | Wrong private key or corrupted ciphertext | Ensure the correct private key matches the public key used for encryption |
| "Web Crypto API is not available" | Browser doesn't support crypto.subtle or not using HTTPS/localhost | Use Chrome 37+, Firefox 34+, Safari 11+, or Edge 79+; use HTTPS in production |
Key validation helpers
Use the provided validation functions to check keys before encryption:
import { isValidPEMPublicKey, isValidPEMPrivateKey, isValidPEMKey } from 'encrypt-rsa';
if (!isValidPEMPublicKey(key)) {
console.error('Invalid public key format');
}
if (!isValidPEMPrivateKey(key)) {
console.error('Invalid private key format');
}
// Check if key is either public or private
if (!isValidPEMKey(key)) {
console.error('Invalid key format');
}
Debugging tips
Check key format: Ensure PEM keys have proper headers/footers
-----BEGIN PUBLIC KEY----- [base64 content] -----END PUBLIC KEY-----Verify key pair matching: The private key must match the public key
const keys = await nodeRSA.createPrivateAndPublicKeys(2048); // keys.publicKey and keys.privateKey are a matching pairTest with small messages: Start with short messages to isolate size issues
const short = 'test'; // Start here const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({ text: short, publicKey });Use try-catch blocks: Always wrap crypto operations
try { const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey }); } catch (error) { console.error('Encryption failed:', error.message); }
Testing
The project includes tests for both the Node and web builds:
- Node tests (
tests/functionality.node.spec.ts): Run against the Node build; cover all methods including encrypt/decrypt with private/public key and buffer operations. - Web tests (
tests/functionality.web.spec.ts): Run against the web build; requirecrypto.subtle(Node 19+ or a browser). Skipped automatically when Web Crypto is not available.
npm test
Both suites run with npm test. Web tests are skipped when crypto.subtle is not available (e.g. Node below 19).
Documentation
API documentation is generated with Compodoc. To generate the docs (from the Node build source):
npm run docs
Generated files are written to the docs/ folder. To serve them locally:
npm run docs:serve
The docs reflect the NodeRSA class and its async API (Node and web share the same interface).
Releasing
- Changelog from commits: Run
npm run changelogto update the changelog from conventional commits since the last tag (feat:,fix:,BREAKING CHANGE:, etc.). - Full release: Run
npm run release -- --release-as major|minor|patchto bump version, update the changelog, commit, and tag. Push tomasterto trigger the publish workflow (see the publish workflow).
Publish via GitHub Actions (on merge to master):
- Secret: In the repo go to Settings → Secrets and variables → Actions and add NPM_TOKEN (npm automation token with “Publish” permission).
- Trigger: Merging (or pushing) to the master branch runs the workflow: install → test → build → publish. The JS-DevTools/npm-publish action publishes only if the version in
package.jsonis greater than the latest on npm; otherwise the job succeeds but skips publishing. - Optional: To make installs reproducible in CI, commit
package-lock.json(remove it from.gitignore) and change the workflow step fromnpm installtonpm ci.
Contribution
- Fork the repository on GitHub.
- Clone your fork:
git clone git@github.com:miladezzat/encrypt-rsa.git - Create a branch:
git checkout -b feature/your-feature-name - Make your changes, then commit with a clear message.
- Push to your fork and open a pull request with a description of your changes.
Code of conduct
This project is released with a Contributor Code of Conduct. By participating you agree to abide by its terms.
Reporting issues
Please report issues via the GitHub issue tracker. Include details about the problem and your environment (OS, Node.js version, bundler, etc.).