jsencrypt

repository·master·Indexed 27 days ago

https://github.com/travist/jsencrypt

A 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.

Tokens
33.5K
Snippets
54
Records
91
Agent score
86%

What's inside jsencrypt

  1. Overview of JSEncrypt

    master
    JSEncrypt is a JavaScript library that provides a simple interface for RSA encryption and decryption. It supports OpenSSL-compatible key formats and is built on top of the jsbn library. Use JSEncrypt to integrate RSA cryptographic operations into your JavaScript applications.
  2. Secure error handling and logging

    master

    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;
            }
        }
    }
  3. Generate RSA keys using OpenSSL (Recommended)

    master

    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.pem
    openssl genrsa -out private.pem 2048
    openssl rsa -pubout -in private.pem -out public.pem
  4. Use JSEncrypt in the Browser via CDN

    master

    To 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>
  5. Perform RSA Encryption and Decryption

    master

    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);
  6. Implement secure production encryption with integrity checks

    master

    For production environments, wrap JSEncrypt in a layer that adds security metadata. This includes:

    • Key Strength Validation: Ensure keys use standard headers (-----BEGIN PUBLIC KEY----- or -----BEGIN RSA PRIVATE KEY-----) and meet minimum length requirements (e.g., 2048 bits).
    • Payload Integrity: Include a timestamp and a cryptographically secure nonce within the encrypted payload to prevent replay attacks and verify freshness.
    • Expiration (TTL): When decrypting, verify the payload's timestamp against a maxAge threshold to ensure the data is not too old.
    • Secure Randomness: Use window.crypto.getRandomValues in browsers or crypto.randomBytes in Node.js for generating nonces.

    Security Checklist:

    1. Never expose private keys in client-side code.
    2. Use HTTPS for all communications.
    3. Validate input data before encryption.
    4. Use a minimum key size of 2048 bits for RSA.
    5. Handle errors gracefully to avoid leaking sensitive information via error messages.
    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
                };
            }
        }
    }
  7. Implement API token validation with SHA-256

    master

    You can build an API token management system by combining signSha256() for token generation and verifySha256() for validation. A typical workflow involves:

    1. Creating a JSON payload with user data and expiration.
    2. Signing the payload string.
    3. Encoding the payload and signature (e.g., via Base64) into a single token.
    4. Decoding the token and verifying the signature against the payload before trusting the data.
    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' };
            }
        }
    }
  8. Generate RSA keys securely with OpenSSL

    master

    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 4096
  9. Validate input and handle RSA size limits

    master

    RSA 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);
    }
  10. Install JSEncrypt

    master

    You can install JSEncrypt using npm or yarn, or include it directly in your HTML via a CDN.

    npm

    npm install jsencrypt

    yarn

    yarn add jsencrypt

    CDN

    Include the following script tag in your HTML:

    <script src="https://cdn.jsdelivr.net/npm/jsencrypt@latest/bin/jsencrypt.min.js"></script>
  11. Implement Client-Server Encrypted Communication

    master

    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']
    });