Cryptography: Why does my encryption initialization vector only effect the first 16 bytes?
Asked Answered
L

1

7

I wanted to try my hand at encrypting a file and utilized the following stack overflow response. However, while testing out the initialization vector, I found it only effected the first 16 bytes.

When I pass an empty iv to the decrypt cipher (other than the first 16 bytes) the data was unexpectedly decrypted. [I assume the library isn't broke and that I'm doing something improperly; but it's a scary thought that others may be in the same boat and not realize it.]

Example:

    Initial bytes ..... 2222222222222222222222222222222222222222222222222222
    Encrypted bytes ... b35b3945cdcd08e2f8a65b353ff754c32a48d9624e16b616d432
    Decrypted bytes ... 3c4154f7f33a2edbded5e5af5d3d39b422222222222222222222
Q: Why isn't the entire decryption failing?

Speculation: I suppose I could do the encryption by iterating over the data 16 bytes at a time and updating the iv each round by hashing the prior encrypted 16 byte block. However, that seems to be busy work that I would have expected the library to do. And I would have expected it to have been mentioned by the experts that present implementation guidelines. But I'm just grasping at straws here. For all I know, maybe the security community is only worried about a hack on the first block.

Note: Just now I found a 5.5 year old stack overflow post that identified this same issue; and unfortunately it still does not have a response.



package test;

import java.security.AlgorithmParameters;
import java.security.spec.KeySpec;
import java.util.Formatter;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;

/*
 * Issue: Using "AES/CBC/PKCS5Padding" encryption, the Initialization Vector
 *        appears to only affect the first block?!? 
 * 
 * Example Output
 *    iv 1e6376d5d1180cf9fcf7c78d7f1f1b96
 *    bv 00000000000000000000000000000000
 *    I: 222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
 *    E: b35b3945cdcd08e2f8a65b353ff754c32a48d9624e16b616d432ee5f78a26aa295d83625634d1048bf2dbb51fc657b7f796b60066129da5e1e7d3c7b51a30c1d962db75ac6666d4b32513c154b47f18eb66f62d7417cfd77f07f81f27f08d7d818e6910ca5849da3e6cff852bc06317e2d51907879598c8d3ae74074f4c27f7b8e2f74ca04d3ed6ac839b819a0f4cb462d0a4d9497cd917b8bd0aafb590ddd593b5b652cf8f642d3b2cd9dc0981dc1c913d52d065a844ea65e72cd7738eee3b488c4304e884109320dc54668ac4659d6014de9cf19422f7f68157d4330478589533571434d07b1939e56259fb8828823361bc912b84dc6ccdd5878b1d05801e0a6ce099bc86f1356fd145338163d59a07f2efdb1a6f91f4a35e6304f2d15d9972b0dda3c2275b5942a7f032ab6f90138
 *    D: 3c4154f7f33a2edbded5e5af5d3d39b42222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222222
 */
public class IvBug {

    public static void main(String[] args) throws Exception {
        // Initialize.
        final char[] password = "foo".toCharArray();
        final byte[] salt = "bar".getBytes();

        byte[] iData = new byte[300];
        java.util.Arrays.fill(iData, (byte)0x22);               // Make the issue easy to see.
//      for (int i=0; i<msg.length; i++) msg[i] = (byte) i;     // Alternate fill.

        // Perform the test.
        SecretKey sKey = generateKey(password,salt);
        byte[] iv = generateIv(sKey);
        byte[] eData = encrypt(sKey, iData, iv);
        byte[] badIv = new byte[iv.length];             // Discard initialization vector.
        byte[] dData = decrypt(sKey, eData, badIv);

        // Display the results.
        System.out.println("iv " + hexStr(iv));
        System.out.println("bv " + hexStr(badIv));
        System.out.println("I: " + hexStr(iData));      // Initial
        System.out.println("E: " + hexStr(eData));      // Encrypted
        System.out.println("D: " + hexStr(dData));      // Decrypted
    }

    static SecretKey generateKey(char[] password, byte[] salt) throws Exception {
        SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA256");
        KeySpec spec = new PBEKeySpec(password, salt, 65536, 128);
        SecretKey tmp = factory.generateSecret(spec);
        return new SecretKeySpec(tmp.getEncoded(), "AES");
    }

    static byte[] generateIv(SecretKey key) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        AlgorithmParameters params = cipher.getParameters();
        return params.getParameterSpec(IvParameterSpec.class).getIV();
    }

    static byte[] encrypt(SecretKey key, byte[] data, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.ENCRYPT_MODE, key, new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }

    static byte[] decrypt(SecretKey key, byte[] data, byte[] iv) throws Exception {
        Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
        cipher.init(Cipher.DECRYPT_MODE, key, new IvParameterSpec(iv));
        return cipher.doFinal(data);
    }

    static String hexStr(byte[] bytes) {
        try (Formatter formatter = new Formatter()) {
            for (byte b : bytes) formatter.format("%02x", b);
            return formatter.toString();
        }
    }
}

Lachus answered 11/11, 2017 at 22:0 Comment(0)
A
6

You seem to misunderstand why we use an IV. The behavior you have observed is the intended behavior. Consider this diagram of how CBC mode works taken from Wikipedia:

CBC Decryption

When we decrypt, the IV is only used to derive the first block of encrypted data. Notice that, in the diagram, we don't actually need the IV to decrypt any subsequent blocks.

I feel like you might (mistakenly) be viewing the IV as relevant to the security of the cipher. This is not the case. The key is the secret value. An IV does not need to be kept secret. The only requirement for an IV is that it be cryptographically random and unpredictable.

Affusion answered 11/11, 2017 at 22:22 Comment(6)
I now see the Wiki description of the IV states "For CBC and CFB, reusing an IV leaks some information about the first block of plaintext, and about any common prefix shared by the two messages." Thus, I assume there must be some header info they are trying to protect in that first block. I guess PCBC would have met my expectations. And knowing they are simply protecting the header, you have changed my expectations. Thanks!Lachus
There is no header information. IV reuse just reduces the first block of ciphertext to be vulnerable in the same way as ECB mode. It might be worth a bit more reading on the subject. Glad I could help.Affusion
If any two msgs share a "common prefix" that crosses several blocks, wouldn't all but the first block have that vulnerability?Lachus
If I understand you correctly, no. That is the point in CBC mode. To ensure that duplicate blocks of plaintext don't encrypt to duplicate blocks of ciphertext. By XORing each resulting block of ciphertext with the next block of plaintext, we ensure no meaningful duplicates. The IV is used for the first block since there is no block of ciphertext we can use there.Affusion
Ahhh, the vulnerability is in the resulting ciphertext itself (and not the decryption process). So as you state, it is indeed only the first block that needed the random IV.Lachus
@Lachus The IV should be a different random value for each encryption. One common menthol is to create a random IV, use it for encryption and prefix the encrypted data with the IV for use on decryption.Opportune

© 2022 - 2024 — McMap. All rights reserved.