Skip to content

CWE-780: Use of RSA Without OAEP - JavaScript/Node.js

Overview

Using RSA encryption without OAEP (Optimal Asymmetric Encryption Padding) enables padding oracle attacks, chosen ciphertext attacks, and message malleability. In Node.js, this commonly occurs when using the crypto module without explicitly specifying OAEP padding, which defaults to the insecure PKCS#1 v1.5 padding.

Primary Defence: Always specify padding: crypto.constants.RSA_PKCS1_OAEP_PADDING with oaepHash set to 'sha256' or stronger when using crypto.publicEncrypt() and crypto.privateDecrypt(), use Web Crypto API's RSA-OAEP algorithm with explicit hash functions in browser environments, never rely on default padding schemes, and consider using hybrid encryption (RSA for key exchange + AES-GCM for data) to prevent padding oracle attacks and ensure cryptographic security.

Common Vulnerable Patterns

Default Padding with crypto.publicEncrypt()

// VULNERABLE - Defaults to RSA_PKCS1_PADDING (insecure!)
const crypto = require('crypto');

const encrypted = crypto.publicEncrypt(
    publicKey,  // No padding specified
    Buffer.from(message)
);
// Uses PKCS#1 v1.5 by default - vulnerable to Bleichenbacher's attack!

Why this is vulnerable:

  • Omitting padding in crypto.publicEncrypt() defaults to RSA_PKCS1_PADDING (PKCS#1 v1.5).
  • PKCS#1 v1.5 is deterministic, enabling Bleichenbacher-style padding oracles.
  • Attackers can adaptively probe decryption success/failure to recover plaintext.
  • Identical plaintexts yield identical ciphertexts, enabling pattern leakage.

Using node-forge without OAEP

// VULNERABLE - node-forge with PKCS#1 v1.5 padding
const forge = require('node-forge');

const publicKey = forge.pki.publicKeyFromPem(publicKeyPem);
const encrypted = publicKey.encrypt(
    message,
    'RSAES-PKCS1-V1_5'  // Insecure padding scheme
);

Why this is vulnerable:

  • 'RSAES-PKCS1-V1_5' implements deprecated PKCS#1 v1.5 padding.
  • Distinct error messages or timing differences create a padding oracle.
  • Attackers can brute-force with crafted ciphertexts and recover plaintext.
  • Real-world exploits exist against TLS and messaging systems.

crypto-browserify with Default Padding

// VULNERABLE - crypto-browserify defaults to PKCS#1 v1.5
const crypto = require('crypto-browserify');

const encrypted = crypto.publicEncrypt(publicKeyPem, Buffer.from(message));
// No padding specified - uses insecure default

Why this is vulnerable:

  • crypto-browserify defaults to PKCS#1 v1.5 when padding is omitted.
  • Browser decryption can leak timing differences, enabling padding oracles.
  • Deterministic PKCS#1 v1.5 exposes repeated tokens or keys.
  • Client-side environments lack server-grade timing mitigations.

WebCrypto API with PKCS#1 v1.5

// VULNERABLE - Using RSA-PKCS1-v1_5 algorithm
async function encryptInsecure(data, publicKey) {
    const encoded = new TextEncoder().encode(data);
    const ciphertext = await crypto.subtle.encrypt(
        {
            name: 'RSA-PKCS1-v1_5'  // Insecure!
        },
        publicKey,
        encoded
    );
    return ciphertext;
}

Why this is vulnerable:

  • 'RSA-PKCS1-v1_5' uses obsolete PKCS#1 v1.5 padding.
  • Promise timing differences can reveal padding validity.
  • Browser APIs (e.g., performance.now()) enable high-precision timing oracles.
  • XSS or third-party scripts can probe decryptions at scale.

Using Weak Hash with OAEP

// VULNERABLE - SHA-1 is deprecated
const crypto = require('crypto');

const encrypted = crypto.publicEncrypt(
    {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha1'  // Weak! Use sha256 or better
    },
    Buffer.from(message)
);

Why this is vulnerable:

  • OAEP is fine, but SHA-1 is broken (practical collisions since 2017).
  • OAEP depends on a strong hash; SHA-1 undermines that security proof.
  • 160-bit SHA-1 yields only ~80-bit collision security (too weak).
  • Standards and compliance regimes prohibit SHA-1 in new systems.

Secure Patterns

Node.js crypto Module with RSA-OAEP (PREFERRED)

Always specify RSA_PKCS1_OAEP_PADDING when encrypting with RSA.

// SECURE - Node.js crypto with OAEP padding
const crypto = require('crypto');

// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,  // Minimum 2048 bits
    publicKeyEncoding: {
        type: 'spki',
        format: 'pem'
    },
    privateKeyEncoding: {
        type: 'pkcs8',
        format: 'pem'
    }
});

const message = 'Sensitive data to encrypt';

// SECURE - Encrypt with OAEP padding
const encrypted = crypto.publicEncrypt(
    {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'  // Use SHA-256 or better
    },
    Buffer.from(message, 'utf8')
);

// SECURE - Decrypt with OAEP padding
const decrypted = crypto.privateDecrypt(
    {
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'
    },
    encrypted
);

console.log('Original:', message);
console.log('Decrypted:', decrypted.toString('utf8'));

Why this works:

  • RSA_PKCS1_OAEP_PADDING makes RSA probabilistic, blocking ciphertext replay.
  • oaepHash: 'sha256' provides strong collision resistance for OAEP.
  • Node.js follows RFC 8017, ensuring cross-platform compatibility.
  • OAEP eliminates PKCS#1 v1.5 padding oracles.

WebCrypto API with RSA-OAEP (Browser & Node.js 15.6+)

// SECURE - WebCrypto API with RSA-OAEP
const { webcrypto } = require('crypto');
const { subtle } = webcrypto;

async function generateKeyPairOAEP() {
    return await subtle.generateKey(
        {
            name: 'RSA-OAEP',           // OAEP algorithm
            modulusLength: 2048,
            publicExponent: new Uint8Array([1, 0, 1]),  // 65537
            hash: 'SHA-256'              // Strong hash
        },
        true,  // extractable
        ['encrypt', 'decrypt']
    );
}

async function encryptWithOAEP(data, publicKey) {
    const encoded = new TextEncoder().encode(data);
    const ciphertext = await subtle.encrypt(
        {
            name: 'RSA-OAEP'
        },
        publicKey,
        encoded
    );
    return Buffer.from(ciphertext);
}

async function decryptWithOAEP(ciphertext, privateKey) {
    const plaintext = await subtle.decrypt(
        {
            name: 'RSA-OAEP'
        },
        privateKey,
        ciphertext
    );
    return new TextDecoder().decode(plaintext);
}

// Example usage
(async () => {
    const keyPair = await generateKeyPairOAEP();

    const message = 'Secret message';
    const encrypted = await encryptWithOAEP(message, keyPair.publicKey);
    const decrypted = await decryptWithOAEP(encrypted, keyPair.privateKey);

    console.log('Original:', message);
    console.log('Decrypted:', decrypted);
})();

Why this works:

  • Keys are generated for RSA-OAEP and cannot be used with PKCS#1 v1.5.
  • SHA-256 is bound to the key, keeping encrypt/decrypt consistent.
  • WebCrypto uses native crypto providers and avoids key extraction.
  • Async APIs integrate cleanly while keeping crypto in vetted implementations.

node-forge Library with OAEP

// SECURE - node-forge with RSA-OAEP
const forge = require('node-forge');

// Generate key pair
const keypair = forge.pki.rsa.generateKeyPair({ bits: 2048 });
const publicKey = keypair.publicKey;
const privateKey = keypair.privateKey;

const message = 'Confidential data';

// SECURE - Encrypt with OAEP
const encrypted = publicKey.encrypt(
    message,
    'RSA-OAEP',
    {
        md: forge.md.sha256.create(),     // Use SHA-256
        mgf1: {
            md: forge.md.sha256.create()  // MGF1 with SHA-256
        }
    }
);

// SECURE - Decrypt with OAEP
const decrypted = privateKey.decrypt(
    encrypted,
    'RSA-OAEP',
    {
        md: forge.md.sha256.create(),
        mgf1: {
            md: forge.md.sha256.create()
        }
    }
);

console.log('Original:', message);
console.log('Decrypted:', decrypted);

Why this works:

  • 'RSA-OAEP' forces OAEP padding instead of PKCS#1 v1.5.
  • Explicit md and mgf1.md keep hash selection under your control.
  • Prevents silent fallback to insecure defaults.
  • Works in environments without native crypto (with a performance trade-off).

crypto-browserify with OAEP

// SECURE - crypto-browserify with explicit OAEP padding
const crypto = require('crypto-browserify');

const encrypted = crypto.publicEncrypt(
    {
        key: publicKeyPem,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'
    },
    Buffer.from(message)
);

const decrypted = crypto.privateDecrypt(
    {
        key: privateKeyPem,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'
    },
    encrypted
);

Why this works:

  • Same API as Node.js (RSA_PKCS1_OAEP_PADDING, oaepHash) for drop-in use.
  • Uses WebCrypto when available, falls back to JS otherwise.
  • Explicit padding/hash avoids environment-specific defaults.
  • Keeps security consistent across Node and browser builds.

Hybrid Encryption for Large Data

// SECURE - Hybrid encryption (RSA-OAEP + AES-GCM)
const crypto = require('crypto');

class HybridEncryption {

    static encrypt(data, rsaPublicKey) {
        // Step 1: Generate random AES-256 key
        const aesKey = crypto.randomBytes(32);  // 256 bits

        // Step 2: Encrypt data with AES-GCM
        const iv = crypto.randomBytes(12);  // 96-bit IV for GCM
        const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);

        let ciphertext = cipher.update(data, 'utf8');
        ciphertext = Buffer.concat([ciphertext, cipher.final()]);
        const authTag = cipher.getAuthTag();

        // Step 3: Encrypt AES key with RSA-OAEP
        const encryptedKey = crypto.publicEncrypt(
            {
                key: rsaPublicKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            aesKey
        );

        return {
            encryptedKey: encryptedKey,
            iv: iv,
            authTag: authTag,
            ciphertext: ciphertext
        };
    }

    static decrypt(encryptedData, rsaPrivateKey) {
        // Step 1: Decrypt AES key with RSA-OAEP
        const aesKey = crypto.privateDecrypt(
            {
                key: rsaPrivateKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            encryptedData.encryptedKey
        );

        // Step 2: Decrypt data with AES-GCM
        const decipher = crypto.createDecipheriv(
            'aes-256-gcm', 
            aesKey, 
            encryptedData.iv
        );
        decipher.setAuthTag(encryptedData.authTag);

        let plaintext = decipher.update(encryptedData.ciphertext);
        plaintext = Buffer.concat([plaintext, decipher.final()]);

        return plaintext.toString('utf8');
    }
}

// Example: Encrypt large data (megabytes)
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

const largeData = 'A'.repeat(1000000);
const encrypted = HybridEncryption.encrypt(largeData, publicKey);
const decrypted = HybridEncryption.decrypt(encrypted, privateKey);

console.log('Data size:', largeData.length);
console.log('Match:', largeData === decrypted);

Why this works:

  • RSA size limits make it unsuitable for bulk data.
  • AES-GCM encrypts large payloads quickly and provides integrity checks.
  • RSA-OAEP securely wraps the short AES key.
  • Mirrors TLS design for performance and security at scale.

Remediation Strategy

Use Node.js crypto with OAEP Padding (PRIMARY FIX)

Always specify RSA_PKCS1_OAEP_PADDING when encrypting with RSA.

Basic RSA-OAEP Encryption/Decryption

const crypto = require('crypto');

// Generate RSA key pair
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,  // Minimum 2048 bits
    publicKeyEncoding: {
        type: 'spki',
        format: 'pem'
    },
    privateKeyEncoding: {
        type: 'pkcs8',
        format: 'pem'
    }
});

const message = 'Sensitive data to encrypt';

// SECURE - Encrypt with OAEP padding
const encrypted = crypto.publicEncrypt(
    {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'  // Use SHA-256 or better
    },
    Buffer.from(message, 'utf8')
);

// SECURE - Decrypt with OAEP padding
const decrypted = crypto.privateDecrypt(
    {
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'
    },
    encrypted
);

console.log('Original:', message);
console.log('Decrypted:', decrypted.toString('utf8'));

Using async/await (Node.js 15.6+)

const { webcrypto } = require('crypto');
const { subtle } = webcrypto;

async function generateKeyPairAsync() {
    return await subtle.generateKey(
        {
            name: 'RSA-OAEP',
            modulusLength: 2048,
            publicExponent: new Uint8Array([1, 0, 1]),  // 65537
            hash: 'SHA-256'
        },
        true,  // extractable
        ['encrypt', 'decrypt']
    );
}

async function encryptWithOAEP(data, publicKey) {
    const encoded = new TextEncoder().encode(data);
    const ciphertext = await subtle.encrypt(
        {
            name: 'RSA-OAEP'
        },
        publicKey,
        encoded
    );
    return Buffer.from(ciphertext);
}

async function decryptWithOAEP(ciphertext, privateKey) {
    const plaintext = await subtle.decrypt(
        {
            name: 'RSA-OAEP'
        },
        privateKey,
        ciphertext
    );
    return new TextDecoder().decode(plaintext);
}

// Example usage
(async () => {
    const keyPair = await generateKeyPairAsync();

    const message = 'Secret message';
    const encrypted = await encryptWithOAEP(message, keyPair.publicKey);
    const decrypted = await decryptWithOAEP(encrypted, keyPair.privateKey);

    console.log('Original:', message);
    console.log('Decrypted:', decrypted);
})();

Hybrid Encryption for Large Data

RSA is limited to small data (~190 bytes for 2048-bit RSA with SHA-256). Use hybrid encryption for larger data.

const crypto = require('crypto');

class HybridEncryption {

    static encrypt(data, rsaPublicKey) {
        // Step 1: Generate random AES-256 key
        const aesKey = crypto.randomBytes(32);  // 256 bits

        // Step 2: Encrypt data with AES-GCM
        const iv = crypto.randomBytes(12);  // 96-bit IV for GCM
        const cipher = crypto.createCipheriv('aes-256-gcm', aesKey, iv);

        let ciphertext = cipher.update(data, 'utf8');
        ciphertext = Buffer.concat([ciphertext, cipher.final()]);
        const authTag = cipher.getAuthTag();

        // Step 3: Encrypt AES key with RSA-OAEP
        const encryptedKey = crypto.publicEncrypt(
            {
                key: rsaPublicKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            aesKey
        );

        return {
            encryptedKey: encryptedKey,
            iv: iv,
            authTag: authTag,
            ciphertext: ciphertext
        };
    }

    static decrypt(encryptedData, rsaPrivateKey) {
        // Step 1: Decrypt AES key with RSA-OAEP
        const aesKey = crypto.privateDecrypt(
            {
                key: rsaPrivateKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            encryptedData.encryptedKey
        );

        // Step 2: Decrypt data with AES-GCM
        const decipher = crypto.createDecipheriv(
            'aes-256-gcm', 
            aesKey, 
            encryptedData.iv
        );
        decipher.setAuthTag(encryptedData.authTag);

        let plaintext = decipher.update(encryptedData.ciphertext);
        plaintext = Buffer.concat([plaintext, decipher.final()]);

        return plaintext.toString('utf8');
    }
}

// Example usage
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

// Can encrypt large data (megabytes)
const largeData = 'A'.repeat(1000000);
const encrypted = HybridEncryption.encrypt(largeData, publicKey);
const decrypted = HybridEncryption.decrypt(encrypted, privateKey);

console.log('Data size:', largeData.length);
console.log('Match:', largeData === decrypted);

RSA Signatures with PSS (Not OAEP)

For signatures (not encryption), use PSS padding instead of OAEP.

const crypto = require('crypto');

function signWithPSS(message, privateKey) {
    const sign = crypto.createSign('RSA-SHA256');
    sign.update(message);
    sign.end();

    // Sign with PSS padding
    return sign.sign({
        key: privateKey,
        padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        saltLength: crypto.constants.RSA_PSS_SALTLEN_MAX_SIGN
    });
}

function verifyWithPSS(message, signature, publicKey) {
    const verify = crypto.createVerify('RSA-SHA256');
    verify.update(message);
    verify.end();

    return verify.verify({
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_PSS_PADDING,
        saltLength: crypto.constants.RSA_PSS_SALTLEN_AUTO
    }, signature);
}

// Example
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
    modulusLength: 2048,
    publicKeyEncoding: { type: 'spki', format: 'pem' },
    privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
});

const message = 'Document to sign';
const signature = signWithPSS(message, privateKey);
const isValid = verifyWithPSS(message, signature, publicKey);

console.log('Signature valid:', isValid);

Complete Working Example with Key Storage

const crypto = require('crypto');
const fs = require('fs');

class RSAKeyManager {

    // Generate and save key pair
    static generateAndSaveKeys(publicKeyFile, privateKeyFile) {
        const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            publicKeyEncoding: {
                type: 'spki',
                format: 'pem'
            },
            privateKeyEncoding: {
                type: 'pkcs8',
                format: 'pem'
            }
        });

        fs.writeFileSync(publicKeyFile, publicKey);
        fs.writeFileSync(privateKeyFile, privateKey);

        return { publicKey, privateKey };
    }

    // Load keys from files
    static loadKeys(publicKeyFile, privateKeyFile) {
        const publicKey = fs.readFileSync(publicKeyFile, 'utf8');
        const privateKey = fs.readFileSync(privateKeyFile, 'utf8');
        return { publicKey, privateKey };
    }

    // Encrypt with OAEP
    static encrypt(message, publicKey) {
        return crypto.publicEncrypt(
            {
                key: publicKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            Buffer.from(message, 'utf8')
        );
    }

    // Decrypt with OAEP
    static decrypt(ciphertext, privateKey) {
        return crypto.privateDecrypt(
            {
                key: privateKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            ciphertext
        ).toString('utf8');
    }
}

// Usage
const publicKeyFile = 'public.pem';
const privateKeyFile = 'private.pem';

// Generate keys
RSAKeyManager.generateAndSaveKeys(publicKeyFile, privateKeyFile);

// Load keys
const { publicKey, privateKey } = RSAKeyManager.loadKeys(
    publicKeyFile, 
    privateKeyFile
);

// Encrypt
const message = 'Confidential data';
const encrypted = RSAKeyManager.encrypt(message, publicKey);
console.log('Encrypted:', encrypted.toString('base64'));

// Decrypt
const decrypted = RSAKeyManager.decrypt(encrypted, privateKey);
console.log('Decrypted:', decrypted);

TypeScript Version

import * as crypto from 'crypto';

interface EncryptedData {
    encryptedKey: Buffer;
    iv: Buffer;
    authTag: Buffer;
    ciphertext: Buffer;
}

interface KeyPair {
    publicKey: string;
    privateKey: string;
}

class SecureRSA {

    static generateKeyPair(): KeyPair {
        return crypto.generateKeyPairSync('rsa', {
            modulusLength: 2048,
            publicKeyEncoding: {
                type: 'spki',
                format: 'pem'
            },
            privateKeyEncoding: {
                type: 'pkcs8',
                format: 'pem'
            }
        });
    }

    static encryptOAEP(message: string, publicKey: string): Buffer {
        return crypto.publicEncrypt(
            {
                key: publicKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            Buffer.from(message, 'utf8')
        );
    }

    static decryptOAEP(ciphertext: Buffer, privateKey: string): string {
        return crypto.privateDecrypt(
            {
                key: privateKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            ciphertext
        ).toString('utf8');
    }
}

// Usage
const keys = SecureRSA.generateKeyPair();
const encrypted = SecureRSA.encryptOAEP('Secret', keys.publicKey);
const decrypted = SecureRSA.decryptOAEP(encrypted, keys.privateKey);

Migration from PKCS#1 v1.5 to OAEP

const crypto = require('crypto');

class MigrationHelper {

    // Dual-padding decryption for migration period
    static decryptWithFallback(ciphertext, privateKey) {
        // Try OAEP first (secure)
        try {
            return crypto.privateDecrypt(
                {
                    key: privateKey,
                    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                    oaepHash: 'sha256'
                },
                ciphertext
            ).toString('utf8');
        } catch (error) {
            console.warn('OAEP decryption failed, trying legacy PKCS1 padding');
        }

        // Fall back to legacy PKCS#1 v1.5
        try {
            return crypto.privateDecrypt(
                {
                    key: privateKey,
                    padding: crypto.constants.RSA_PKCS1_PADDING
                },
                ciphertext
            ).toString('utf8');
        } catch (error) {
            throw new Error(`Decryption failed with both OAEP and PKCS1: ${error.message}`);
        }
    }

    // Always encrypt with OAEP (never use PKCS1 for new data)
    static encryptSecure(message, publicKey) {
        return crypto.publicEncrypt(
            {
                key: publicKey,
                padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
                oaepHash: 'sha256'
            },
            Buffer.from(message, 'utf8')
        );
    }
}

Common Finding Examples

Example 1: Node.js crypto with default padding

// security finding: CWE-780
// Source: request.body.data
// Sink: crypto.publicEncrypt()

// VULNERABLE CODE:
const data = req.body.data;
const encrypted = crypto.publicEncrypt(publicKey, Buffer.from(data));

// FIX:
const data = req.body.data;
const encrypted = crypto.publicEncrypt(
    {
        key: publicKey,
        padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
        oaepHash: 'sha256'
    },
    Buffer.from(data)
);

Example 2: node-forge with PKCS#1 v1.5

// security finding: CWE-780
// Source: config.secretData
// Sink: publicKey.encrypt()

// VULNERABLE CODE:
const forge = require('node-forge');
const encrypted = publicKey.encrypt(secretData, 'RSAES-PKCS1-V1_5');

// FIX:
const forge = require('node-forge');
const encrypted = publicKey.encrypt(
    secretData,
    'RSA-OAEP',
    {
        md: forge.md.sha256.create(),
        mgf1: { md: forge.md.sha256.create() }
    }
);

Example 3: WebCrypto API with wrong algorithm

// security finding: CWE-780
// Source: userInput
// Sink: subtle.encrypt()

// VULNERABLE CODE:
async function encryptData(data, publicKey) {
    const encoded = new TextEncoder().encode(data);
    return await crypto.subtle.encrypt(
        { name: 'RSA-PKCS1-v1_5' },  // Insecure!
        publicKey,
        encoded
    );
}

// FIX:
async function encryptData(data, publicKey) {
    const encoded = new TextEncoder().encode(data);
    return await crypto.subtle.encrypt(
        { name: 'RSA-OAEP' },  // Secure!
        publicKey,
        encoded
    );
}

Additional Resources