Java AES encryption/decryption procedure and usage of Initialization Vector

10,576

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.

Share:
10,576
Leevi Lehtonen
Author by

Leevi Lehtonen

Updated on June 11, 2022

Comments

  • Leevi Lehtonen
    Leevi Lehtonen almost 2 years

    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!

    • Artjom B.
      Artjom B. about 9 years
    • Artjom B.
      Artjom B. about 9 years
      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.
    • Leevi Lehtonen
      Leevi Lehtonen about 9 years
      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?
    • Artjom B.
      Artjom B. about 9 years
      You should definetly look into PBKDF2 in Java with a strong cryptographic hash function like SHA256. MD5 is just not that good anymore.
    • Marandil
      Marandil about 9 years
      @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)
    • Artjom B.
      Artjom B. about 9 years
      @Marandil I don't know why I mentioned MD5. I probably misread it. Nevertheless, SHA-256 is better than SHA-1.
    • Leevi Lehtonen
      Leevi Lehtonen about 9 years
      @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?
    • Artjom B.
      Artjom B. about 9 years
      @LeeviLehtonen You have to use the same salt during encryption and decryption.
    • Leevi Lehtonen
      Leevi Lehtonen about 9 years
      @ArtjomB. Okay thanks, so I can keep the salt as a constant and delete "genreteSalt" function
    • Artjom B.
      Artjom B. about 9 years
      @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.
    • Leevi Lehtonen
      Leevi Lehtonen about 9 years
      @ArtjomB. Yeah, that is the best solution I think. Thank you very much for help!
  • Leevi Lehtonen
    Leevi Lehtonen about 9 years
    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
  • Maarten Bodewes
    Maarten Bodewes about 9 years
    @LeeviLehtonen The IV is always the same size (the block size). It's better & more efficient to prefix the IV to the ciphertext before encoding it in base 64...
  • Leevi Lehtonen
    Leevi Lehtonen about 9 years
    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...