如何使用从密码派生的密钥正确加密和解​​密文件 [英] How to correctly encrypt and decrypt a file using a secret key derived from a password

查看:74
本文介绍了如何使用从密码派生的密钥正确加密和解​​密文件的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

我正在尝试制定正确的过程来使用"PBEWithHmacSHA256AndAES_256"加密和解密文件.标准.

I am trying to work out the correct process to encrypt and decrypt a file using the "PBEWithHmacSHA256AndAES_256" standard.

根据我对本我已经收集到需要盐,以及迭代计数和哈希标准.

I have gathered that a salt is needed, as well as an iteration count and the hash standard.

所以我有我的主要方法,传递给加密方法:

So I have my main method, passing into the encryption method:

  1. 用户定义的密码 new String(key).toCharArray()作为字节数组(使用此方法进行其他加密运行)
  2. 作为字节数组的安全随机IV initVector
  3. 纯文本文件 inputFile 作为字符串
  4. 要创建的密文文件名称 outputFile 作为字符串
  1. a user-defined password new String(key).toCharArray() as a byte array (using this method for other encryption runs)
  2. a secure random IV initVector as a byte array
  3. the plain text file inputFile as a String
  4. the name of, to be created, ciphertext file outputFile as a String

我遵循了代码示例,对我认为对加密方法正确的代码进行了编码.然后,我将盐和IV都添加到密文中,以用于解密.

I have followed the code example to code up what I believe is correct for the encrypt method. And I am storing the salt and IV to be used for decryption by appending them both to the ciphertext.

private static void encrypt(byte[] key, byte[] initVector, String inputFile, String outputFile) //exceptions for throws... {
    //Initalisation for encryption
    Cipher cipher;

    byte[] salt = new byte[16];

        SecureRandom rand = new SecureRandom();

        // Salt randomly generated with base64
        rand.nextBytes(salt);
        System.out.println("my salt should be" + Base64.getEncoder().encodeToString(salt));
        salt = Base64.getEncoder().encode(salt);

        // Iteration count
        int count = 1000;

        IvParameterSpec iv = new IvParameterSpec(initVector);
        
        // Create PBE parameter set
        PBEParameterSpec pbeParamSpec = new PBEParameterSpec(Base64.getDecoder().decode(salt), count, iv);                
        // Convert pass into SecretKey object
        PBEKeySpec pbeKeySpec = new PBEKeySpec(new String(key).toCharArray());
        SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
        SecretKey pbeKey;
        try {
            pbeKey = keyFac.generateSecret(pbeKeySpec);
        } catch (InvalidKeySpecException e) {
            System.out.println("Sorry, the password specified cannot be used as a secret key");
            System.out.println("Please check that your password uses valid characters");
            return;
        }

        // Create PBE Cipher
        cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");

        // Initialize PBE Cipher with key and parameters
        cipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);
    }

    //File error checking and file handling (i.e. generating file paths)...

    System.out.println("Secret key is " + Base64.getEncoder().encodeToString(key));
    System.out.println("IV is " + Base64.getEncoder().encodeToString(initVector));

    //Special file reading and writing with 'Cipher Stream'
    try (InputStream fin = FileEncryptor.class.getResourceAsStream(loadFile.getName());
            OutputStream fout = Files.newOutputStream(saveFile);

            CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher) {
            }) {
        final byte[] bytes = new byte[1024];
        for(int length=fin.read(bytes); length!=-1; length = fin.read(bytes)){

                fout.write(initVector);
                fout.write(salt);

            cipherOut.write(bytes, 0, length);

        }
    } catch (IOException e) {
        System.out.println("Something went wrong with reading and writing these files!");
        System.out.println("Please check you have the latest version of this program");
        System.out.println("Contact your IT admin to make sure you have sufficient privileges");
    }
    System.out.println("SUCCESS! Encryption finished, saved at specified location");
}

然后我还有我的主要方法,传递给解密方法:

Then I also have my main method, passing into the decryption method:

  1. 用户定义的密码字符串inputKEY 作为字符串(也使用此方法进行其他解密运行)

  1. a user-defined password String inputKEY as a string (also using this method for other dencryption runs)

用于 inputIV 的字符串,由于未用于PBE,因此已作为null传递.

a String for inputIV , has been passed in as null, since not used for PBE.

密文文件 inputFile 作为字符串

要创建的名称,以字符串形式显示纯文本文件 outputFile

the name of, to be created, revealplaintext file outputFile as a String

私有静态无效解密(String inputKEY,String inputIV,String inputFile,String outputFile){密码cipher = null;

private static void decrypt(String inputKEY, String inputIV, String inputFile, String outputFile) { Cipher cipher = null;

 //File error checking and file handling (i.e. generating file paths)...

 InputStream encryptedData = Files.newInputStream(loadFilePath);


     PBEKeySpec pbeKeySpec = new PBEKeySpec(inputKEY.toCharArray());
     SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
     SecretKey pbeKey = null;
     try {
         pbeKey = keyFac.generateSecret(pbeKeySpec);
     } catch (InvalidKeySpecException e) {
         // TODO Auto-generated catch block
         e.printStackTrace();
     }
     byte[] initVect = new byte[16];
     encryptedData.read(initVect);

     IvParameterSpec iv = new IvParameterSpec(Base64.getDecoder().decode(initVect);

     byte[] salt = new byte[16];
     encryptedData.read(salt);

     PBEParameterSpec pbeParamSpec = new PBEParameterSpec(Base64.getDecoder().decode(salt), 1000, iv);  
     cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");

     System.out.println("my salt should be" + Base64.getEncoder().encodeToString(Base64.getDecoder().decode(salt)));

     cipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec); 

 try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher);    
         OutputStream decryptedOut = Files.newOutputStream(saveFile)){
     final byte[] bytes = new byte[1024];
     for(int length=decryptStream.read(bytes); length!=-1; length = decryptStream.read(bytes)){
         decryptedOut.write(bytes, 0, length);
     }
 } catch (IOException e) { //This is caught when decryption is run
     System.out.println("Something went wrong with reading and writing these files!");
     System.out.println("Please check you have the latest version of this program");
     System.out.println("Contact your IT admin to make sure you have sufficient privileges");
 }

 System.out.println("SUCESS! Decryption finished, saved at specified location");

}

根据我对PBE的理解,我认为有些事情是不对的,因此,我实施它的方式可能是错误的.谁能指出似乎有问题的地方吗?

I believe something is not right with my understanding of PBE, and thus the way I implemented it is probably wrong. Could anyone point out what seems to be wrong?

推荐答案

主要问题是:

  • IV和Salt不能写在 for 循环内.
  • IV以 encrypt 进行存储,而不是通过Base64编码,但是通过 decrypt 进行了Base64解码.
  • 将16个字节的盐存储在 encrypt 中(不必要)以Base64编码,即存储24个字节.在 decrypt 中,仅加载16个字节.
  • IV and Salt must not be written inside the for loop.
  • The IV is stored in encrypt not Base64 encoded, but is Base64 decoded in decrypt.
  • The 16 bytes salt is stored in encrypt (unnecessarily) Base64 encoded, i.e. 24 bytes are stored. In decrypt however only 16 bytes are loaded.

也:

  • 在编码/解码时,有时未指定编码,因此使用默认编码.
  • encrypt decrypt 为key和IV使用不同的参数类型.
  • 代码中有很多复制/粘贴错误.
  • Upon encoding/decoding, sometimes no encoding is specified, so the default encoding is used.
  • encrypt and decrypt use different parameter types for key and IV.
  • There are many copy/paste errors in the code.

注意:与您的代码相反,

Note: In contrast to your code, the linked code determines besides the key also the IV from password and salt. In your code the IV is passed. Thus you have to ensure that a key/IV pair may only be used once. Usually a random IV is generated for each encryption.

在下面的代码(基于您的代码,但是为了简单起见,没有异常处理)中,这些问题已得到解决/优化.此外,该代码将应用 FileInputStream FileOutputStream 而不是您的类(但这不是必需的):

In the following code (which is based on your code, but for simplicity without exception handling) these issues are fixed/optimized. Furthermore, the code applies FileInputStream and FileOutputStream instead of your classes (but this is not required):

private static void encrypt(String key, byte[] initVector, String inputFile, String outputFile) throws Exception {

    // Key
    PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
    SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
    SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

    // IV
    IvParameterSpec iv = new IvParameterSpec(initVector);

    // Salt
    SecureRandom rand = new SecureRandom();
    byte[] salt = new byte[16];
    rand.nextBytes(salt);

    // ParameterSpec
    int count = 1000; // should be larger, see Michael Fehr's comment
    PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count, iv);

    // Cipher
    Cipher cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
    cipher.init(Cipher.ENCRYPT_MODE, pbeKey, pbeParamSpec);

    try (FileInputStream fin = new FileInputStream(inputFile);
         FileOutputStream fout = new FileOutputStream(outputFile);
         CipherOutputStream cipherOut = new CipherOutputStream(fout, cipher)) {
    
        // Write IV, Salt
        fout.write(initVector);
        fout.write(salt);
    
        // Encrypt
        final byte[] bytes = new byte[1024];
        for (int length = fin.read(bytes); length != -1; length = fin.read(bytes)) {
            cipherOut.write(bytes, 0, length);
        }
    } 
}

private static void decrypt(String key, byte[] initVect, String inputFile, String outputFile) throws Exception {

    try (FileInputStream encryptedData = new FileInputStream(inputFile);
         FileOutputStream decryptedOut = new FileOutputStream(outputFile)) {

        // Key
        PBEKeySpec pbeKeySpec = new PBEKeySpec(key.toCharArray());
        SecretKeyFactory keyFac = SecretKeyFactory.getInstance("PBEWithHmacSHA256AndAES_256");
        SecretKey pbeKey = keyFac.generateSecret(pbeKeySpec);

        // Read IV
        if (initVect == null) {
            initVect = encryptedData.readNBytes(16);
        }
        IvParameterSpec iv = new IvParameterSpec(initVect);

        // Read salt
        byte[] salt = encryptedData.readNBytes(16);

        // ParameterSpec
        int count = 1000;
        PBEParameterSpec pbeParamSpec = new PBEParameterSpec(salt, count, iv);

        // Cipher
        Cipher cipher = Cipher.getInstance("PBEWithHmacSHA256AndAES_256");
        cipher.init(Cipher.DECRYPT_MODE, pbeKey, pbeParamSpec);

        try (CipherInputStream decryptStream = new CipherInputStream(encryptedData, cipher)) {
        
            // Decrypt
            final byte[] bytes = new byte[1024];
            for (int length = decryptStream.read(bytes); length != -1; length = decryptStream.read(bytes)) {
                decryptedOut.write(bytes, 0, length);
            }
        } 
    }
}

编辑-关于在 decrypt 中读取盐和IV的信息:
正如 GPI 在其评论中指出的,
FileInputStream.read(byte [] b) 通常读取 b.length 字节,但这不能保证.更可靠的方法是确定读取数据的长度,并在循环中调用该方法,直到数据完成为止.另一种选择是使用 InputStream.readNBytes((int len) ),保证可以读取 len 个字节(除非末尾)如 Zabuzard 所建议的那样,遇到流或抛出异常).在代码中,现在使用后者,即 read readNBytes 代替.

EDIT - Concerning the reading of salt and IV in decrypt:
As GPI pointed out in their comment, FileInputStream.read(byte[] b) generally reads b.length bytes, but this is not guaranteed. More robust is to determine the length of the read data and call the method in a loop until the data is complete. Another alternative is the use of InputStream.readNBytes​(int len), which is guaranteed to read len bytes (unless end of stream is encountered or an exception is thrown), as Zabuzard has suggested. In the code, the latter is now used, i.e. read was replaced by readNBytes​.

这篇关于如何使用从密码派生的密钥正确加密和解​​密文件的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

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