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

时间:2012-12-02 20:32:07

标签: java encryption

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

    }

}

2 个答案:

答案 0 :(得分:7)

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

    }
}

答案 1 :(得分: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;
}