Skip to main content
List item continuation paragraphs
Source Link
Toby Speight
  • 88.3k
  • 14
  • 104
  • 327
  1. You are reusing the IV across all messages

    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.

    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());
    
// 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(rsaEncAesKey, iv, ciphertext), and document lengths so you can parse it unambiguously on decrypt. The Code Review threadother answers already discussesdiscuss hybrid encryption at a high level; this pins down the wire format and provider‑safe OAEP setup.

  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 Code Review thread already discusses hybrid encryption at a high level; this pins down the wire format and provider‑safe OAEP setup.

  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.

Source Link

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 Code Review thread already discusses hybrid encryption at a high level; this pins down the wire format and provider‑safe OAEP setup.