【问题标题】:Using password-based encryption on a file in Java在 Java 中对文件使用基于密码的加密
【发布时间】:2012-12-02 20:32:06
【问题描述】:

我正在尝试使用 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();
        }

    }

}

【问题讨论】:

    标签: java encryption


    【解决方案1】:

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

    已修复,但您的布尔值错误。加密应该为真,解密应该为假。我强烈建议不要使用 false/true 作为函数参数,并始终使用枚举,如 Cipher.ENCRYPT... 继续

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

    那么您没有将填充应用于加密。我很惊讶这实际上必须手动完成, 但是padding is explained here。填充方案 PKCS5 似乎在这里隐式使用。

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

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

    【讨论】:

    • 我设法忽略了这一点。感谢您指出。我最初在 encryptFile/decryptFile 方法中创建了密钥和密码,然后它们组合成 makeCipher。我在原始问题的代码中修复了问题,但我仍然遇到我所做的编辑中提到的问题。
    • 我很确定这让我更好地理解了加密过程应该如何在 Java 中工作。我的印象是填充与密钥本身而不是数据有关。就像您说的那样,我认为不必手动向数据中添加/删除额外的字节。我认为这个问题涉及比实际更复杂的加密过程部分。我对整个事情感到非常沮丧,我非常感谢您的帮助。
    • 在您的 keyToFile 方法中,getEncoded 返回一个字节[]。调用 toString 可能不会给你任何有用的东西。您可能想使用 Base64.getEncoder().encodeToString()。
    • 另外,我建议使用 Console 类将密码读入 char[] 而不是 String。这样一旦不再需要它就可以立即清除。
    【解决方案2】:

    鉴于 Java 中的一些新功能,这些是对 @Esailija 答案的一些改进。

    通过使用 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;
    }
    

    【讨论】:

      猜你喜欢
      • 2010-12-26
      • 1970-01-01
      • 2015-04-21
      • 2012-11-22
      • 1970-01-01
      • 2010-11-02
      • 2018-05-22
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多