在Java中对文件使用基于密码的加密

我正在尝试使用Java中的密码将一个文件的内容加密到另一个文件中。 该文件被读取到一个字节数组,加密到另一个字节数组,然后写入新文件。 不幸的是,当我尝试反转加密时,输出文件被解密为垃圾。

我强烈怀疑这个问题与每次使用相同的密码时生成相同的密钥有关。 我写了一个测试方法,只要生成一个密钥就会将密钥转储到文件中。 密钥以直接和编码forms记录。 前者每次都是相同的,但后者由于某种原因总是不同的。

老实说,我对加密方法知之甚少,特别是在Java方面。 我只需要数据具有中等安全性,并且加密不必承受具有大量时间和技能的任何人的攻击。 提前感谢任何有此建议的人。

编辑:Esailija很友好地指出我总是使用ENCRYPT_MODE设置密码。 我使用布尔参数更正了问题,但现在我得到以下exception:

javax.crypto.IllegalBlockSizeException:使用填充密码解密时,输入长度必须是8的倍数

这听起来像密码短语没有被正确使用。 我的印象是“PBEWithMD5AndDES”会将其散列为一个16字节的代码,这肯定是8的倍数。我想知道为什么密钥生成并被用于加密模式,但是当它尝试时它会抱怨在完全相同的条件下解密。

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(); } } } 

您正在使用Cipher.ENCRYPT_MODE进行解密和加密。 您应该使用Cipher.DECRYPT_MODE来解密文件。

这已得到修复,但你的布尔值是错误的。 对于加密应该是真的,对于解密应该是假的。 我强烈建议不要使用false/true作为函数参数,并始终使用像Cipher.ENCRYPT这样的Cipher.ENCRYPT ……继续前进

然后,您正在加密.encrypted文件,但尝试解密原始纯文本文件。

那么你没有应用填充加密。 我很惊讶这实际上必须手动完成,但这里解释了填充 。 填充方案PKCS5似乎在此隐式使用。

这是完整的工作代码,将加密文件写入test.txt.encrypted ,并将解密文件写入test.txt.decrypted.txt 。 在注释中解释了在加密中添加填充并在解密中删除它。

 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(); } } } 

考虑到Java中的一些新function,这些是@Esailija答案的一些改进。

我使用CipherInputStream和CipherOutputStream类,大大减少了代码的长度和复杂性。

我还使用char []而不是String作为密码。

您可以使用System.console()。readPassword(“输入密码:”)将密码作为char []获取,以便它永远不是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; }