Using password based encryption in a file in Java

I am trying to encrypt the contents of one file to another file using a passphrase in Java. The file is read into a byte array, encrypted into another byte array, and then written to a new file. Unfortunately, when I try to change the encryption, the output file is decrypted as garbage.

I strongly suspect that the problem is with generating an identical key every time the same phrase is used. I wrote a testing method that dumps a key into a file whenever generated. The key is recorded both directly and in encoded form. The former is identical each time, but for some reason, the latter is always different.

Honestly, I am not very versed in encryption methods, especially in Java. I only need data that should be moderately secure, and encryption should not withstand attacks from anyone with significant time and skills. Thanks in advance to everyone who has advice on this.

Edit: Esailija was kind enough to indicate that I always set the cipher using ENCRYPT_MODE. I fixed the problem with a boolean argument, but now I get the following exception:

javax.crypto.IllegalBlockSizeException: input length must be a multiple of 8 when decrypting with the addition of encryption

It sounds like the phrase is not being used properly. I got the impression that "PBEWithMD5AndDES" would hash it into a 16-byte code, which, of course, is a multiple of 8. I wonder why the key is generated and used just fine for encryption mode, but then it complains when it tries to decrypt in those same conditions.

import java.various.stuff; /**Utility class to encrypt and decrypt files**/ public class FileEncryptor { //Arbitrarily selected 8-byte salt sequence: private static final byte[] salt = { (byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7, (byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17 }; private static Cipher makeCipher(String pass, Boolean decryptMode) throws GeneralSecurityException{ //Use a KeyFactory to derive the corresponding key from the passphrase: PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(keySpec); //Create parameters from the salt and an arbitrary number of iterations: PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42); /*Dump the key to a file for testing: */ FileEncryptor.keyToFile(key); //Set up the cipher: Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); //Set the cipher mode to decryption or encryption: if(decryptMode){ cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec); } else { cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec); } return cipher; } /**Encrypts one file to a second file using a key derived from a passphrase:**/ public static void encryptFile(String fileName, String pass) throws IOException, GeneralSecurityException{ byte[] decData; byte[] encData; File inFile = new File(fileName); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, false); //Read in the file: FileInputStream inStream = new FileInputStream(inFile); decData = new byte[(int)inFile.length()]; inStream.read(decData); inStream.close(); //Encrypt the file data: encData = cipher.doFinal(decData); //Write the encrypted data to a new file: FileOutputStream outStream = new FileOutputStream(new File(fileName + ".encrypted")); outStream.write(encData); outStream.close(); } /**Decrypts one file to a second file using a key derived from a passphrase:**/ public static void decryptFile(String fileName, String pass) throws GeneralSecurityException, IOException{ byte[] encData; byte[] decData; File inFile = new File(fileName); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, true); //Read in the file: FileInputStream inStream = new FileInputStream(inFile); encData = new byte[(int)inFile.length()]; inStream.read(encData); inStream.close(); //Decrypt the file data: decData = cipher.doFinal(encData); //Write the decrypted data to a new file: FileOutputStream target = new FileOutputStream(new File(fileName + ".decrypted.txt")); target.write(decData); target.close(); } /**Record the key to a text file for testing:**/ private static void keyToFile(SecretKey key){ try { File keyFile = new File("C:\\keyfile.txt"); FileWriter keyStream = new FileWriter(keyFile); String encodedKey = "\n" + "Encoded version of key: " + key.getEncoded().toString(); keyStream.write(key.toString()); keyStream.write(encodedKey); keyStream.close(); } catch (IOException e) { System.err.println("Failure writing key to file"); e.printStackTrace(); } } } 
+4
source share
2 answers

Used by Cipher.ENCRYPT_MODE for decryption and encryption. You must use Cipher.DECRYPT_MODE to decrypt the file.

This is fixed, but your boolean value is incorrect. This should be true for encryption and false for decryption. I would strongly recommend using false/true as arguments to the function and always use an enumeration of type Cipher.ENCRYPT ... moving around

Then you encrypt the .encrypted file, but try to decrypt the original text file.

Then you do not apply the encryption add-on. I am surprised that this really needs to be done manually, but the addition is described here. The PKCS5 gasket scheme seems to be implicitly used here.

This is the full working code, writing the encrypted file on test.txt.encrypted and the decrypted file on test.txt.decrypted.txt . Adding additions to encryption and removing it in decryption is explained in the comments.

 import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.FileWriter; import java.io.IOException; import java.security.GeneralSecurityException; import java.util.Arrays; import javax.crypto.Cipher; import javax.crypto.SecretKey; import javax.crypto.SecretKeyFactory; import javax.crypto.spec.PBEKeySpec; import javax.crypto.spec.PBEParameterSpec; public class FileEncryptor { public static void main( String[] args ) { try { encryptFile( "C:\\test.txt", "password" ); decryptFile( "C:\\test.txt", "password" ); } catch (IOException e) { // TODO Auto-generated catch block e.printStackTrace(); } catch (GeneralSecurityException e) { // TODO Auto-generated catch block e.printStackTrace(); } } //Arbitrarily selected 8-byte salt sequence: private static final byte[] salt = { (byte) 0x43, (byte) 0x76, (byte) 0x95, (byte) 0xc7, (byte) 0x5b, (byte) 0xd7, (byte) 0x45, (byte) 0x17 }; private static Cipher makeCipher(String pass, Boolean decryptMode) throws GeneralSecurityException{ //Use a KeyFactory to derive the corresponding key from the passphrase: PBEKeySpec keySpec = new PBEKeySpec(pass.toCharArray()); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(keySpec); //Create parameters from the salt and an arbitrary number of iterations: PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 42); /*Dump the key to a file for testing: */ FileEncryptor.keyToFile(key); //Set up the cipher: Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); //Set the cipher mode to decryption or encryption: if(decryptMode){ cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec); } else { cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec); } return cipher; } /**Encrypts one file to a second file using a key derived from a passphrase:**/ public static void encryptFile(String fileName, String pass) throws IOException, GeneralSecurityException{ byte[] decData; byte[] encData; File inFile = new File(fileName); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, true); //Read in the file: FileInputStream inStream = new FileInputStream(inFile); int blockSize = 8; //Figure out how many bytes are padded int paddedCount = blockSize - ((int)inFile.length() % blockSize ); //Figure out full size including padding int padded = (int)inFile.length() + paddedCount; decData = new byte[padded]; inStream.read(decData); inStream.close(); //Write out padding bytes as per PKCS5 algorithm for( int i = (int)inFile.length(); i < padded; ++i ) { decData[i] = (byte)paddedCount; } //Encrypt the file data: encData = cipher.doFinal(decData); //Write the encrypted data to a new file: FileOutputStream outStream = new FileOutputStream(new File(fileName + ".encrypted")); outStream.write(encData); outStream.close(); } /**Decrypts one file to a second file using a key derived from a passphrase:**/ public static void decryptFile(String fileName, String pass) throws GeneralSecurityException, IOException{ byte[] encData; byte[] decData; File inFile = new File(fileName+ ".encrypted"); //Generate the cipher using pass: Cipher cipher = FileEncryptor.makeCipher(pass, false); //Read in the file: FileInputStream inStream = new FileInputStream(inFile ); encData = new byte[(int)inFile.length()]; inStream.read(encData); inStream.close(); //Decrypt the file data: decData = cipher.doFinal(encData); //Figure out how much padding to remove int padCount = (int)decData[decData.length - 1]; //Naive check, will fail if plaintext file actually contained //this at the end //For robust check, check that padCount bytes at the end have same value if( padCount >= 1 && padCount <= 8 ) { decData = Arrays.copyOfRange( decData , 0, decData.length - padCount); } //Write the decrypted data to a new file: FileOutputStream target = new FileOutputStream(new File(fileName + ".decrypted.txt")); target.write(decData); target.close(); } /**Record the key to a text file for testing:**/ private static void keyToFile(SecretKey key){ try { File keyFile = new File("C:\\keyfile.txt"); FileWriter keyStream = new FileWriter(keyFile); String encodedKey = "\n" + "Encoded version of key: " + key.getEncoded().toString(); keyStream.write(key.toString()); keyStream.write(encodedKey); keyStream.close(); } catch (IOException e) { System.err.println("Failure writing key to file"); e.printStackTrace(); } } } 
+6
source

These are some of the improvements in @Esailija's answer, given some of the new Java features.

My use of the CipherInputStream and CipherOutputStream classes greatly reduces the length and complexity of the code.

I also use char [] instead of String for password.

You can use System.console (). readPassword ("input password:") to get the password as char [] so that it is never a string.

 public static void encryptFile(String inFileName, String outFileName, char[] pass) throws IOException, GeneralSecurityException { Cipher cipher = PasswordProtectFile.makeCipher(pass, true); try (CipherOutputStream cipherOutputStream = new CipherOutputStream(new FileOutputStream(outFileName), cipher); BufferedInputStream bis = new BufferedInputStream(new FileInputStream(inFileName))) { int i; while ((i = bis.read()) != -1) { cipherOutputStream.write(i); } } } public static void decryptFile(String inFileName, String outFileName, char[] pass) throws GeneralSecurityException, IOException { Cipher cipher = PasswordProtectFile.makeCipher(pass, false); try (CipherInputStream cipherInputStream = new CipherInputStream(new FileInputStream(inFileName), cipher); BufferedOutputStream bos = new BufferedOutputStream(new FileOutputStream(outFileName))) { int i; while ((i = cipherInputStream.read()) != -1) { bos.write(i); } } } private static Cipher makeCipher(char[] pass, Boolean decryptMode) throws GeneralSecurityException { // Use a KeyFactory to derive the corresponding key from the passphrase: PBEKeySpec keySpec = new PBEKeySpec(pass); SecretKeyFactory keyFactory = SecretKeyFactory.getInstance("PBEWithMD5AndDES"); SecretKey key = keyFactory.generateSecret(keySpec); // Create parameters from the salt and an arbitrary number of iterations: PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, 43); // Set up the cipher: Cipher cipher = Cipher.getInstance("PBEWithMD5AndDES"); // Set the cipher mode to decryption or encryption: if (decryptMode) { cipher.init(Cipher.ENCRYPT_MODE, key, pbeParamSpec); } else { cipher.init(Cipher.DECRYPT_MODE, key, pbeParamSpec); } return cipher; } 
+1
source

All Articles