Skip to main content
2 of 2
List item continuation paragraphs
Toby Speight
  • 88.3k
  • 14
  • 104
  • 327

Nice setup. Two things here will make or break the scheme.

  1. You are reusing the IV across all messages. IvParameterSpec Iv = new IvParameterSpec(SecureRandom.getSeed(16)); is executed once, so every Encrypt(…) and Decrypt(…) call uses the same IV. With AES‑CBC, that is a critical flaw. CBC requires a fresh, unpredictable IV per encryption. Reusing it leaks relationships between first blocks and enables practical attacks. Generate a new IV each time, and store or prefix it with the ciphertext.

    // per-encrypt call
    byte[] iv = new byte[16];                    // 16 bytes for AES block size
    SecureRandom rng = new SecureRandom();
    rng.nextBytes(iv);
    
    Cipher c = Cipher.getInstance("AES/CBC/PKCS5Padding");
    c.init(Cipher.ENCRYPT_MODE, aesKey, new IvParameterSpec(iv));
    byte[] ct = c.doFinal(plaintext);
    
    // serialize as: RSA(aesKey) || iv || ct
    

    NIST SP 800‑38A explicitly requires an unpredictable IV for CBC; predictable or reused IVs are unsafe.

  2. Lock down OAEP parameters explicitly. You use "RSA/ECB/OAEPWithSHA-256AndMGF1Padding", which is fine, but different providers have different defaults for OAEP internals. Make the hash and MGF explicit to avoid cross‑provider mismatches and subtle downgrade bugs:

    Cipher rsa = Cipher.getInstance("RSA/ECB/OAEPWithSHA-256AndMGF1Padding");
    OAEPParameterSpec oaep = new OAEPParameterSpec(
            "SHA-256",
            "MGF1",
            MGF1ParameterSpec.SHA256,
            PSource.PSpecified.DEFAULT
    );
    rsa.init(Cipher.ENCRYPT_MODE, rsaPublicKey, oaep);
    byte[] encKey = rsa.doFinal(aesKey.getEncoded());
    

Also define a clear message format, for example: (rsaEncAesKey, iv, ciphertext), and document lengths so you can parse it unambiguously on decrypt. The other answers already discuss hybrid encryption at a high level; this pins down the wire format and provider‑safe OAEP setup.