在 Java 中对文件使用基于密码的加密 [英] Using password-based encryption on a file in Java

查看:22
本文介绍了在 Java 中对文件使用基于密码的加密的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

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

我强烈怀疑该问题与每次使用相同密码时生成相同的密钥有关.我编写了一种测试方法,每当生成密钥时,该方法就会将密钥转储到文件中.密钥既直接记录又以编码形式记录.前者每次都一样,但后者总是因为某种原因而不同.

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

老实说,我对加密方法知之甚少,尤其是在 Java 中.我只需要数据适度安全,并且加密不必承受来自任何有大量时间和技能的人的攻击.在此先感谢任何对此有建议的人.

In all honesty, I don't know a great deal about encryption methods, especially in Java. I only need the data to be moderately secure, and the encryption doesn't have to withstand an attack from anyone with significant time and skills. Thanks in advance to anyone who has advice on this.

Esailija 非常友好地指出我总是使用 ENCRYPT_MODE 设置密码.我使用布尔参数更正了问题,但现在出现以下异常:

Esailija was kind enough to point out that I was always setting the cipher with ENCRYPT_MODE. I corrected the problem using a boolean argument, but now I'm getting the following exception:

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

javax.crypto.IllegalBlockSizeException: Input length must be multiple of 8 when decrypting with padded cipher

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

That sounds to me like the passphrase isn't being used properly. I was under the impression that "PBEWithMD5AndDES" would hash it into a 16 byte code, which most certainly is a multiple of 8. I'm wondering why the key generates and gets used just fine for encryption mode, but then it complains when trying to decrypt under the exact 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 = "
" + "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 来解密文件.

You are using the Cipher.ENCRYPT_MODE for both, decrypting and encrypting. You should use Cipher.DECRYPT_MODE for decrypting the file.

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

That has been fixed, but your boolean is wrong. It should be true for encrypt and false for decrypt. I would strongly recommend against using false/true as function arguments and always use enum like Cipher.ENCRYPT... moving on

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

Then you are encrypting to .encrypted file, but trying to decrypt the original plain text file.

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

Then you are not applying padding to encryption. I am surprised this actually has to be done manually, but padding is explained here. The padding scheme PKCS5 appeared to be implicitly used here.

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

This is full working code, writing encrypted file to test.txt.encrypted, and decrypted file to test.txt.decrypted.txt. Adding padding in 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 = "
" + "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 中对文件使用基于密码的加密的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆