OK, it seems I found the answer to my question. I got information from fooobar.com/questions/141022 / .... From what I understand, IV (initialization vector) is used to add entropy to the encryption process. Each time you create a new cipher, Java creates a slightly different IV. Therefore, there are two solutions:
- User Fixed IV, or
- Save IV along with encrypted data.
From what I read, option 1 is not a good practice; therefore, option 2 is. I understand that it should be possible to simply add IV to the encrypted string (since the secret is still required), and therefore IV can be restored when the time comes for decryption.
Here is a complete solution almost . I still get some padding errors when decrypting (see my comment). I donโt have time to spend on it now, as a temporary measure, I immediately try to decrypt the encrypted string and continue trying (iterating) until it works. It seems to have a 50% chance + I'm not encrypting enough to be a performance issue. It would be nice if someone could suggest a fix though (just for the sake of completeness).
private static final String salt = "SaltySalt"; private static final int IV_LENGTH = 16; private static byte[] getSaltBytes() throws Exception { return salt.getBytes("UTF-8"); } private static char[] getMasterPassword() { return "SuperSecretPassword".toCharArray(); } public static String encrpytString (String input) throws Exception { SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536,256); SecretKey secretKey = factory.generateSecret(spec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.ENCRYPT_MODE, secret); byte[] ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV(); byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8")); byte[] finalByteArray = new byte[ivBytes.length + encryptedTextBytes.length]; System.arraycopy(ivBytes, 0, finalByteArray, 0, ivBytes.length); System.arraycopy(encryptedTextBytes, 0, finalByteArray, ivBytes.length, encryptedTextBytes.length); return DatatypeConverter.printBase64Binary(finalByteArray); } public static String decrpytString (String input) throws Exception { if (input.length() <= IV_LENGTH) { throw new Exception("The input string is not long enough to contain the initialisation bytes and data."); } byte[] byteArray = DatatypeConverter.parseBase64Binary(input); byte[] ivBytes = new byte[IV_LENGTH]; System.arraycopy(byteArray, 0, ivBytes, 0, 16); byte[] encryptedTextBytes = new byte[byteArray.length - ivBytes.length]; System.arraycopy(byteArray, IV_LENGTH, encryptedTextBytes, 0, encryptedTextBytes.length); SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1"); PBEKeySpec spec = new PBEKeySpec(getMasterPassword(), getSaltBytes(), 65536, 256); SecretKey secretKey = factory.generateSecret(spec); SecretKeySpec secret = new SecretKeySpec(secretKey.getEncoded(), "AES"); Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding"); cipher.init(Cipher.DECRYPT_MODE, secret, new IvParameterSpec(ivBytes)); byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes); return new String(decryptedTextBytes); }