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 toRSA/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/PKCS1Paddingis 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-1is 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
IllegalBlockSizeExceptionor 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
RSA with OAEP and SHA-256 (Recommended)
// 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
OAEPParameterSpecavoids 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:
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:
- Where is the
Cipherinstance created? - How is the key obtained (generated, loaded from file, retrieved from key store)?
- What data is being encrypted?
- Is the encrypted data stored or transmitted?
- 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
PKCS1Paddingreferences withOAEPWithSHA-256AndMGF1Padding - Use
OAEPParameterSpecfor 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-1AndMGF1PaddingwithOAEPWithSHA-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:
-
Verify OAEP is used: Search codebase for RSA cipher usage
-
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!"); } -
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)"); } -
Static analysis: Use tools to detect weak cryptography
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
- Decrypt existing data with old PKCS#1 method
- Re-encrypt with new OAEP method
- Update data storage
- 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()usesCipher.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)!");
}