java - 如何使用从密码派生的 key 正确加密和解​​密文件

标签 java encryption salt jce

我正在尝试制定使用“PBEWithHmacSHA256AndAES_256”标准加密和解密文件的正确过程。
据我了解,example code来自甲骨文。
我已经收集到需要盐,以及迭代计数和哈希标准。
所以我有我的主要方法,传递给加密方法:

  • 用户自定义密码new String(key).toCharArray()作为字节数组(使用此方法进行其他加密运行)
  • 安全随机 IV initVector作为字节数组
  • 纯文本文件inputFile作为字符串
  • 要创建的密文文件的名称outputFile作为字符串

  • 我已经按照代码示例编写了我认为对加密方法正确的代码。我通过将它们都附加到密文来存储要用于解密的盐和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");
    }
    
    然后我也有我的main方法,传入解密方法:
  • 用户自定义密码String inputKEY作为字符串(也将此方法用于其他加密运行)
  • inputIV 的字符串, 已作为 null 传入,因为不用于 PBE。
  • 密文文件inputFile作为字符串
  • 要创建的显示纯文本文件的名称 outputFile作为字符串
    私有(private)静态无效解密(字符串输入 key ,字符串输入IV,字符串输入文件,字符串输出文件){
    密码密码=空;
     //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 的理解有些不对劲,因此我实现它的方式可能是错误的。谁能指出似乎有什么问题?

    最佳答案

    主要问题是:

  • IV 和 Salt 不能写在 for 中。环形。
  • IV 存储在 encrypt不是 Base64 编码,而是在 decrypt 中进行 Base64 解码.
  • 16 字节的盐存储在 encrypt (不必要)Base64 编码,即存储 24 个字节。在 decrypt但是只加载了 16 个字节。

  • 还:
  • 在编码/解码时,有时没有指定编码,所以使用默认编码。
  • encryptdecrypt对 key 和 IV 使用不同的参数类型。
  • 代码中有很多复制/粘贴错误。

  • 注意:与您的代码相比,linked除了 key 之外,代码还确定来自密码和盐的 IV。
    在您的代码中,IV 被传递。因此,您必须确保 key /IV 对只能使用一次。通常为每个加密生成一个随机 IV。
    在以下代码中(基于您的代码,但为了简单起见,没有异常处理)这些问题已得到修复/优化。此外,该代码适用 FileInputStreamFileOutputStream而不是你的类(class)(但这不是必需的):
    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​ .

    关于java - 如何使用从密码派生的 key 正确加密和解​​密文件,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/63717001/

    相关文章:

    java - 删除另一个 map 中嵌套 map 的值

    java - 从 JTable 中删除渲染器

    passwords - 为什么密码盐称为 "salt"?

    java - Spring 4.x/3.x (Web MVC) REST API 和 JSON2 Post 请求,如何一劳永逸?

    java - Linux 中哈希命令的区别

    java - 客户端已知的客户端-服务器应用程序的加密和身份验证

    algorithm - 密码算法加密作业

    c#加密xml文件

    python - 为什么可以将bcrypt.hashpw同时用于哈希和验证密码?

    linux - 由于盐值是随机的,密码管理器如何重新检查密码?