8

I want to learn the basics of AES encryption so I started to make a very simple Java program. The program loads a text file in to a String and asks for a key from the user. The program then uses AES to encrypt the text creating a new text file with the encrypted text. The program prints the Initialization Vector (IV) to the user.

The program also has the decryption function. The user specifies the encrypted text file along with the Initialization Vector and the key to decrypt it back to the original text in a new text file.

However I think I'm doing something wrong. Is it normal procedure in AES encryption that the user needs to have both key and IV to decrypt the file? I have browsed through the internet and almost in every example, the encrypted data can be decrypted by the user specifying only the key but in my case the user needs to have both the key and the IV. The program is working fine but I think it isn't efficient.

So should I use a constant, known IV which is used in all the encryptions and decryptions or what? Also some tutorials are using "salt", what is it and should I use it?

Here are my encrypt and decrypt methods:

public String encrypt(String stringToEncrypt, String userKey)
        throws NoSuchAlgorithmException, NoSuchPaddingException,
        InvalidKeyException, IllegalBlockSizeException, BadPaddingException {

    // User gives string key which is formatted to 16 byte and to a secret
    // key
    byte[] key = userKey.getBytes();
    MessageDigest sha = MessageDigest.getInstance("SHA-1");
    key = sha.digest(key);
    key = Arrays.copyOf(key, 16);
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");

    // Cipher initialization
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    cipher.init(Cipher.ENCRYPT_MODE, secretKey);

    // Encryption and encoding
    String encryptedData = new BASE64Encoder().encode(cipher
            .doFinal(stringToEncrypt.getBytes()));

    // IV is printed to user
    System.out.println("\nENCRYPTION IV: \n"
            + new BASE64Encoder().encode(cipher.getIV()) + "\n");

    // Function returns encrypted string which can be writed to text file
    return encryptedData;

}

public String decrypt(String stringToDecrypt, String userKey, String userIv)
        throws NoSuchAlgorithmException, IOException,
        NoSuchPaddingException, InvalidKeyException,
        InvalidAlgorithmParameterException, IllegalBlockSizeException,
        BadPaddingException {

    // User gives the same string key which was used for encryption
    byte[] key = userKey.getBytes();
    MessageDigest sha = MessageDigest.getInstance("SHA-1");
    key = sha.digest(key);
    key = Arrays.copyOf(key, 16);
    SecretKeySpec secretKey = new SecretKeySpec(key, "AES");

    // Decode string iv to byte
    byte[] iv = new BASE64Decoder().decodeBuffer(userIv);

    // Cipher initialization
    Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");
    cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(iv));

    // Decryption and decoding
    String decryptedData = new String(cipher.doFinal(new BASE64Decoder()
            .decodeBuffer(stringToDecrypt)));

    // Function returns decrypted string which can be writed to text file
    return decryptedData;

}

UPDATE

I updated my code now to use "PBKDF2WithHmacSHA256" algorithm with salt and etc. I also combined the Initialization Vector (IV) byte array to the cipher text byte array as prefix so I can split them in decrypt method and get the IV there (That's working fine).

However there's now an issue with the key, because I'm generating new encrypted key also in decryption method which of course is a wrong key for encrypted data. I want to be able to close the program so I can't store the key as a class variable. It's very hard to explain the issue but I hope you understand the problem...

public static byte[] getEncryptedPassword(String password, byte[] salt,
        int iterations, int derivedKeyLength)
        throws NoSuchAlgorithmException, InvalidKeySpecException {

    KeySpec mKeySpec = new PBEKeySpec(password.toCharArray(), salt,
            iterations, derivedKeyLength);

    SecretKeyFactory mSecretKeyFactory = SecretKeyFactory
            .getInstance("PBKDF2WithHmacSHA256");

    return mSecretKeyFactory.generateSecret(mKeySpec).getEncoded();

}

public String encrypt(String dataToEncrypt, String key) throws InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException, InvalidParameterSpecException, IllegalBlockSizeException, BadPaddingException, InvalidKeySpecException {

    byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
            16384, 128);

    SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");


    Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

    mCipher.init(Cipher.ENCRYPT_MODE, mSecretKeySpec);

    byte[] ivBytes = mCipher.getIV();
    byte[] encryptedTextBytes = mCipher.doFinal(dataToEncrypt.getBytes());

    byte[] combined = new byte[ivBytes.length+encryptedTextBytes.length];       
    System.arraycopy(ivBytes, 0, combined, 0, ivBytes.length);
    System.arraycopy(encryptedTextBytes, 0, combined, ivBytes.length, encryptedTextBytes.length);

    return Base64.getEncoder().encodeToString(combined);

}

public String decrypt(String dataToDecrypt, String key) throws NoSuchAlgorithmException, NoSuchPaddingException, InvalidKeyException, InvalidKeySpecException, IllegalBlockSizeException, BadPaddingException, InvalidAlgorithmParameterException {


    byte[] encryptedCombinedBytes = Base64.getDecoder().decode(dataToDecrypt);
    byte[] mEncryptedPassword = getEncryptedPassword(key, generateSalt(),
            16384, 128);

    byte[] ivbytes = Arrays.copyOfRange(encryptedCombinedBytes,0,16);

    SecretKeySpec mSecretKeySpec = new SecretKeySpec(mEncryptedPassword, "AES");

    Cipher mCipher = Cipher.getInstance("AES/CBC/PKCS5Padding");

    mCipher.init(Cipher.DECRYPT_MODE, mSecretKeySpec, new IvParameterSpec(ivbytes));    

    byte[] encryptedTextBytes = Arrays.copyOfRange(encryptedCombinedBytes, 16, encryptedCombinedBytes.length);

    System.out.println(encryptedTextBytes.length);
    byte[] decryptedTextBytes = mCipher.doFinal(encryptedTextBytes);



    return Base64.getEncoder().encodeToString(decryptedTextBytes);

}

public byte[] generateSalt() {
    SecureRandom random = new SecureRandom();
    byte saltBytes[] = new byte[16];
    random.nextBytes(saltBytes);
    return saltBytes;
}}

I hope somebody knows how to make this better. Thanks!

Kishor Prakash
  • 8,011
  • 12
  • 61
  • 92
Leevi Lehtonen
  • 118
  • 1
  • 1
  • 7
  • possible duplicate of [Should I use an initialization vector (IV) along with my encryption?](http://stackoverflow.com/questions/65879/should-i-use-an-initialization-vector-iv-along-with-my-encryption) – Artjom B. Feb 20 '15 at 11:06
  • A random salt is either used for deriving the key from the password or to create a secure hash from a password for authentication purposes. Salt and IV are generally distinct things. – Artjom B. Feb 20 '15 at 11:08
  • So should I implement the the salt to derive key from the userKey string or is it enough secure with the MessageDigest I'm using? – Leevi Lehtonen Feb 20 '15 at 11:39
  • You should definetly look into PBKDF2 in Java with a strong cryptographic hash function like SHA256. MD5 is just not that good anymore. – Artjom B. Feb 20 '15 at 11:42
  • @ArtjomB. I don't understand why you mention MD5 in first place. The source code utilizes SHA-1 hash function, and moreover, functions from SHA-2 family (256 and 512) should be already implemented in standard (though I can't find source of that information right now) – Marandil Feb 20 '15 at 14:11
  • @Marandil I don't know why I mentioned MD5. I probably misread it. Nevertheless, SHA-256 is better than SHA-1. – Artjom B. Feb 20 '15 at 14:14
  • @ArtjomB. I made some tweaks now but it only works if I store the key to class variable during encryption and then use the key in decryption. However I don't want to do that because I may want to close the program and come later to only decrypt the data. Should I still use the previous version's MessageDigest but with "SHA-256" or do you have any Ideas how I should handle this? – Leevi Lehtonen Feb 20 '15 at 15:44
  • @LeeviLehtonen You have to use the same salt during encryption and decryption. – Artjom B. Feb 20 '15 at 16:07
  • @ArtjomB. Okay thanks, so I can keep the salt as a constant and delete "genreteSalt" function – Leevi Lehtonen Feb 20 '15 at 16:10
  • @LeeviLehtonen I don't know your use case, but the best would be probably to add the salt to the encrypted file alongside the IV. – Artjom B. Feb 20 '15 at 16:11

1 Answers1

6

Just save the IV in the file before the encrypted data.

You should never use the same IV more than once (it's ok-ish, if you roll a new IV every time, and it just so happens that you roll the same twice, so you don't have to store and check that). Using the same IV many times poses a great security risk, as encrypting the same content twice reveals that it's - in fact - the same content.

Storing IV alongside the encrypted data is a common, and secure procedure, as it's role is to introduce "randomness" to the encryption scheme, and it shouldn't be secret, just securely (and in some schemes randomly) generated.

Marandil
  • 1,042
  • 1
  • 15
  • 31
  • So I can basically put this to my encrypt function's return: return new BASE64Encoder().encode(cipher.getIV()) + " " + encryptedData and then in decryption I split it back to encrypted data and IV – Leevi Lehtonen Feb 20 '15 at 11:23
  • 4
    @LeeviLehtonen The IV is always the same size (the [block size](http://docs.oracle.com/javase/7/docs/api/javax/crypto/Cipher.html#getBlockSize())). It's better & more efficient to prefix the IV to the ciphertext *before* encoding it in base 64... – Maarten Bodewes Feb 20 '15 at 11:44
  • I updated it to combine the byte arrays and then I split them back in the decryption method. Thanks. Although I updated also the secret key functions which aren't now working... – Leevi Lehtonen Feb 20 '15 at 15:36