We have a stable codebase (we think) that we stream a file into, encrypt it, and write out to a resultant encrypted file. Then we decrypt the encrypted file to ensure that the process worked correctly full circle.
We have been using PBEWITHSHA1ANDDESEDE
and it's been working fine for about a year, but it came to our attention that this algorithm is considered risky/broken. However, we tried to make a swap to a better algorithm, and now the content can not be decrypted and comes out unreadable. The algorithm we swapped to is PBEWithHmacSHA512AndAES_128
as per a Veracode blog we found here.
Why would switching to this alternate algorithm cause the file not to encrypt/decrypt full circle, if we made no other code changes? What can we do to make it work?
Below is included the important (I think) part of the code, with notes/comments.
// defined outside the below method; this algorithm works:
private static String ALGORITHM = "PBEWITHSHA1ANDDESEDE";
// the new version, which fails:
//private static String ALGORITHM = "PBEWithHmacSHA512AndAES_128";
private static Cipher getCipher(int mode, String password) throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
// Create secret key using password
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
// Create the cipher
byte[] salt = new byte[SALT_SIZE];
salt = password.getBytes();
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, PBEPARAMETERSPEC_ITERATION_COUNT);
Cipher cipher = Cipher.getInstance(ALGORITHM);
// this original line causes crash with the new algorithm, reporting:
// Exception in thread "main" java.security.InvalidAlgorithmParameterException: Missing parameter type: IV expected
// solved as per this S.O. post:
// https://stackoverflow.com/questions/29215274/how-do-i-properly-use-the-pbewithhmacsha512andaes-256-algorithm
cipher.init(mode, secretKey, pbeParameterSpec);
// this new line causes the encryption/decryption to apparently fail, giving results that look like this:
// �0�.�����j�"��ۗP#o˾���IYc� �we����)�Tq(f�C���.��njDt�.pG��
//cipher.init(mode, secretKey, cipher.getParameters());
return cipher;
}
Edit:
We are currently using PBEWITHSHA1ANDDESEDE to encrypt and decrypt. What we want to do is switch so that we use PBEWithHmacSHA512AndAES_128 to encrypt and decrypt. We do not expect that ciphertext encrypted with PBEWITHSHA1ANDDESEDE could be decrypted with PBEWithHmacSHA512AndAES_128.
To be clear, this is all taking place in a single class with a single execution in a sandbox as a proof of concept. We just want to get the PBEWithHmacSHA512AndAES_128 algorithm to encrypt, and immediately decrypt a single piece of non-critical content. We just want to replace PBEWITHSHA1ANDDESEDE at the recommendation of Veracode and we can't figure out why this isn't working.
By request, here is the entire PoC:
import java.io.ByteArrayOutputStream;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.security.spec.InvalidKeySpecException;
import javax.crypto.Cipher;
import javax.crypto.CipherOutputStream;
import javax.crypto.NoSuchPaddingException;
import javax.crypto.SecretKey;
import javax.crypto.SecretKeyFactory;
import javax.crypto.spec.PBEKeySpec;
import javax.crypto.spec.PBEParameterSpec;
public class FileEncryption {
private static String PASSWORD = "fake-password-for-stack-overflow-post";
private static String SOURCE_PATH = "";
private static String SOURCE_FILE;
private static String ENCRYPTED_FILE;
private static String DECRYPTED_FILE;
private static String ALGORITHM = "PBEWithHmacSHA512AndAES_128";
private static int SALT_SIZE = 8;
private static int PBEPARAMETERSPEC_ITERATION_COUNT = 100;
public static void main(String[] args) throws Exception {
if(args.length != 1) {
System.out.println("Only accept a single variable, the path to input.txt.");
System.out.println("(output will go to same dir)");
return;
}
SOURCE_PATH = args[0];
System.out.println("Set path: " + SOURCE_PATH);
if( SOURCE_PATH.charAt(SOURCE_PATH.length()-1) != '/' ) {
SOURCE_PATH += '/';
}
SOURCE_FILE = SOURCE_PATH + "plainfile.txt";
ENCRYPTED_FILE = SOURCE_PATH + "plainfile.encrypted.txt";
DECRYPTED_FILE = SOURCE_PATH + "plainfile.decrypted.txt";
encryptContent(SOURCE_FILE, ENCRYPTED_FILE, PASSWORD);
decryptContent(ENCRYPTED_FILE, DECRYPTED_FILE, PASSWORD);
}
private static void encryptContent(String inputFile, String outputFile, String password) throws Exception {
Cipher cipher = getCipher(Cipher.ENCRYPT_MODE, password);
performReadWrite(inputFile, outputFile, cipher);
}
private static void decryptContent(String inputFile, String outputFile, String password) throws Exception {
Cipher cipher = getCipher(Cipher.DECRYPT_MODE, password);
performReadWrite(inputFile, outputFile, cipher);
performRead(inputFile, cipher);
}
private static void performReadWrite(String inputFile, String outputFile, Cipher cipher)
throws FileNotFoundException, IOException {
FileInputStream inFile = new FileInputStream(inputFile);
FileOutputStream outFile = new FileOutputStream(outputFile);
CipherOutputStream cos = new CipherOutputStream(outFile, cipher);
int c;
while ((c = inFile.read()) != -1)
{
cos.write(c);
}
cos.close();
cos = null;
inFile.close();
inFile = null;
}
private static void performRead(String inputFile, Cipher cipher)
throws FileNotFoundException, IOException {
FileInputStream inFile = new FileInputStream(inputFile);
ByteArrayOutputStream os = new ByteArrayOutputStream();
CipherOutputStream cos = new CipherOutputStream(os, cipher);
int c;
while ((c = inFile.read()) != -1)
{
cos.write(c);
}
cos.close();
cos = null;
inFile.close();
inFile = null;
// aClass.outputStreamMethod(os);
String aString = new String(os.toByteArray(),"UTF-8");
System.out.println(aString);
}
private static Cipher getCipher(int mode, String password) throws NoSuchAlgorithmException, InvalidKeySpecException,
NoSuchPaddingException, InvalidKeyException, InvalidAlgorithmParameterException {
// Create secret key using password
PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray());
SecretKeyFactory secretKeyFactory = SecretKeyFactory.getInstance(ALGORITHM);
SecretKey secretKey = secretKeyFactory.generateSecret(pbeKeySpec);
// Create the cipher
byte[] salt = new byte[SALT_SIZE];
salt = password.getBytes();
PBEParameterSpec pbeParameterSpec = new PBEParameterSpec(salt, PBEPARAMETERSPEC_ITERATION_COUNT);
Cipher cipher = Cipher.getInstance(ALGORITHM);
cipher.init(mode, secretKey, cipher.getParameters());
return cipher;
}
}