Overview of JSEncrypt
masterjsbn library. Use JSEncrypt to integrate RSA cryptographic operations into your JavaScript applications.repository·master·Indexed 27 days ago
https://github.com/travist/jsencryptA lightweight, zero-dependency JavaScript library for OpenSSL RSA encryption, decryption, and key generation. Compatible with both Browser and Node.js environments, it supports synchronous and asynchronous operations, digital signatures with various hash functions (md2, md5, sha1, sha224, sha256, sha384, sha512, ripemd160), and OAEP padding with SHA-256.
jsbn library. Use JSEncrypt to integrate RSA cryptographic operations into your JavaScript applications.When handling encryption errors, avoid logging sensitive information such as the plaintext data or the private keys. Log metadata like the operation type, timestamp, and whether the key was loaded, but keep the actual secrets out of the logs.
class SecureEncryption {
constructor(config) {
this.crypt = new JSEncrypt(config);
this.logger = config.logger || console;
}
encrypt(data) {
try {
const result = this.crypt.encrypt(data);
if (!result) {
// Log error without exposing sensitive data
this.logger.error('Encryption failed', {
timestamp: new Date().toISOString(),
operation: 'encrypt',
keyLoaded: !!this.crypt.getKey()
});
throw new Error('Encryption operation failed');
}
return result;
} catch (error) {
// Don't log the actual data being encrypted
this.logger.error('Encryption error', {
error: error.message,
stack: error.stack,
timestamp: new Date().toISOString()
});
throw error;
}
}
decrypt(ciphertext) {
try {
const result = this.crypt.decrypt(ciphertext);
if (result === false) {
this.logger.warn('Decryption failed', {
timestamp: new Date().toISOString(),
operation: 'decrypt',
ciphertextLength: ciphertext.length
});
throw new Error('Decryption failed');
}
return result;
} catch (error) {
// Log error without exposing plaintext or keys
this.logger.error('Decryption error', {
error: error.message,
timestamp: new Date().toISOString()
});
throw error;
}
}
}For production applications, it is recommended to generate RSA keys using OpenSSL to ensure maximum security and entropy.
Use the following commands to generate a 2048-bit private key and extract the corresponding public key:
# Generate a 2048-bit private key
openssl genrsa -out private.pem 2048
# Extract the public key
openssl rsa -pubout -in private.pem -out public.pemopenssl genrsa -out private.pem 2048
openssl rsa -pubout -in private.pem -out public.pemTo use JSEncrypt directly in a browser without a build step, include the minified script via JSDelivr.
<script src="https://cdn.jsdelivr.net/npm/jsencrypt/bin/jsencrypt.min.js"></script>
<script>
const crypt = new JSEncrypt();
// ... use crypt instance
</script>Use the JSEncrypt instance to manage keys and perform cryptographic operations. You can either generate a new key pair using getPrivateKey() or load existing PEM-formatted keys (compatible with OpenSSL) using setPrivateKey() and setPublicKey().
import { JSEncrypt } from 'jsencrypt';
const crypt = new JSEncrypt();
// Option A: Generate a new key pair
const privateKey = crypt.getPrivateKey();
crypt.setPrivateKey(privateKey);
// Option B: Load existing OpenSSL PEM keys
// crypt.setPrivateKey(`-----BEGIN RSA PRIVATE KEY-----\n...\n-----END RSA PRIVATE KEY-----`);
// crypt.setPublicKey(`-----BEGIN PUBLIC KEY-----\n...\n-----END PUBLIC KEY-----`);
// Encrypt data
const encrypted = crypt.encrypt('Hello World!');
// Decrypt data
const decrypted = crypt.decrypt(encrypted);For production environments, wrap JSEncrypt in a layer that adds security metadata. This includes:
-----BEGIN PUBLIC KEY----- or -----BEGIN RSA PRIVATE KEY-----) and meet minimum length requirements (e.g., 2048 bits).timestamp and a cryptographically secure nonce within the encrypted payload to prevent replay attacks and verify freshness.maxAge threshold to ensure the data is not too old.window.crypto.getRandomValues in browsers or crypto.randomBytes in Node.js for generating nonces.Security Checklist:
class ProductionEncryption {
constructor(publicKey, privateKey) {
const validation = SecurityUtils.validateKeyStrength(publicKey, privateKey);
if (!validation.isValid) {
console.warn('Key validation warnings:', validation.warnings);
}
this.crypt = new JSEncrypt();
this.publicKey = publicKey;
this.privateKey = privateKey;
}
encryptSecure(data, metadata = {}) {
try {
this.crypt.setPublicKey(this.publicKey);
const payload = {
data: data,
timestamp: Date.now(),
nonce: SecurityUtils.generateSecureRandom(16),
...metadata
};
const serialized = JSON.stringify(payload);
const encrypted = this.crypt.encrypt(serialized);
if (!encrypted) {
throw new Error('Encryption failed');
}
return {
success: true,
encrypted: encrypted,
timestamp: payload.timestamp
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
decryptSecure(encryptedData, maxAge = 3600000) { // 1 hour default
try {
this.crypt.setPrivateKey(this.privateKey);
const decrypted = this.crypt.decrypt(encryptedData);
if (!decrypted) {
throw new Error('Decryption failed');
}
const payload = JSON.parse(decrypted);
const age = Date.now() - payload.timestamp;
if (age > maxAge) {
throw new Error('Data too old');
}
if (!payload.nonce) {
throw new Error('Invalid payload format');
}
return {
success: true,
data: payload.data,
timestamp: payload.timestamp,
age: age
};
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}You can build an API token management system by combining signSha256() for token generation and verifySha256() for validation. A typical workflow involves:
class APITokenManager {
constructor(privateKey, publicKey) {
this.crypt = new JSEncrypt();
this.privateKey = privateKey;
this.publicKey = publicKey;
}
// Generate a signed API token
generateToken(userId, permissions, expiresIn = 3600) {
const tokenData = {
userId: userId,
permissions: permissions,
issuedAt: Math.floor(Date.now() / 1000),
expiresAt: Math.floor(Date.now() / 1000) + expiresIn
};
const payload = JSON.stringify(tokenData);
this.crypt.setPrivateKey(this.privateKey);
const signature = this.crypt.signSha256(payload);
// Return token as base64 encoded payload + signature
const token = Buffer.from(JSON.stringify({
payload: payload,
signature: signature
})).toString('base64');
return token;
}
// Validate an API token
validateToken(token) {
try {
// Decode the token
const decoded = JSON.parse(Buffer.from(token, 'base64').toString());
const { payload, signature } = decoded;
// Verify signature using convenience method
this.crypt.setPublicKey(this.publicKey);
const isValidSignature = this.crypt.verifySha256(payload, signature);
if (!isValidSignature) {
return { valid: false, reason: 'Invalid signature' };
}
// Check expiration
const tokenData = JSON.parse(payload);
const now = Math.floor(Date.now() / 1000);
if (tokenData.expiresAt < now) {
return { valid: false, reason: 'Token expired' };
}
return {
valid: true,
data: tokenData
};
} catch (error) {
return { valid: false, reason: 'Invalid token format' };
}
}
}Use require to import JSEncrypt in Node.js environments using CommonJS modules.
const JSEncrypt = require('jsencrypt');
const crypt = new JSEncrypt();For production environments, avoid using JavaScript-based key generation. Instead, use OpenSSL to generate keys with a minimum size of 2048 bits (4096 bits is preferred). You can also generate password-protected keys for an additional layer of security.
# Secure key generation with OpenSSL
openssl genrsa -out private.pem 4096
# Generate password-protected keys for additional security
openssl genrsa -aes256 -out private_protected.pem 4096RSA encryption has strict data length limits based on the key size. When implementing encryption, validate that the input is a string and that its length does not exceed the maximum allowed bytes (calculated as (keySize / 8) - 11 to account for PKCS#1 padding overhead).
function secureEncrypt(data, crypt) {
// Validate input
if (typeof data !== 'string') {
throw new Error('Data must be a string');
}
// Check data length (RSA has size limits)
const keySize = crypt.getKey().n.bitLength();
const maxLength = Math.floor(keySize / 8) - 11; // PKCS#1 padding overhead
if (data.length > maxLength) {
throw new Error(`Data too long. Maximum length: ${maxLength} bytes`);
}
// Validate key is loaded
if (!crypt.getKey()) {
throw new Error('No encryption key loaded');
}
return crypt.encrypt(data);
}You can install JSEncrypt using npm or yarn, or include it directly in your HTML via a CDN.
npm install jsencryptyarn add jsencryptInclude the following script tag in your HTML:
<script src="https://cdn.jsdelivr.net/npm/jsencrypt@latest/bin/jsencrypt.min.js"></script>Establish secure communication by encrypting data on the client side using the server's public key and decrypting it on the server side using the server's private key. The client can also decrypt server responses using its own private key.
// Client-side encryption
class SecureAPIClient {
constructor(serverPublicKey, clientPrivateKey) {
this.serverPublicKey = serverPublicKey;
this.clientPrivateKey = clientPrivateKey;
this.encryptor = new JSEncrypt();
this.decryptor = new JSEncrypt();
this.encryptor.setPublicKey(serverPublicKey);
this.decryptor.setPrivateKey(clientPrivateKey);
}
async secureRequest(endpoint, data) {
// Encrypt the request data
const encryptedData = this.encryptor.encrypt(JSON.stringify(data));
const response = await fetch(endpoint, {
method: 'POST',
headers: {
'Content-Type': 'application/json',
'X-Encrypted': 'true'
},
body: JSON.stringify({ encrypted: encryptedData })
});
const result = await response.json();
// Decrypt the response
if (result.encrypted) {
const decryptedResponse = this.decryptor.decrypt(result.encrypted);
return JSON.parse(decryptedResponse);
}
return result;
}
}
// Usage
const client = new SecureAPIClient(serverPublicKey, clientPrivateKey);
const userData = await client.secureRequest('/api/user/profile', {
userId: 12345,
fields: ['name', 'email', 'preferences']
});