This is a little module use to encrypt and decrypt strings with RSA keys (public and private keys)
npm install encrypt-rsaNodeRSA 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.
``bash`
npm install encrypt-rsaOR
yarn add encrypt-rsa
If you are upgrading from 3.x, this release includes breaking changes. You should bump to 4.0.0 when publishing:
1. Async API – All crypto methods now return Promises (sync → async). You must use await or .then().const encrypted = nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey });
- Before (3.x): const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey });
- After (4.x): main
2. Entry points – The package now has separate Node and Web builds. /module/types point to the Node build; the browser field and conditional exports point 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 root encrypt-rsa.decryptBufferWithRsaPrivateKey
3. Buffer methods – now returns Promise (type). In Node the runtime value is still a Buffer (extends Uint8Array). Prefer Uint8Array in types; avoid relying on instanceof Buffer in shared code.
See CHANGELOG.md for the full list of changes.
One package, two environments:
- Node.js: Uses the built-in crypto module. All crypto methods return Promises (async API).exports
- Browser: Uses the Web Crypto API. Same async API; bundlers resolve the web build via / browser field.
You use the same import; the correct implementation is chosen at build/runtime:
`ts`
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.
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(publicKey?: string, privateKey?: string, modulusLength?: number)
- Same constructor: encryptStringWithRsaPublicKey
- Same methods: , decryptStringWithRsaPrivateKey, encrypt, decrypt, createPrivateAndPublicKeys, encryptBufferWithRsaPublicKey, decryptBufferWithRsaPrivateKeyPromise<...>
- Same parameter and return types: All crypto methods return ; buffer methods use Uint8Array (in Node, Buffer extends Uint8Array so it works as well).
See docs/FEATURE_PARITY.md for a feature-by-feature comparison of Node vs Web (including the browser limitation for encrypt/decrypt with private/public key).
`ts
// 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);
`
`ts
// 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.
`ts`
const nodeRSA = new NodeRSA(publicKey?, privateKey?, modulusLength?);
All crypto methods return Promises. Use await or .then():
`ts`
const { publicKey, privateKey } = await nodeRSA.createPrivateAndPublicKeys(modulusLength);
console.log('Public Key:', publicKey);
console.log('Private Key:', privateKey);
#### Encrypt with public key, decrypt with private key
`ts
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 with private key, decrypt with public key (Node only)
In the browser build, these methods throw. In Node, they work:
`ts
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);
`
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 in both environments.
`ts
// 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()
);
`
#### Constructor
`ts`
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 | Encrypt with public key. |decryptStringWithRsaPrivateKey(args)
| | Promise | Decrypt with private key. |encrypt(args)
| | Promise | Encrypt with private key. Node only. |decrypt(args)
| | Promise | Decrypt with public key. Node only. |encryptBufferWithRsaPublicKey(buffer, publicKey?)
| | Promise | Encrypt buffer; returns base64 string. |decryptBufferWithRsaPrivateKey(encryptedText, privateKey?)
| | Promise | 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 }
- 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.
`ts
// 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);
`
`ts
const encryptedCredentials = await nodeRSA.encryptStringWithRsaPublicKey({
text: 'username:password',
publicKey: serverPublicKey,
});
const decryptedCredentials = await nodeRSA.decryptStringWithRsaPrivateKey({
text: encryptedCredentials,
privateKey: serverPrivateKey,
});
console.log('Decrypted Credentials:', decryptedCredentials);
`
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
To run the Node.js example:
`bash`
node examples/node-basic.js
To view the browser example, open examples/browser-basic.html in your web browser.
RSA encryption with OAEP padding has inherent size limitations based on the key size:
| Key Size | Max Bytes |
|----------|-----------|
| 2048-bit | ~190 bytes |
| 4096-bit | ~446 bytes |
RSA with OAEP padding requires overhead:
- OAEP padding scheme: 2 * hash_size + 2 bytes
- With SHA-1 (20 bytes): 2 * 20 + 2 = 42 bytes overhead
- Formula: max_bytes = (key_size_bytes - 42 - 2) ≈ key_size_bytes / 8 - 46
For encrypting larger messages, use hybrid encryption:
1. Generate a random symmetric key (e.g., 32 bytes for AES-256)
2. Encrypt the symmetric key with RSA (fits in ~190 bytes)
3. Encrypt your large data with the symmetric key (no size limit)
4. Send both encrypted key and encrypted data to the recipient
Example libraries:
- crypto-js – Symmetric encryption with AEStweetnacl-js
- – Modern cryptography with libsodium
- TweetNaCl.js – XChaCha20-Poly1305
| Error | Cause | Solution |
|-------|-------|----------|
| "data too large for key size" | Message exceeds RSA capacity (~190 bytes for 2048-bit keys) | Use smaller message, larger key, or hybrid encryption |
| "Invalid public key format" | PEM key is malformed or wrong type | Verify key starts with -----BEGIN PUBLIC KEY----- |-----BEGIN PRIVATE KEY-----
| "Invalid private key format" | PEM key is malformed or wrong type | Verify key starts with |
| "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 |
Use the provided validation functions to check keys before encryption:
`ts
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');
}
`
1. Check key format: Ensure PEM keys have proper headers/footers
``
-----BEGIN PUBLIC KEY-----
[base64 content]
-----END PUBLIC KEY-----
2. Verify key pair matching: The private key must match the public key
`ts`
const keys = await nodeRSA.createPrivateAndPublicKeys(2048);
// keys.publicKey and keys.privateKey are a matching pair
3. Test with small messages: Start with short messages to isolate size issues
`ts`
const short = 'test'; // Start here
const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({ text: short, publicKey });
4. Use try-catch blocks: Always wrap crypto operations
`ts`
try {
const encrypted = await nodeRSA.encryptStringWithRsaPublicKey({ text, publicKey });
} catch (error) {
console.error('Encryption failed:', error.message);
}
The project includes tests for both the Node and web builds:
- Node tests (tests/functionalty.node.spec.ts): Run against the Node build; cover all methods including encrypt/decrypt with private/public key and buffer operations.tests/functionalty.web.spec.ts
- Web tests (): Run against the web build; require crypto.subtle (Node 19+ or a browser). Skipped automatically when Web Crypto is not available.
`bash`
npm test
Both suites run with npm test. Web tests are skipped when crypto.subtle is not available (e.g. Node below 19).
API documentation is generated with Compodoc. To generate the docs (from the Node build source):
`bash`
npm run docs
Generated files are written to the docs/ folder. To serve them locally:
`bash`
npm run docs:serve
The docs reflect the NodeRSA class and its async API (Node and web share the same interface).
- Changelog from commits: Run npm run changelog to update CHANGELOG.md from conventional commits since the last tag (feat:, fix:, BREAKING CHANGE:, etc.).npm run release -- --release-as major|minor|patch
- Full release: Run to bump version, update the changelog, commit, and tag. Push to master to trigger the publish workflow (see .github/workflows/publish.yml).
Publish via GitHub Actions (on merge to master):
1. Secret: In the repo go to Settings → Secrets and variables → Actions and add NPM_TOKEN (npm automation token with “Publish” permission).
2. 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.json is greater than the latest on npm; otherwise the job succeeds but skips publishing.package-lock.json
3. Optional: To make installs reproducible in CI, commit (remove it from .gitignore) and change the workflow step from npm install to npm ci.
1. Fork the repository on GitHub.
2. Clone your fork: git clone git@github.com:miladezzat/encrypt-rsa.gitgit checkout -b feature/your-feature-name`
3. Create a branch:
4. Make your changes, then commit with a clear message.
5. Push to your fork and open a pull request with a description of your changes.
This project is released with a Contributor Code of Conduct. By participating you agree to abide by its terms.
Please report issues via the GitHub issue tracker. Include details about the problem and your environment (OS, Node.js version, bundler, etc.).