Skip to content

CWE-780: Use of RSA Without OAEP - Java

Overview

Using RSA encryption without OAEP (Optimal Asymmetric Encryption Padding) enables padding oracle attacks, chosen ciphertext attacks, and message malleability. In Java, this commonly occurs when using Cipher.getInstance("RSA") without specifying the padding mode, which defaults to the insecure PKCS#1 v1.5 padding.

Primary Defence: Always use Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding") or stronger variants instead of the default "RSA" transformation, specify OAEP parameters explicitly with OAEPParameterSpec for SHA-256 or SHA-384, never rely on default cipher transformations, and consider using hybrid encryption (RSA for key exchange + AES for data) to prevent padding oracle attacks and ensure cryptographic security.

Remediation Strategy

PRIORITY 1: Use RSA with OAEP Padding (PRIMARY FIX)

Always specify the complete cipher transformation string including OAEP padding.

Basic RSA-OAEP Encryption/Decryption

import javax.crypto.Cipher;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.PrivateKey;
import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.spec.OAEPParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import javax.crypto.spec.PSource;

public class SecureRSAExample {

    public static KeyPair generateKeyPair() throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048, new SecureRandom());  // Minimum 2048 bits
        return keyGen.generateKeyPair();
    }

    // SECURE - RSA with OAEP and SHA-256
    public static byte[] encryptOAEP(byte[] plaintext, PublicKey publicKey) 
            throws Exception {
        // Method 1: Using transformation string (recommended)
        Cipher cipher = Cipher.getInstance(
            "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
        );
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        return cipher.doFinal(plaintext);
    }

    public static byte[] decryptOAEP(byte[] ciphertext, PrivateKey privateKey) 
            throws Exception {
        Cipher cipher = Cipher.getInstance(
            "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
        );
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(ciphertext);
    }

    // Alternative: Using OAEPParameterSpec for more control
    public static byte[] encryptOAEPWithParams(byte[] plaintext, PublicKey publicKey) 
            throws Exception {
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256",           // Hash function
            "MGF1",              // Mask generation function
            MGF1ParameterSpec.SHA256,  // MGF parameters
            PSource.PSpecified.DEFAULT  // Source of label (usually empty)
        );

        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
        return cipher.doFinal(plaintext);
    }

    public static byte[] decryptOAEPWithParams(byte[] ciphertext, PrivateKey privateKey) 
            throws Exception {
        OAEPParameterSpec oaepParams = new OAEPParameterSpec(
            "SHA-256",
            "MGF1",
            MGF1ParameterSpec.SHA256,
            PSource.PSpecified.DEFAULT
        );

        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey, oaepParams);
        return cipher.doFinal(ciphertext);
    }

    public static void main(String[] args) throws Exception {
        KeyPair keyPair = generateKeyPair();

        String message = "Sensitive data to encrypt";
        byte[] plaintext = message.getBytes("UTF-8");

        // Encrypt
        byte[] ciphertext = encryptOAEP(plaintext, keyPair.getPublic());

        // Decrypt
        byte[] decrypted = decryptOAEP(ciphertext, keyPair.getPrivate());

        System.out.println("Original: " + message);
        System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
    }
}

PRIORITY 2: 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.

import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.*;
import java.util.Arrays;

public class HybridEncryption {

    private static final int AES_KEY_SIZE = 256;
    private static final int GCM_IV_SIZE = 12;
    private static final int GCM_TAG_SIZE = 128;

    public static class EncryptedData {
        public byte[] encryptedKey;
        public byte[] iv;
        public byte[] ciphertext;

        public EncryptedData(byte[] encryptedKey, byte[] iv, byte[] ciphertext) {
            this.encryptedKey = encryptedKey;
            this.iv = iv;
            this.ciphertext = ciphertext;
        }
    }

    public static EncryptedData hybridEncrypt(byte[] plaintext, PublicKey rsaPublicKey) 
            throws Exception {
        // Step 1: Generate random AES key
        KeyGenerator keyGen = KeyGenerator.getInstance("AES");
        keyGen.init(AES_KEY_SIZE, new SecureRandom());
        SecretKey aesKey = keyGen.generateKey();

        // Step 2: Encrypt data with AES-GCM
        byte[] iv = new byte[GCM_IV_SIZE];
        new SecureRandom().nextBytes(iv);

        Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_SIZE, iv);
        aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, gcmSpec);
        byte[] ciphertext = aesCipher.doFinal(plaintext);

        // Step 3: Encrypt AES key with RSA-OAEP
        Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        rsaCipher.init(Cipher.ENCRYPT_MODE, rsaPublicKey);
        byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());

        return new EncryptedData(encryptedKey, iv, ciphertext);
    }

    public static byte[] hybridDecrypt(EncryptedData encrypted, PrivateKey rsaPrivateKey) 
            throws Exception {
        // Step 1: Decrypt AES key with RSA-OAEP
        Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        rsaCipher.init(Cipher.DECRYPT_MODE, rsaPrivateKey);
        byte[] aesKeyBytes = rsaCipher.doFinal(encrypted.encryptedKey);
        SecretKey aesKey = new SecretKeySpec(aesKeyBytes, "AES");

        // Step 2: Decrypt data with AES-GCM
        Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
        GCMParameterSpec gcmSpec = new GCMParameterSpec(GCM_TAG_SIZE, encrypted.iv);
        aesCipher.init(Cipher.DECRYPT_MODE, aesKey, gcmSpec);
        return aesCipher.doFinal(encrypted.ciphertext);
    }

    public static void main(String[] args) throws Exception {
        KeyPair keyPair = SecureRSAExample.generateKeyPair();

        // Large data (can be megabytes)
        byte[] largeData = new byte[1000000];
        new SecureRandom().nextBytes(largeData);

        // Encrypt
        EncryptedData encrypted = hybridEncrypt(largeData, keyPair.getPublic());

        // Decrypt
        byte[] decrypted = hybridDecrypt(encrypted, keyPair.getPrivate());

        System.out.println("Match: " + Arrays.equals(largeData, decrypted));
    }
}

PRIORITY 3: RSA Signatures with PSS (Not OAEP)

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

import java.security.*;
import java.security.spec.MGF1ParameterSpec;
import java.security.spec.PSSParameterSpec;

public class RSASignatureExample {

    public static byte[] signWithPSS(byte[] message, PrivateKey privateKey) 
            throws Exception {
        // Configure PSS parameters
        PSSParameterSpec pssSpec = new PSSParameterSpec(
            "SHA-256",                    // Hash algorithm
            "MGF1",                       // Mask generation function
            MGF1ParameterSpec.SHA256,     // MGF parameters
            32,                           // Salt length (hash output length)
            1                             // Trailer field
        );

        Signature signature = Signature.getInstance("RSASSA-PSS");
        signature.setParameter(pssSpec);
        signature.initSign(privateKey);
        signature.update(message);
        return signature.sign();
    }

    public static boolean verifyWithPSS(byte[] message, byte[] signatureBytes, 
                                       PublicKey publicKey) throws Exception {
        PSSParameterSpec pssSpec = new PSSParameterSpec(
            "SHA-256",
            "MGF1",
            MGF1ParameterSpec.SHA256,
            32,
            1
        );

        Signature signature = Signature.getInstance("RSASSA-PSS");
        signature.setParameter(pssSpec);
        signature.initVerify(publicKey);
        signature.update(message);
        return signature.verify(signatureBytes);
    }
}

Common Vulnerable Patterns

Default RSA Transformation (Uses PKCS#1 v1.5)

// VULNERABLE - Defaults to "RSA/ECB/PKCS1Padding" (weak!)
import javax.crypto.Cipher;
import java.security.PublicKey;

Cipher cipher = Cipher.getInstance("RSA");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(plaintext);
// This uses PKCS#1 v1.5, vulnerable to Bleichenbacher's attack!

Why this is vulnerable:

  • Cipher.getInstance("RSA") defaults to RSA/ECB/PKCS1Padding (PKCS#1 v1.5), not OAEP.
  • PKCS#1 v1.5 padding is susceptible to padding oracle and chosen-ciphertext attacks.
  • The Bleichenbacher attack (1998) breaks PKCS#1 v1.5 when decryption success/failure can be observed.
  • An attacker can adaptively probe decryption to recover plaintext without the private key.
  • Secure use requires explicitly specifying OAEP in the transformation string.

Explicitly Using PKCS1Padding

// VULNERABLE - Explicitly specifying weak padding
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(plaintext);
// PKCS#1 v1.5 padding enables Bleichenbacher's attack

Why this is vulnerable:

  • RSA/ECB/PKCS1Padding is PKCS#1 v1.5, which is vulnerable to Bleichenbacher-style padding oracles.
  • Attackers can adaptively query decryption and learn plaintext from success/failure signals.
  • The padding structure is deterministic enough to enable chosen-ciphertext recovery.
  • PKCS#1 v1.5 also allows malleability and timing side-channel leakage.

Using Deprecated SHA-1 with OAEP

// VULNERABLE - SHA-1 is deprecated
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(plaintext);
// SHA-1 is cryptographically broken, use SHA-256 or better

Why this is vulnerable:

  • OAEP is correct, but SHA-1 is broken (practical collisions since 2017).
  • OAEP assumes a strong hash; SHA-1’s weaknesses undermine that proof.
  • Collision attacks can enable crafted inputs that weaken OAEP’s masking behavior.
  • Modern standards prohibit SHA-1 for new systems; use SHA-256/384/512.

Encrypting Large Data Directly

// VULNERABLE - Will throw exception or fail
// Max plaintext for 2048-bit RSA with SHA-256 OAEP: ~190 bytes
byte[] largeData = new byte[1000];
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encrypted = cipher.doFinal(largeData);  // javax.crypto.IllegalBlockSizeException!

Why this is vulnerable:

  • RSA can only encrypt data smaller than the modulus minus padding overhead.
  • For 2048-bit RSA with SHA-256 OAEP, max plaintext is ~190 bytes.
  • Larger inputs throw IllegalBlockSizeException or risk truncation in some implementations.
  • Even with bigger keys, RSA is too slow for bulk data.
  • The safe pattern is hybrid encryption (AES-GCM for data + RSA-OAEP for the key).

Secure Patterns

// SECURE - RSA with OAEP padding and SHA-256
import javax.crypto.Cipher;
import java.security.PublicKey;
import java.security.PrivateKey;

// Encryption
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] ciphertext = cipher.doFinal(plaintext);

// Decryption
cipher.init(Cipher.DECRYPT_MODE, privateKey);
byte[] decrypted = cipher.doFinal(ciphertext);

Why this works:

  • OAEP makes RSA probabilistic, blocking ciphertext replay and oracle-style probes.
  • SHA-256 provides strong collision resistance for OAEP’s security proof.
  • MGF1 spreads randomness across the message to defeat chosen-ciphertext attacks.
  • OAEP with SHA-256 meets modern standards and avoids PKCS#1 v1.5 weaknesses.

RSA-OAEP with Explicit Parameters

// SECURE - Using OAEPParameterSpec for full control
import javax.crypto.Cipher;
import java.security.spec.OAEPParameterSpec;
import java.security.spec.MGF1ParameterSpec;
import javax.crypto.spec.PSource;

OAEPParameterSpec oaepParams = new OAEPParameterSpec(
    "SHA-256",                      // Hash function
    "MGF1",                          // Mask generation function
    MGF1ParameterSpec.SHA256,        // MGF parameters
    PSource.PSpecified.DEFAULT       // Source of label (usually empty)
);

Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPPadding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey, oaepParams);
byte[] ciphertext = cipher.doFinal(plaintext);

Why this works:

  • Explicit OAEPParameterSpec avoids provider defaults that might select weak hashes.
  • Matching SHA-256 for both OAEP and MGF1 keeps padding strength consistent.
  • The empty label is standard and interoperable across implementations.
  • Explicit parameters reduce surprises during provider upgrades or migrations.

Hybrid Encryption for Large Data

// SECURE - Hybrid encryption combining AES-GCM with RSA-OAEP
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import java.security.SecureRandom;

// Step 1: Generate random AES key
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(256, new SecureRandom());
SecretKey aesKey = keyGen.generateKey();

// Step 2: Encrypt large data with AES-GCM
byte[] iv = new byte[12];
new SecureRandom().nextBytes(iv);
Cipher aesCipher = Cipher.getInstance("AES/GCM/NoPadding");
aesCipher.init(Cipher.ENCRYPT_MODE, aesKey, new GCMParameterSpec(128, iv));
byte[] encryptedData = aesCipher.doFinal(largeData);

// Step 3: Encrypt AES key with RSA-OAEP
Cipher rsaCipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
rsaCipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] encryptedKey = rsaCipher.doFinal(aesKey.getEncoded());

// Send: encryptedKey, iv, encryptedData

Why this works:

  • RSA’s size limits make direct encryption impractical for large data.
  • AES-GCM efficiently encrypts bulk data and provides integrity checks.
  • RSA-OAEP securely wraps the ephemeral AES key.
  • The split (encrypted key + IV + ciphertext) matches standard TLS-style designs.

RSA-OAEP with SHA-512 (Maximum Security)

// SECURE - Using SHA-512 for maximum security
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-512AndMGF1Padding");
cipher.init(Cipher.ENCRYPT_MODE, publicKey);
byte[] ciphertext = cipher.doFinal(plaintext);

Why this works:

  • SHA-512 provides a higher security margin than SHA-256 for long-term secrecy.
  • OAEP construction stays the same, but the hash primitive is stronger.
  • Useful for high-assurance or long-retention data requirements.
  • Trade-off: slightly larger ciphertext overhead.

Complete Working Example with Key Storage

import javax.crypto.Cipher;
import java.io.*;
import java.security.*;
import java.security.spec.*;
import java.util.Base64;

public class RSAKeyManagement {

    // Generate and save key pair
    public static void generateAndSaveKeyPair(String publicKeyFile, String privateKeyFile) 
            throws Exception {
        KeyPairGenerator keyGen = KeyPairGenerator.getInstance("RSA");
        keyGen.initialize(2048, new SecureRandom());
        KeyPair keyPair = keyGen.generateKeyPair();

        // Save public key
        X509EncodedKeySpec x509Spec = new X509EncodedKeySpec(
            keyPair.getPublic().getEncoded()
        );
        try (FileOutputStream fos = new FileOutputStream(publicKeyFile)) {
            fos.write(x509Spec.getEncoded());
        }

        // Save private key
        PKCS8EncodedKeySpec pkcs8Spec = new PKCS8EncodedKeySpec(
            keyPair.getPrivate().getEncoded()
        );
        try (FileOutputStream fos = new FileOutputStream(privateKeyFile)) {
            fos.write(pkcs8Spec.getEncoded());
        }
    }

    // Load public key
    public static PublicKey loadPublicKey(String filename) throws Exception {
        byte[] keyBytes = readFile(filename);
        X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePublic(spec);
    }

    // Load private key
    public static PrivateKey loadPrivateKey(String filename) throws Exception {
        byte[] keyBytes = readFile(filename);
        PKCS8EncodedKeySpec spec = new PKCS8EncodedKeySpec(keyBytes);
        KeyFactory keyFactory = KeyFactory.getInstance("RSA");
        return keyFactory.generatePrivate(spec);
    }

    private static byte[] readFile(String filename) throws IOException {
        try (FileInputStream fis = new FileInputStream(filename);
             ByteArrayOutputStream baos = new ByteArrayOutputStream()) {
            byte[] buffer = new byte[1024];
            int bytesRead;
            while ((bytesRead = fis.read(buffer)) != -1) {
                baos.write(buffer, 0, bytesRead);
            }
            return baos.toByteArray();
        }
    }

    public static void main(String[] args) throws Exception {
        // Generate keys
        generateAndSaveKeyPair("public.key", "private.key");

        // Load keys
        PublicKey publicKey = loadPublicKey("public.key");
        PrivateKey privateKey = loadPrivateKey("private.key");

        // Encrypt
        String message = "Confidential data";
        byte[] plaintext = message.getBytes("UTF-8");

        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.ENCRYPT_MODE, publicKey);
        byte[] ciphertext = cipher.doFinal(plaintext);

        // Decrypt
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        byte[] decrypted = cipher.doFinal(ciphertext);

        System.out.println("Original: " + message);
        System.out.println("Decrypted: " + new String(decrypted, "UTF-8"));
    }
}

Verification

After implementing the recommended secure patterns, verify the fix through multiple approaches:

  • Manual testing: Submit malicious payloads relevant to this vulnerability and confirm they're handled safely without executing unintended operations
  • Code review: Confirm all instances use the secure pattern (parameterized queries, safe APIs, proper encoding) with no string concatenation or unsafe operations
  • Static analysis: Use security scanners to verify no new vulnerabilities exist and the original finding is resolved
  • Regression testing: Ensure legitimate user inputs and application workflows continue to function correctly
  • Edge case validation: Test with special characters, boundary conditions, and unusual inputs to verify proper handling
  • Framework verification: If using a framework or library, confirm the recommended APIs are used correctly according to documentation
  • Authentication/session testing: Verify security controls remain effective and cannot be bypassed (if applicable to the vulnerability type)
  • Rescan: Run the security scanner again to confirm the finding is resolved and no new issues were introduced

Locate the Finding

Understand the Vulnerability Pattern

Identify which vulnerable pattern the code matches:

Pattern A - Default RSA Transformation:

// Line XX in ReportedClass.java
Cipher cipher = Cipher.getInstance("RSA"); // Defaults to PKCS1Padding

Pattern B - Explicit PKCS#1 v1.5:

// Line XX in ReportedClass.java
Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");

Pattern C - Weak Hash Algorithm:

// Line XX in ReportedClass.java
Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-1AndMGF1Padding");

Trace Data Flow

Follow the encryption operation:

  1. Where is the Cipher instance created?
  2. How is the key obtained (generated, loaded from file, retrieved from key store)?
  3. What data is being encrypted?
  4. Is the encrypted data stored or transmitted?
  5. Where is decryption performed?

Apply the Fix

Choose the appropriate remediation approach based on your requirements:

Option 1: Update cipher transformation to use OAEP (Recommended - most common fix)

  • Change Cipher.getInstance("RSA") to "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
  • Replace any PKCS1Padding references with OAEPWithSHA-256AndMGF1Padding
  • Use OAEPParameterSpec for explicit control over hash algorithms
  • Ensure both encryption and decryption use matching OAEP parameters

Option 2: Use hybrid encryption for large data (Data > 190 bytes)

  • RSA with OAEP has strict size limits based on key length and hash algorithm
  • Generate ephemeral AES-256 key for symmetric encryption of bulk data
  • Encrypt data with AES-GCM, encrypt AES key with RSA-OAEP
  • Combine encrypted key, IV, and ciphertext for secure transmission

Option 3: Upgrade weak hash algorithms (If already using OAEP with SHA-1)

  • Replace OAEPWithSHA-1AndMGF1Padding with OAEPWithSHA-256AndMGF1Padding
  • Use SHA-384 or SHA-512 for higher security requirements
  • Ensure MGF1 hash matches main hash algorithm
  • Update both encryption and decryption to use same hash

Option 4: Implement migration strategy for existing data (Backward compatibility needed)

  • Create dual-mode decryption supporting both OAEP and legacy PKCS#1
  • Always encrypt new data with OAEP, never use PKCS#1 for new operations
  • Plan batch re-encryption of stored data to OAEP
  • Remove PKCS#1 support after migration period

See the Secure Patterns and Remediation Strategy sections for detailed implementation examples of each approach.

Verify the Fix

Code Review Checklist:

  • Cipher transformation string includes "OAEP"
  • Hash algorithm is SHA-256, SHA-384, or SHA-512 (not SHA-1 or MD5)
  • MGF algorithm matches the hash algorithm
  • Data size is within RSA limits or hybrid encryption is used
  • Both encryption and decryption use matching algorithms
  • Key size is at least 2048 bits
  • SecureRandom is used for key generation

Verification steps:

  1. Verify OAEP is used: Search codebase for RSA cipher usage

    # Find all RSA cipher instantiations
    grep -r "Cipher.getInstance.*RSA" src/
    
    # Should all use OAEP, not PKCS1Padding
    # SECURE - RSA/ECB/OAEPWithSHA-256AndMGF1Padding
    # VULNERABLE - RSA/ECB/PKCS1Padding or just "RSA"
    
  2. Manual verification: Test encryption uses OAEP

    // In development, verify the cipher algorithm:
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    System.out.println("✓ Using OAEP: " + cipher.getAlgorithm());
    
    // Verify it's NOT using PKCS1:
    if (cipher.getAlgorithm().contains("PKCS1")) {
        System.err.println("ERROR: Using vulnerable PKCS1 padding!");
    }
    
  3. Verify probabilistic encryption: Encrypt same data twice, verify ciphertexts differ

    // OAEP should produce different ciphertext each time (randomized padding)
    byte[] plaintext = "test".getBytes();
    byte[] ciphertext1 = encryptOAEP(plaintext, publicKey);
    byte[] ciphertext2 = encryptOAEP(plaintext, publicKey);
    
    if (Arrays.equals(ciphertext1, ciphertext2)) {
        System.err.println("ERROR: Encryption is deterministic (not OAEP)!");
    } else {
        System.out.println("✓ Encryption is probabilistic (OAEP working)");
    }
    
  4. Static analysis: Use tools to detect weak cryptography

    # SpotBugs detector: WEAK_PADDING (detects PKCS1Padding)
    # SonarQube rule: S5547 (Cipher algorithms should be robust)
    mvn spotbugs:check
    mvn sonar:sonar
    

Check for Similar Issues

Search the codebase for related vulnerabilities:

# Search for all RSA cipher usage
grep -r "Cipher.getInstance.*RSA" src/

# Look for PKCS1Padding usage
grep -r "PKCS1Padding" src/

# Find default RSA instantiation
grep -r 'Cipher.getInstance("RSA")' src/

Migration Considerations

If the system has existing encrypted data:

Option A - Dual Decryption (Transition Period)

public byte[] decryptWithFallback(byte[] ciphertext, PrivateKey privateKey) 
        throws Exception {
    // Try OAEP first (new secure method)
    try {
        Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
        cipher.init(Cipher.DECRYPT_MODE, privateKey);
        return cipher.doFinal(ciphertext);
    } catch (Exception e) {
        logger.warn("OAEP decryption failed, attempting legacy PKCS1");
    }

    // Fallback to legacy PKCS#1 for old data
    Cipher cipher = Cipher.getInstance("RSA/ECB/PKCS1Padding");
    cipher.init(Cipher.DECRYPT_MODE, privateKey);
    return cipher.doFinal(ciphertext);
}

// Always encrypt new data with OAEP
public byte[] encryptSecure(byte[] plaintext, PublicKey publicKey) 
        throws Exception {
    Cipher cipher = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    cipher.init(Cipher.ENCRYPT_MODE, publicKey);
    return cipher.doFinal(plaintext);
}

Option B - Batch Re-encryption

  1. Decrypt existing data with old PKCS#1 method
  2. Re-encrypt with new OAEP method
  3. Update data storage
  4. Remove PKCS#1 decryption support

Verification:

  • Cipher transformation uses OAEP
  • SHA-256 hash algorithm specified
  • Card number is small enough for RSA (<190 bytes)
  • Corresponding decryption updated
  • Unit tests pass
  • Security scan confirms finding resolved

Common Finding Scenarios

Scenario 1: Default RSA in Crypto Utility Class

  • Finding: CryptoUtil.encrypt() uses Cipher.getInstance("RSA")
  • Fix: Change to "RSA/ECB/OAEPWithSHA-256AndMGF1Padding"
  • Impact: Update all callers to ensure compatibility

Scenario 2: PKCS#1 in Legacy Code

  • Finding: Old encryption service uses PKCS1Padding explicitly
  • Fix: Implement dual decryption during migration, always encrypt with OAEP
  • Impact: Batch re-encryption of stored data may be needed

Scenario 3: SHA-1 with OAEP

  • Finding: Code uses "RSA/ECB/OAEPWithSHA-1AndMGF1Padding"
  • Fix: Upgrade to SHA-256 or SHA-512
  • Impact: Ensure both encryption and decryption are updated together

Scenario 4: Large Data Encryption

  • Finding: Attempting to encrypt files with RSA directly
  • Fix: Implement hybrid encryption (AES-GCM + RSA-OAEP)
  • Impact: Significant code refactoring required

Migration from PKCS#1 v1.5 to OAEP

Verification with Encryption/Decryption Roundtrip

// Manual test to verify OAEP works correctly:
KeyPair keyPair = SecureRSAExample.generateKeyPair();

String message = "Test message";
byte[] plaintext = message.getBytes("UTF-8");

// Encrypt with OAEP
byte[] ciphertext = SecureRSAExample.encryptOAEP(plaintext, keyPair.getPublic());

// Decrypt with OAEP  
byte[] decrypted = SecureRSAExample.decryptOAEP(ciphertext, keyPair.getPrivate());

// Verify decryption works
if (message.equals(new String(decrypted, "UTF-8"))) {
    System.out.println("✓ OAEP encryption/decryption working");
} else {
    System.err.println("ERROR: Decryption failed!");
}

// Verify probabilistic behavior (same plaintext -> different ciphertext)
byte[] ciphertext2 = SecureRSAExample.encryptOAEP(plaintext, keyPair.getPublic());
if (!java.util.Arrays.equals(ciphertext, ciphertext2)) {
    System.out.println("✓ OAEP is probabilistic (ciphertexts differ)");
} else {
    System.err.println("ERROR: Encryption is deterministic (not using OAEP properly)!");
}

Additional Resources