2

I was implementing encryption decryption of a file using the AES/CBC/PKCS5PADDING algorithm. And I noticed some peculiarity. With the correct order of initialization of the IV, everything is decrypted correctly. But if the order is wrong (see commented out lines), the beginning of the line is decoded incorrectly.

But if the decryption happens with wrong IV in CBC mode then nothing should be decrypted. After all, that's how AES/CBC works.

My question is - why is the string still decrypted with the wrong IV ?

Output

org.junit.ComparisonFailure: 
Expected :Test string Test string Test string Test string Test string Test string
Actual   :�Eݠ�/ՙ�, 9B�� string Test string Test string Test string Test string

Code

import javax.crypto.*;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.SecretKeySpec;
import java.io.*;
import java.nio.charset.StandardCharsets;
import java.security.*;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.InvalidParameterSpecException;
import java.security.spec.KeySpec;


public class CryptographyService {

    private static final String SECRET_KEY_DERIVATION_ALGORITHM = "PBKDF2WithHmacSHA256";

    private static final String CIPHER_ALGORITHM_MODE_PADDING = "AES/CBC/PKCS5PADDING";

    private static final String CIPHER_ALGORITHM = "AES";

    private static final int SALT_LEN = 32;

    private static byte[] createSalt() {
        byte[] salt = new byte[SALT_LEN];
        SecureRandom random = new SecureRandom();
        random.nextBytes(salt);
        return salt;
    }

    private static SecretKey secretKeyCreate(String userPassword, byte[] salt) throws NoSuchAlgorithmException,
            InvalidKeySpecException {
        SecretKeyFactory factory = SecretKeyFactory.getInstance(SECRET_KEY_DERIVATION_ALGORITHM);
        KeySpec spec = new PBEKeySpec(userPassword.toCharArray(), salt, 25000, 256);
        SecretKey tmp = factory.generateSecret(spec);
        SecretKey secret = new SecretKeySpec(tmp.getEncoded(), CIPHER_ALGORITHM);
        return secret;
    }

    public static void encrypt(String userPassword, String fileEncryptName, String jsonPasswordsData)
            throws NoSuchAlgorithmException, InvalidKeySpecException,
            NoSuchPaddingException, InvalidKeyException, InvalidParameterSpecException, IOException {

        byte[] salt = createSalt();
        SecretKey secretKey = secretKeyCreate(userPassword, salt);

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_MODE_PADDING);
        cipher.init(Cipher.ENCRYPT_MODE, secretKey);
        AlgorithmParameters params = cipher.getParameters();
        byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();
        //cipher.init(Cipher.ENCRYPT_MODE, secretKey); // incorrect decrypt

        try (FileOutputStream fileOutputStream = new FileOutputStream(fileEncryptName);
        CipherOutputStream cipherOutputStream = new CipherOutputStream(fileOutputStream, cipher)) {

            fileOutputStream.write(iv);
            fileOutputStream.write(salt);
            fileOutputStream.flush();

            cipherOutputStream.write(jsonPasswordsData.getBytes(StandardCharsets.UTF_8));
        }
    }

    public static String decrypt(String userPassword, String fileDecryptName) throws NoSuchPaddingException,
            NoSuchAlgorithmException, IOException, InvalidParameterSpecException, InvalidKeySpecException,
            InvalidAlgorithmParameterException, InvalidKeyException {

        Cipher cipher = Cipher.getInstance(CIPHER_ALGORITHM_MODE_PADDING);
        AlgorithmParameters params = cipher.getParameters();
        int ivLength = params.getParameterSpec(IvParameterSpec.class).getIV().length;
        byte[] iv = new byte[ivLength];
        byte[] salt = new byte[SALT_LEN];
        byte[] plainText;

        try (FileInputStream fileInputStream = new FileInputStream(fileDecryptName);
             CipherInputStream cipherInputStream = new CipherInputStream(fileInputStream, cipher)) {

            fileInputStream.read(iv);
            fileInputStream.read(salt);

            SecretKey secretKey = secretKeyCreate(userPassword, salt);
            cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));
            plainText = cipherInputStream.readAllBytes();
        }
        return new String(plainText, StandardCharsets.UTF_8);
    }
}
Oxonomy
  • 29
  • 3
  • 3
    *...But if the decryption happens with wrong IV in CBC mode then nothing should be decrypted. After all, that's how AES/CBC works...*: For CBC, if a wrong IV is used during decryption (i.e. an IV that does not correspond to that of the encryption), this leads to a corrupted first block (16 bytes for AES). The rest is decrypted correctly. And exactly this is your result. – Topaco Jul 14 '22 at 10:58
  • @Topaco Is it possible to decrypt the rest of the blocks with the wrong IV, except for the first one? I thought that due to the CBC chaining scheme, the wrong IV would make all the information undecipherable – Oxonomy Jul 14 '22 at 11:04
  • 2
    Just have a look at the [CBC flowchart](https://en.wikipedia.org/wiki/Block_cipher_mode_of_operation#Cipher_block_chaining_(CBC)) for decryption, from which you can immediately deduce that only the first block is affected: The IV only plays a role in the decryption of the first block! You may be confusing this with another mode. – Topaco Jul 14 '22 at 11:12
  • @Topaco I figured it out. IV affects subsequent blocks (in the sense of mixing information). But if we consider the decryption of a separate block, then thanks to XOR, it does not matter how the previous block was created. Thanks for the help. – Oxonomy Jul 14 '22 at 11:30

0 Answers0