0

I am trying to encrypt text using the AES encryption algorithm , save this encrypted text to a file and then reopen later and decrypt these. Following is my encryption and decryption logic

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.ENCRYPT_MODE, key, iv);
    byte[] stringBytes = clear_text.getBytes();
    byte[] raw = cipher.doFinal(stringBytes);
    return Base64.encodeBase64String(raw);

And this is the decryption logic

    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
    cipher.init(Cipher.DECRYPT_MODE, key, iv);
    byte[] raw = Base64.decodeBase64(encText);
    byte[] stringBytes = cipher.doFinal(raw);
    String clear_text = new String(stringBytes, "UTF8");
    return clear_text;

I get a BadPaddingSize exception. My guess is that by using the SecureRandom class, both the methods use different keys while encrypting or decypting text. Is there a way I can use the same key in both the routines?

user_mda
  • 18,148
  • 27
  • 82
  • 145
  • You cannot encrypt and then decrypt with different keys (and IVs). You have to store the key (and IV) somewhere. This is up to you. Since we know nothing about your application, you need to clarify and add more code. – Artjom B. Nov 10 '14 at 17:40
  • Yes, my question was how to have a common key for both. By using secureRandom, I am changing the key and IV everytime I try and decrypt. Or in other words, how to I save the key of type Key and use it whenever I need? What is an alternate way to generate a key without using random bits - – user_mda Nov 10 '14 at 18:00

1 Answers1

2

Yes, you can use the same key; it is even required to use the same key. You should however never use the same key / IV combination as that is not secure. So often the IV gets prefixed to the ciphertext instead.

Note that the following implementation shows you how to generate the random IV without SecureRandom, but that's a bit disingenious as the Cipher class will just use the default one internally to create the IV. For CBC the IV may be known to an attacker, but the attacker should not be able to distinguish it from random data.

In this example the key data is simply stored within a "constant". Storing the key within source code may not provide enough security. Instead it is often encrypted with a public key, password, stored on a USB key, stored within a smartcard or HSM etc. etc. Key management is however a vast subject so I won't discuss it further for this answer.

In Java you should however use SecretKey/SecretKeySpec to create keys from known data and IvParameterSpec for a known IV (or Nonce).

import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESWithStaticKeyAndRandomIV {

    private static byte[] KEY = new byte[] { (byte) 0x14, (byte) 0x0b,
            (byte) 0x41, (byte) 0xb2, (byte) 0x2a, (byte) 0x29, (byte) 0xbe,
            (byte) 0xb4, (byte) 0x06, (byte) 0x1b, (byte) 0xda, (byte) 0x66,
            (byte) 0xb6, (byte) 0x74, (byte) 0x7e, (byte) 0x14 };

    public static byte[] encrypt(byte[] plaintext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey key = new SecretKeySpec(KEY, "AES");
            cipher.init(Cipher.ENCRYPT_MODE, key);

            AlgorithmParameters params = cipher.getParameters();
            byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

            byte[] ciphertext = new byte[iv.length
                    + cipher.getOutputSize(plaintext.length)];
            System.arraycopy(iv, 0, ciphertext, 0, iv.length);
            cipher.doFinal(plaintext, 0, plaintext.length, ciphertext,
                    iv.length);
            return ciphertext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | InvalidParameterSpecException
                | ShortBufferException | IllegalBlockSizeException
                | BadPaddingException e) {
            throw new IllegalStateException(
                    "CBC encryption with standard algorithm should never fail",
                    e);
        }
    }

    public static byte[] decrypt(byte[] ciphertext) throws IllegalBlockSizeException,
            BadPaddingException {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKeySpec key = new SecretKeySpec(KEY, "AES");

            if (ciphertext.length < cipher.getBlockSize()) {
                throw new IllegalArgumentException(
                        "Ciphertext too small to contain IV");
            }

            IvParameterSpec ivSpec = new IvParameterSpec(ciphertext, 0,
                    cipher.getBlockSize());
            cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

            byte[] plaintext = new byte[cipher.getOutputSize(ciphertext.length
                    - cipher.getBlockSize())];
            cipher.doFinal(ciphertext, cipher.getBlockSize(), ciphertext.length
                    - cipher.getBlockSize(), plaintext, 0);
            return plaintext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | ShortBufferException
                | InvalidAlgorithmParameterException e) {
            throw new IllegalStateException(
                    "CBC decryption with standard algorithm should be available",
                    e);
        }
    }

    public static void main(String[] args) throws Exception {
           byte[] plaintext = decrypt(encrypt("owlstead".getBytes(StandardCharsets.UTF_8)));
           System.out.println(new String(plaintext, StandardCharsets.UTF_8));
    }
}

With a key store (you have to use JCEKS for now):

import java.io.IOException;
import java.nio.charset.StandardCharsets;
import java.security.AlgorithmParameters;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.KeyStore;
import java.security.KeyStore.ProtectionParameter;
import java.security.KeyStore.SecretKeyEntry;
import java.security.KeyStoreException;
import java.security.NoSuchAlgorithmException;
import java.security.UnrecoverableEntryException;
import java.security.cert.CertificateException;
import java.security.spec.InvalidParameterSpecException;

import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.ShortBufferException;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

public class AESWithStaticKeyAndRandomIV {

    private static final String KEY_ALIAS = "secret";

    private static byte[] KEY = new byte[] { (byte) 0x14, (byte) 0x0b,
            (byte) 0x41, (byte) 0xb2, (byte) 0x2a, (byte) 0x29, (byte) 0xbe,
            (byte) 0xb4, (byte) 0x06, (byte) 0x1b, (byte) 0xda, (byte) 0x66,
            (byte) 0xb6, (byte) 0x74, (byte) 0x7e, (byte) 0x14 };

    private static ProtectionParameter PASSWORD = new KeyStore.PasswordProtection(
            new char[] {'p', 'a', 's', 's', 'w', 'o', 'r', 'd'});

    private final KeyStore store;

    private AESWithStaticKeyAndRandomIV(KeyStore store) {
        this.store = store;
    }

    public byte[] encrypt(byte[] plaintext) {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey key;
            try {
                key = ((SecretKeyEntry) store.getEntry(KEY_ALIAS, PASSWORD))
                        .getSecretKey();
            } catch (UnrecoverableEntryException | KeyStoreException e) {
                throw new IllegalStateException("What key?", e);
            }
            cipher.init(Cipher.ENCRYPT_MODE, key);

            AlgorithmParameters params = cipher.getParameters();
            byte[] iv = params.getParameterSpec(IvParameterSpec.class).getIV();

            byte[] ciphertext = new byte[iv.length
                    + cipher.getOutputSize(plaintext.length)];
            System.arraycopy(iv, 0, ciphertext, 0, iv.length);
            cipher.doFinal(plaintext, 0, plaintext.length, ciphertext,
                    iv.length);
            return ciphertext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | InvalidParameterSpecException
                | ShortBufferException | IllegalBlockSizeException
                | BadPaddingException e) {
            throw new IllegalStateException(
                    "CBC encryption with standard algorithm should never fail",
                    e);
        }
    }

    public byte[] decrypt(byte[] ciphertext) throws IllegalBlockSizeException,
            BadPaddingException {
        try {
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
            SecretKey key;
            try {
                key = ((SecretKeyEntry) store.getEntry(KEY_ALIAS, PASSWORD))
                        .getSecretKey();
            } catch (UnrecoverableEntryException | KeyStoreException e) {
                throw new IllegalStateException("What key?", e);
            }

            if (ciphertext.length < cipher.getBlockSize()) {
                throw new IllegalArgumentException(
                        "Ciphertext too small to contain IV");
            }

            IvParameterSpec ivSpec = new IvParameterSpec(ciphertext, 0,
                    cipher.getBlockSize());
            cipher.init(Cipher.DECRYPT_MODE, key, ivSpec);

            byte[] plaintext = new byte[cipher.getOutputSize(ciphertext.length
                    - cipher.getBlockSize())];
            cipher.doFinal(ciphertext, cipher.getBlockSize(), ciphertext.length
                    - cipher.getBlockSize(), plaintext, 0);
            return plaintext;
        } catch (InvalidKeyException | NoSuchAlgorithmException
                | NoSuchPaddingException | ShortBufferException
                | InvalidAlgorithmParameterException e) {
            throw new IllegalStateException(
                    "CBC decryption with standard algorithm should be available",
                    e);
        }
    }

    public static KeyStore createStoreWithSecretKey() {

        try {
            KeyStore keyStore = KeyStore.getInstance("JCEKS");
            keyStore.load(null);
            SecretKey key = new SecretKeySpec(KEY, "AES");
            keyStore.setEntry(KEY_ALIAS, new KeyStore.SecretKeyEntry(key), PASSWORD);
            return keyStore;
        } catch (KeyStoreException | NoSuchAlgorithmException
                | CertificateException | IOException e) {
            throw new IllegalStateException("Unable to create key store", e);
        }
    }

    public static void main(String[] args) throws Exception {
        AESWithStaticKeyAndRandomIV crypt = new AESWithStaticKeyAndRandomIV(
                createStoreWithSecretKey());

        byte[] plaintext = crypt.decrypt(crypt.encrypt("owlstead"
                .getBytes(StandardCharsets.UTF_8)));
        System.out.println(new String(plaintext, StandardCharsets.UTF_8));
    }
}
Maarten Bodewes
  • 90,524
  • 13
  • 150
  • 263
  • Thanks so much, Can I use a key generated using the keytool and have it somewhere in my system? If yes, how would the IV work? I need to decrypt these cleartexs in other languages too , so I need a common key and IV to use – user_mda Nov 10 '14 at 19:55
  • Added some code. The IV should always be random and either be calculated from some unique value or stored with the ciphertext (as in the example). It should *not* be stored with the key and will therefore not be within the `KeyStore` interface. – Maarten Bodewes Nov 10 '14 at 20:30
  • hi owlstead, thank you for adding your code, I tried out with your code but I still get the Illegal blockSize exception, do you think this is because I am saving the encrypted text in a file and then reading it from the file and doing so changes the padding of the encrypted string? – user_mda Nov 11 '14 at 16:53
  • Yes, I would consider that likely. Have you tried with [`readAllBytes`](https://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#readAllBytes(java.nio.file.Path))? – Maarten Bodewes Nov 11 '14 at 16:55
  • I dont want to read all bytes in the file, but only the encrypted text. My guess is that saving it to a file, changes the bytes which while decrypting throws an exception. – user_mda Nov 11 '14 at 17:05
  • Eh, no, that key *was* static of course, even though the keystore wasn't. Sorry, already was forgetting the code :) Check the file handling and encoding/decoding or ask a new question... – Maarten Bodewes Nov 11 '14 at 17:17
  • Will the decryption fail with the same key but a different IV everytime? Trying to understand why I cant decrypt a string when using the same key – user_mda Nov 12 '14 at 16:13
  • Yes that would fail, although with just CBC it would just produce an incorrect message if that message is the same size or larger than one block. That's why I kept the IV with the ciphertext. – Maarten Bodewes Nov 12 '14 at 16:15
  • 1
    My unit tests of this solution were failing attempting to compare the results of an encrypt / decrypt of a 6 byte value, because the decrypt would return a unexpected 16 bytes. I isolated the problem in the example to the creation of the plaintext array in the decrypt which unsuccessfully attempts to use cipher.getOutputSize(..) to get the size of the originally encrypted byte array. It returns 6 bytes instead of 16 (i.e. block size). To solve I resorted to copying all non-IV bytes to their own array, then used the plain doFinal(byte[]) method which did properly return 6 bytes. – LeastOne Oct 11 '16 at 19:40