java—如何使用从密码派生的密钥正确加密和解密文件

oo7oh9g9  于 2021-06-30  发布在  Java
关注(0)|答案(1)|浏览(485)

我正在尝试使用“pbewithhmacsha256andaesĀu256”标准制定加密和解密文件的正确过程。
从我看到的oracle示例代码的理解来看。
我已经收集到需要salt,以及迭代计数和散列标准。
所以我有我的主要方法,传入加密方法:
用户定义的密码 new String(key).toCharArray() 作为字节数组(将此方法用于其他加密运行)
安全随机静脉注射 initVector 作为字节数组
纯文本文件 inputFile 作为字符串
要创建的密文文件的名称 outputFile 作为字符串
我按照代码示例编写了我认为正确的加密方法。我把salt和iv存储起来,通过把它们都加到密文中来解密。

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

然后我还有我的主要方法,传递到解密方法:
用户定义的密码 String inputKEY 作为字符串(也将此方法用于其他解密运行)
一根绳子 inputIV ,已作为null传入,因为不用于pbe。
密文文件 inputFile 作为字符串
要创建的revealplaintext文件的名称 outputFile 作为字符串
私有静态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的理解有些不对劲,因此我实现它的方式可能是错误的。有人能指出哪里出了问题吗?

ws51t4hk

ws51t4hk1#

主要问题是:
iv和盐不能写在 for 循环。
静脉注射保存在 encrypt 不是base64编码的,而是base64解码的 decrypt .
16字节的salt存储在 encrypt (不必要的)base64编码,即存储24字节。在 decrypt 但是,只加载了16个字节。
也:
在编码/解码时,有时不指定编码,因此使用默认编码。 encrypt 以及 decrypt 对key和iv使用不同的参数类型。
代码中有许多复制/粘贴错误。
注意:与您的代码不同,链接的代码除了决定键外还决定了来自密码和salt的iv。在你的代码中,iv是通过的。因此,您必须确保密钥/iv对只能使用一次。通常为每个加密生成一个随机iv。
在下面的代码中(基于您的代码,但为简单起见,没有异常处理),这些问题已得到修复/优化。此外,本守则适用 FileInputStream 以及 FileOutputStream 而不是你的课程(但这不是必需的):

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

编辑-关于盐和iv的阅读 decrypt :
正如gpi在评论中指出的, FileInputStream.read(byte[] b) 一般阅读 b.length 字节,但这不能保证。更可靠的方法是确定读取数据的长度,并在循环中调用该方法,直到数据完成。另一种选择是使用 InputStream.readNBytes​(int len) ,保证 len 字节(除非遇到流结尾或抛出异常),正如zabuzard所建议的。在代码中,现在使用了后者,即。 read 被替换为 readNBytes​ .

相关问题