2012-12-02 44 views
4

我試圖使用Java中的密碼將一個文件的內容加密到另一個文件中。該文件正在讀取到一個字節數組,加密到另一個字節數組,然後寫入新文件。不幸的是,當我嘗試反轉加密時,輸出文件被解密爲垃圾。在Java文件中使用基於密碼的加密

我強烈懷疑這個問題與每次使用相同密碼短語時生成相同的密鑰有關。我編寫了一個測試方法,可以在生成密鑰時將密鑰轉儲到文件中。密鑰直接記錄並以編碼形式記錄。前者每次都是一樣的,但後者由於某種原因總是不同的。

誠實地說,我不太瞭解加密方法,特別是在Java中。我只需要這些數據是適度安全的,並且加密不需要承受任何有大量時間和技能的人的攻擊。在此先感謝任何有此建議的人。

編輯:Esailija非常友善地指出我總是使用ENCRYPT_MODE設置密碼。我糾正使用布爾參數的問題,但現在我得到以下異常:

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

    } 

} 

回答

6

您正在使用Cipher.ENCRYPT_MODE進行解密和加密。您應該使用Cipher.DECRYPT_MODE來解密文件。

這已被修復,但您的布爾錯誤。對於加密和解密應該是真實的。我強烈建議不要使用false/true作爲函數參數,並始終使用枚舉類似Cipher.ENCRYPT ...繼續

然後,您正在加密到.encrypted文件,但試圖解密原始純文本文件。

然後你沒有應用填充加密。我很驚訝這實際上必須手動完成, 但padding is explained here。填充方案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(); 
     } 

    } 
} 
+0

我以某種方式設法忽略了這一點。感謝您指出。我最初在encryptFile/decryptFile方法內部創建了密鑰和密碼,然後將它們合併到makeCipher中。我在原始問題的代碼中解決了問題,但是我仍然遇到了編輯中提到的問題。 – user1870901

+0

@ user1870901請參閱編輯我的答案 – Esailija

+0

我非常肯定,這讓我更好地理解加密過程應該如何在Java中工作。我的印象是填充與密鑰本身而不是數據有關。就像你說的,我不認爲向數據添加/刪除額外的字節將不得不手動完成。我認爲這個問題涉及到加密過程中比實際更復雜的部分。我對整件事情感到沮喪,我非常感謝幫助。 – user1870901

1

這些對@Esailija的回答給出了Java中一些新特性的一些改進。

我使用CipherInputStream和CipherOutputStream類,代碼的長度和複雜度大大降低。

我也使用char []而不是String作爲密碼。

您可以使用System.console()。readPassword(「input password:」)以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; 
} 
+0

認真嗎?爲此而投票? – BPS

相關問題