I have a problem with AES encryption. I need to encrypt plaintext using CTR with a random IV. Then I need to encrypt the IV with the same key. I know that it is not required to encrypt the IV, but I need it for my usecase.

To decrypt the message I simply first decrypt the IV and then decrypt the cipher with the decrypted IV and the key.

Currently I am using EBC to encrypt the IV, as otherwhise I would need another IV to encrypt the previous IV and so on.

The problem is that I am able to decrypt the cipher using a a completely different key from the enryption.

What am I doing wrong?

import java.security.SecureRandom;
import java.util.Arrays;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.IvParameterSpec;

    public static void main(String[] args) throws Exception {
    //Generate a random AES Key
    SecretKey key1 = generateAESKey();
    byte[] plainText = "some plain text".getBytes();

    //Gerate random IV
    byte[] iv = new byte[16];
    SecureRandom random = new SecureRandom();

    //Encrypt plainText using the generated key1 using CTR
    byte[] encryptedWithKey1 = encryptCTR(plainText, iv, key1);
    //Now encrypt the IV using key1 with ECB
    byte[] encryptedIvWithKey1 = encryptECB(iv, key1);

    //Generate another key
    SecretKey key2 = generateAESKey();
    //First decrypt IV with wrong key, which resuts in wrong IV
    byte[] decryptedIvWithKey2 = decryptECB(encryptedIvWithKey1, key2);
    //Using this wrong IV decrypt encrypted payload with wrong key
    byte[] decryptedWithKey2 = decryptCTR(encryptedWithKey1, decryptedIvWithKey2, key2);

    //Expect plaintext and decrypted cipher using wrong key to NOT be equal
    System.out.println("should print false: " + Arrays.equals(plainText, decryptedWithKey2));

    private static byte[] encryptCTR(byte[] plainText, byte[] iv, SecretKey key) throws Exception {
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

    Cipher cipher = Cipher.getInstance("AES/CTR/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key, ivParameterSpec);

    return cipher.doFinal(plainText);

private static byte[] decryptCTR(byte[] encrypted, byte[] iv, SecretKey key) throws Exception {
    IvParameterSpec ivParameterSpec = new IvParameterSpec(iv);

    Cipher cipherDecrypt = Cipher.getInstance("AES/CTR/NoPadding");
    cipherDecrypt.init(Cipher.DECRYPT_MODE, key, ivParameterSpec);
    return cipherDecrypt.doFinal(encrypted);

private static byte[] encryptECB(byte[] plainText, SecretKey key) throws Exception {
    Cipher cipher = Cipher.getInstance("AES/ECB/NoPadding");
    cipher.init(Cipher.ENCRYPT_MODE, key);

    return cipher.doFinal(plainText);

private static byte[] decryptECB(byte[] encrypted, SecretKey key) throws Exception {
    Cipher cipherDecrypt = Cipher.getInstance("AES/ECB/NoPadding");
    cipherDecrypt.init(Cipher.DECRYPT_MODE, key);
    return cipherDecrypt.doFinal(encrypted);
    public static SecretKey generateAESKey() {
    SecureRandom random = new SecureRandom();
    byte[] keyBytes = new byte[16];
    SecretKeySpec key = new SecretKeySpec(keyBytes, "AES");
    return key;

1 Answer 1


CTR mode is a way to, in essence, turn a block cipher (which operates on "blocks" of data of a specified size, transforming them in a way that is deterministic based on the key and the input block, but contains complex internal math) into a stream cipher (which is, essentially, a secure pseudo-random number generator that you seed with the key and IV, generate a length of pseudo-random bytes the same as the length of your data, and then XOR them). Because the actual encrypt/decrypt operation (with regard to your data; CTR mode technically involves using your block cipher to encrypt successive numbers - the eponymous counter - to produce the pseudo-random numbers) is just XOR, there is no way for the algorithm to verify that the key is correct. Mathematically, there's nothing preventing you from taking plaintext stream PT, XORing it with pseudo-random stream PR1 (encrypt), and then XORing it again with a different PR2... you just aren't going to get PT back out again!

In fact, in general there's nothing preventing you from decrypting encrypted data with the wrong key. Some padding schemes (used to ensure that your data is a multiple of your block size, which is irrelevant for stream ciphers and counter-based modes of operation, so you aren't using one) include what amounts to a checksum that can be used to detect incorrect decryption (or tampering with the ciphertext) with decent reliability (worst case, with only one byte of padding needed, you'd still have only a 1/256 chance of a random key not producing a detectable error), but this is not secure; a competent attacker can bypass this check. In fact, often enough, an attacker can exploit this padding check to decrypt your message without knowing the key at all (via a padding oracle). There are some block cipher modes of operation that are what's called AEAD (Authenticated Encryption with Associated Data) modes, which include a cryptographic verification that the ciphertext was not tampered with between encryption and decryption, and that the same key was used for both (thus authenticating the decrypted message: you know it is the same as the message that was encrypted). The most common AEAD mode is GCM, which is also a counter mode (but does require additional space for the authentication data, if you want it to be any more secure than CTR). You can also authenticate the decryption in other ways, such as using an HMAC (Hash-based Message Authentication Code), which combines a cryptographically secure hash function with a secret key (can be the same as your encryption key) to produce a verification value (which needs to be sent/stored along with the ciphertext) that can be used to verify the key is correct and the message hasn't been tampered with. In general, though, encryption/decryption by itself provides no guarantee that the message out is the same as the message in, either because the ciphertext was tampered with or the wrong key was used! Encryption, as a general class, provides confidentiality of the data and nothing else.

What am I doing wrong?

Trying to build some very weird cryptosystem when you don't actually understand crypto. Don't do that. Rolling your own cryptosystem is only slightly less of a bad idea than rolling your own crypto primitives, and while one might argue that it has a lower barrier to entry (you don't have to know nearly as much math, for example), you still don't meet it.

For example, I can't even guess why you think your IV needs to be encrypted, but if your use case actually requires it (unlikely), you're probably trying to do something that can't be done (at least, not securely). The IV is just to ensure that encrypting the same data with the same key does not produce identical ciphertext; its actual value is irrelevant so long as it is non-repeating and unpredictable (which is why it's typically long-ish and generated randomly) and certainly not secret. If you're expecting the IV to serve some other purpose than that, you're trying to use symmetric ciphers wrong.

A MUCH better idea would be to explain what you want your cryptosystem to do, and in the case that there's nothing which currently does that exact thing already and it's not impossible, let people knowledgeable the field design a new system to do it. Seriously, even experts screw up all the time. We're on the third standard for WiFi security (WEP, WPA, and now WPA2, with WPA3 coming out to address problems found in WPA2) and the fifth for TCP security even if you ignore the never-published v1 (SSL 2.0, SSL 3.0, TLS 1.0, TLS 1.1, and now TLS 1.2, with TLS 1.3 around the corner), and even if the original versions of those were designed by crypto amateurs (I don't actually know, and it seems unlikely, but those were different times), the intervening versions weren't... and we still found weaknesses in almost all of them.

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .