I have an application that needs to store some secret passwords in a configuration file such as database and ftp passwords/detail. I've looked around and found a lot of encryption/decryption solutions using AES, but I can't seem to figure out how to make it work without changing the key. That means I can encrypt and decrypt (using the same SecretKey), but to maintain persistence across restarts etc. I can't seem to make the SecretKey stay the same. The example below shows my methods working:
String secret = Encryptor.encrpytString("This is secret");
String test = Encryptor.decrpytString(secret);
System.out.println(test); //This is secret is printed
So far so good. However if I run it once I might get the value of '2Vhht/L80UlQ184S3rlAWw==' as my secret, the next time it is 'MeC4zCf9S5wUUKAu8rvpCQ==', so presumably the key is changing. I'm assuming I am applying some counter-intuative logic to the problem and would appreciate if someone can shed some light on either a) what I'm doing wrong, or b) a solution that would allow me to store the password information encrypted and retrievable with the information provided.
My methods are as follows:
private static final String salt = "SaltySalt";
private static byte [] ivBytes = null;
private static byte[] getSaltBytes() throws Exception {
return salt.getBytes("UTF-8");
}
private static char[] getMasterPassword() {
return "SuperSecretPassword".toCharArray();
}
private static byte[] getIvBytes() throws Exception {
if (ivBytes == null) {
//I don't have the parameters, so I'll generate a dummy encryption to create them
encrpytString("test");
}
return ivBytes;
}
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);
ivBytes = cipher.getParameters().getParameterSpec(IvParameterSpec.class).getIV();
byte[] encryptedTextBytes = cipher.doFinal(input.getBytes("UTF-8"));
return DatatypeConverter.printBase64Binary(encryptedTextBytes);
}
public static String decrpytString (String input) throws Exception {
byte[] encryptedTextBytes = DatatypeConverter.parseBase64Binary(input);
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(getIvBytes()));
byte[] decryptedTextBytes = cipher.doFinal(encryptedTextBytes);
return new String(decryptedTextBytes);
}
Thanks for the help!
AES, Advanced Encryption Standard is a block ciphertext encryption and decryption algorithm that processes a block of 128 bits of data using secret keys of 128, 192, or 256 bits. We will also discuss how this algorithm can be implemented using the Java programming language.
AES is an Advanced Encryption Standard algorithm. It is a type of symmetric, block cipher encryption and decryption algorithm. It works with key size 128, 192, and 256 bits. It uses a valid and similar secret key for both encryption and decryption.
Here padding is required and Java provides 3 alternatives. For encoding, the AES algorithm is repetitive in nature and supports 128, 192, and 256 bits.
OK, looks like I've found the answer to my question. I sourced my information from this Stackoverflow post. From what I understand, the IV (initialisation vector) is used to add entropy into the encryption process. Each time you create a new cipher, Java creates a slightly different IV. There are therefore two solutions:
From what I've read, option 1 is not very good practice; so option 2 it is. I understand that it should be possible to simply append the IV to the encrypted string (as the secret is still required) and therefore the IV can be reconstructed when it comes time to decrypt.
Here is the almost complete solution. I'm still getting some padding errors on decryption (see my comment). I don't have time to spend on it now, so as a temporary measure I immediately try decrypting an encrypted string and keep on trying (iterating) until it works. It seems to have about a 50% hit rate + I'm not encrypting often enough for it to be a performance concern. Would be nice if someone could suggest a fix though (just for completeness sake).
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);
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With