3

I'm trying to implement AES encryption for some text and I was looking for a solution that eventually comes down to one password for encryption and decryption. I've got a working solution that I found on this site: Simple Java AES encrypt/decrypt example

import javax.crypto.Cipher; import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;

import org.apache.commons.codec.binary.Base64;

public class Encryptor {
    public static String encrypt(String key1, String key2, String value) {
        try {
            IvParameterSpec iv = new IvParameterSpec(key2.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key1.getBytes("UTF-8"),
                    "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.ENCRYPT_MODE, skeySpec, iv);
            byte[] encrypted = cipher.doFinal(value.getBytes());
            System.out.println("encrypted string:"
                    + Base64.encodeBase64String(encrypted));
            return Base64.encodeBase64String(encrypted);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static String decrypt(String key1, String key2, String encrypted) {
        try {
            IvParameterSpec iv = new IvParameterSpec(key2.getBytes("UTF-8"));

            SecretKeySpec skeySpec = new SecretKeySpec(key1.getBytes("UTF-8"),
                    "AES");
            Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
            cipher.init(Cipher.DECRYPT_MODE, skeySpec, iv);
            byte[] original = cipher.doFinal(Base64.decodeBase64(encrypted));

            return new String(original);
        } catch (Exception ex) {
            ex.printStackTrace();
        }
        return null;
    }

    public static void main(String[] args) {

        String key1 = "Bar12345Bar12345"; // 128 bit key
        String key2 = "ThisIsASecretKet";
        System.out.println(decrypt(key1, key2,
                encrypt(key1, key2, "Hello World")));
    }
}

This algorithm is using key1 and key2 and don't understand why they need the two of them. What is the purpose of each key, and would it be safe to leave key1 as it is for all times and just replace key2 with a personal password?

Thanks

Community
  • 1
  • 1
user3116232
  • 433
  • 5
  • 17
  • 1
    To avoid character encoding issues, *always* specify a charset when calling `String.getBytes()` and `new String()`. – dnault Oct 09 '14 at 00:14
  • 1
    On the internet you will find many samples of encryption, and down to maybe 1% they will compile run, and be terribly broken or unsafe to use. Most of them have these tell-tale signs such as misunderstanding key and IV and now knowing passwords from keys (using UTF-8 as key is just stupid as well, that encoding may encode characters to multiple bytes) kudo's to dnault for pointing them out. Now start learning crypto, and don't grab any random code from the internet to perform encryption. – Maarten Bodewes Oct 10 '14 at 01:00
  • I love the exception handling in the code above, by the way. Error? Nah, `null`! – Maarten Bodewes Oct 10 '14 at 01:01
  • @owlstead: Which charset would you recommend for the key? – user3116232 Oct 11 '14 at 00:55
  • I believe owlstead's point about charsets is that when converting a String to a byte array there's not necessarily a 1:1 correspondence between characters and bytes. In UTF-8, a single character may be encoded using as many as 4 bytes, so calling getBytes("UTF-8") on an arbitrary String with 16 characters is not guaranteed to return an array of length 16. A better way to generate a key from a password is to use an algorithm like PBKDF2. – dnault Oct 11 '14 at 02:58

1 Answers1

8

'key1' is your secret key. In this code, the byte array is obtained by calling String.getBytes() on a password. See this answer for why that isn't advisable, and some suggested alternatives.

'key2' is misnamed; it's actually an Initialization Vector (or 'IV' for short). This is a random sequence of bytes that prevents the same plaintext from always being transformed into the same ciphertext.

A random IV should be generated for each encrypted message using SecureRandom.getBytes(). For AES the IV should be 16 bytes (128 bits). The IV is not a secret; it should be sent along with the ciphertext so the recipient can use it (along with the secret key) to decrypt the message.

After encrypting, I would recommend prepending the IV bytes to the ciphertext bytes and then Base64-encoding the resulting byte array.

As for how the code is structured, there's no need to pass the IV into the encrypt method; the encrypt method can generate the IV internally. Likewise, if the ciphertext is prefixed with the IV then you don't need to pass the IV to the decrypt method; instead the decrypt method can assume the first 16 bytes of the message are the IV.

Generally speaking, encryption alone does not ensure the message hasn't been tampered with. If you want to detect when an attacker tampers with your encrypted messages, you can apply a Message Authentication Code like HMAC to the iv + cyphertext.

Community
  • 1
  • 1
dnault
  • 8,340
  • 1
  • 34
  • 53
  • See also: http://stackoverflow.com/questions/4171204/using-aes-in-cbc-with-the-same-iv-for-messages – dnault Oct 08 '14 at 23:14