【问题标题】:How to handle BadPaddingException During AES256 encryption in C# and decryption in Java如何在 C# 中的 AES256 加密和 Java 中的解密期间处理 BadPaddingException
【发布时间】:2019-01-11 06:16:00
【问题描述】:

我不知道为什么会出现错误。

线程“main”javax.crypto.BadPaddingException 中的异常:给定最终块未正确填充。如果在解密过程中使用了错误的密钥,则可能会出现此类问题。

我了解在解密过程中使用了不正确的密钥会发生此错误。但是,如果您查看下面的测试结果,您可以看到 C# 和 Java 都是相同的(Key、IV、Salt 是 Base64 编码的)。

  1. C#测试结果

  1. Java 测试结果

一样!(Key、IV、Salt)

但是产生了当前的BadpaddingException错误。可能是什么问题呢? 我正在附加我的源文件。

  1. C#(加密)

    class AES {
            private readonly static string keyStr = "This is Key";
            private readonly static string vector = "This is Vector";

            public static Rfc2898DeriveBytes MakeKey(string password){

                byte[] keyBytes = System.Text.Encoding.UTF8.GetBytes(password);
                byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
                Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(keyBytes, saltBytes, 65536);

                return result;
            }

            public static Rfc2898DeriveBytes MakeVector(string vector){

                byte[] vectorBytes = System.Text.Encoding.UTF8.GetBytes(vector);
                byte[] saltBytes = SHA512.Create().ComputeHash(vectorBytes);
                Rfc2898DeriveBytes result = new Rfc2898DeriveBytes(vectorBytes, saltBytes, 65536);

                return result;
            }

            public static void Encrypt(String inputFile, String outputFile) {
                using (RijndaelManaged aes = new RijndaelManaged()){
                    //Create Key and Vector
                    Rfc2898DeriveBytes key = AES.MakeKey(AES.keyStr);
                    Rfc2898DeriveBytes vector = AES.MakeVector(AES.vector);

                    //AES256
                    aes.BlockSize = 128;
                    aes.KeySize = 256;

                    // It is equal in java 
                    // Cipher _Cipher = Cipher.getInstance("AES/CBC/PKCS5PADDING");    
                    aes.Mode = CipherMode.CBC; 
                    aes.Padding = PaddingMode.PKCS7; 
                    aes.Key = key.GetBytes(32); //256bit key
                    aes.IV  = vector.GetBytes(16); //128bit block size


                    //processing Encrypt
                    ICryptoTransform encryptor = aes.CreateEncryptor(aes.Key, aes.IV);
                    byte[] encrypted;

                    using (MemoryStream msEncrypt = new MemoryStream()) { 
                            using (CryptoStream csEncrypt = new CryptoStream(msEncrypt, encryptor, CryptoStreamMode.Write)) {
                                byte[] inputBytes = File.ReadAllBytes(inputFile);
                                csEncrypt.Write(inputBytes, 0, inputBytes.Length);
                            }
                            encrypted = msEncrypt.ToArray();     
                        }
                        string encodedString = Convert.ToBase64String(encrypted);
                        File.WriteAllText(outputFile, encodedString);
                    }
                }
            }

  1. Java(解密)

    public class AES256File {
        private static final String algorithm = "AES";
        private static final String blockNPadding = algorithm+"/CBC/PKCS5Padding";
        private static final String password = "This is Key";
        private static final String IV = "This is Vector";

        private static IvParameterSpec ivSpec;
        private static Key keySpec;

        public static void MakeKey(String password) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            byte[] keyBytes = password.getBytes("UTF-8");

            // C# : byte[] saltBytes = SHA512.Create().ComputeHash(keyBytes);
            byte[] saltBytes = digest.digest(keyBytes);

            //256bit
            PBEKeySpec pbeKeySpec = new PBEKeySpec(password.toCharArray(), saltBytes, 65536, 256);
            Key secretKey = factory.generateSecret(pbeKeySpec);

            byte[] key = new byte[32];
            System.arraycopy(secretKey.getEncoded(), 0, key, 0, 32);

            SecretKeySpec secret = new SecretKeySpec(key, "AES");
            setKeySpec(secret);
        }

        public static void MakeVector(String IV) throws NoSuchAlgorithmException, UnsupportedEncodingException, InvalidKeySpecException{
            SecretKeyFactory factory = SecretKeyFactory.getInstance("PBKDF2WithHmacSHA1");
            MessageDigest digest = MessageDigest.getInstance("SHA-512");
            byte[] vectorBytes = IV.getBytes("UTF-8");
            byte[] saltBytes = digest.digest(vectorBytes);

            // 128bit
            PBEKeySpec pbeKeySpec = new PBEKeySpec(IV.toCharArray(), saltBytes, 65536, 128);
            Key secretIV = factory.generateSecret(pbeKeySpec);

            byte[] iv = new byte[16];
            System.arraycopy(secretIV.getEncoded(), 0, iv, 0, 16);

            IvParameterSpec ivSpec = new IvParameterSpec(iv);
            setIvSpec(ivSpec);
        }

        public void decrypt(File source, File dest) throws Exception {
            Cipher c = Cipher.getInstance(blockNPadding);
            c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
            fileProcessing(source, dest, c);
        }

        public  void fileProcessing(File source, File dest, Cipher c) throws Exception{
            InputStream input = null;
            OutputStream output = null;

            try{
                input = new BufferedInputStream(new FileInputStream(source));
                output = new BufferedOutputStream(new FileOutputStream(dest));
                byte[] buffer = new byte[input.available()];
                int read = -1;
                while((read = input.read(buffer)) != -1){
                    output.write(c.update(buffer, 0, read));
                }
                byte[] deryptedBytes = c.doFinal(buffer); // -----------------------> Error!! Showing! 
                byte[] decodedBytes = Base64.getDecoder().decode(deryptedBytes);
                String decodeString = new String(decodedBytes, "UTF-8");
                decodedBytes = decodeString.getBytes(StandardCharsets.UTF_8);
                output.write(decodedBytes);

            }finally{
                if(output != null){
                    try{output.close();}catch(IOException e){}
                }
                if(input != null){
                    try{input.close();}catch(IOException e){}
                }
            }
        }

我已验证如下。

  1. C# 中的验证密钥和 IV

    //Key Verification
        var salt = Convert.ToBase64String(saltBytes);
                Console.Write("Salt Result : ");
                Console.WriteLine(salt);

        var result_test = Convert.ToBase64String(result.GetBytes(32));
                Console.Write("Key Test Result: ");
                Console.WriteLine(result_test);
    //IV Verification (Salt is Using same code)
        var result_test = Convert.ToBase64String(result.GetBytes(16));
                Console.Write("IV Test Result: ");
                Console.WriteLine(result_test);
  1. Java 中的验证密钥和 IV

    //Key Verification
        /* print Salt */
        String base64 = Base64.getEncoder().encodeToString(saltBytes);
        System.out.println("Salt Result : " + base64);

        /* print Key */
        String result_test = Base64.getEncoder().encodeToString(key);
        System.out.println("Key Test Result : " + result_test);

        /* print generated Key */
        System.out.println("Secret Key Result : " + Base64.getEncoder().encodeToString(secret.getEncoded()));

    //IV Verification (Salt is Using same code)
        /* print IV */
        String result_test = Base64.getEncoder().encodeToString(iv);
        System.out.println("IV Test Result : " + result_test);

        /* print generated IV */
        System.out.println("IV Result : " + Base64.getEncoder().encodeToString(ivSpec.getIV()));

更新

c# .netframework 4.5 / Java8 修改了@Topaco 所说的内容并确认它运行良好。

非常感谢@Topaco 和@Gusto2,我会按照@Gusto2 说的,对安全上已经修改的部分进行修改!

【问题讨论】:

    标签: java c# security encryption aes


    【解决方案1】:

    1) 在 C# Encrypt-方法中,纯文本首先被加密,然后是 Base64 编码。因此,在解密过程中,必须先对数据进行 Base64 解码,然后再进行解密。目前,这是以错误的顺序处理的,即先解密数据,然后再解码。因此,在JavafileProcessing-方法中替换

    while((read = input.read(buffer)) != -1){
        output.write(c.update(buffer, 0, read));
    }
    

    与

    while((read = input.read(buffer)) != -1) {
        byte[] bufferEncoded = buffer;
        if (read != buffer.length) { 
            bufferEncoded = Arrays.copyOf(buffer, read);
        }
        byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
        output.write(c.update(bufferDecoded));
    }
    

    2) 没有必要将buffer(或bufferDecoded)传递给doFinal-方法,因为这已经在update-方法中完成了。因此,

    byte[] deryptedBytes = c.doFinal(buffer);
    

    必须替换为

    output.write(c.doFinal());
    

    3) 由于 Base64 解码已经在 1) 中完成 try-block 中的所有 doFinal-statement 之后的行必须被删除。总的来说,这会导致

    try {
        input = new BufferedInputStream(new FileInputStream(source));
        output = new BufferedOutputStream(new FileOutputStream(dest));
        byte[] buffer = new byte[input.available()];
        int read = -1;
        while((read = input.read(buffer)) != -1) {
            byte[] bufferEncoded = buffer;
            if (read != buffer.length) { 
                bufferEncoded = Arrays.copyOf(buffer, read);
            }
            byte[] bufferDecoded = Base64.getDecoder().decode(bufferEncoded);
            output.write(c.update(bufferDecoded));
        }
        output.write(c.doFinal()); 
    }
    

    4) 缓冲区的大小必须是 4 的倍数,以确保正确的 Base64 解码。因此,替换更可靠

    byte[] buffer = new byte[input.available()];
    

    与

    byte[] buffer = new byte[4 * (input.available() / 4)];
    

    只要在一个块中读取数据(不能保证,参见例如https://docs.oracle.com/en/java/javase/11/docs/api/java.base/java/io/InputStream.html#available())就没有问题。但是,如果数据以多个块读取,则读取 4 个字节的倍数很重要,否则 Base64 解码将失败。这可以通过使用不是 4 的倍数的缓冲区大小轻松证明。如果缓冲区大小明确针对较大的文件定义,则还必须考虑这一点。

    【讨论】:

    • 我得到了很多帮助,我发现我的解码顺序错误,但结果仍然输出错误的原因是什么?
    • 我没有更改c#代码,我通过参考您的测试源进行了更改,但结果仍然是BadPaddingException。我可以检查一下你的测试源吗?
    • 好吧,当它在另一台计算机(Java 10)上运行时它运行良好,但奇怪的是,它在我的计算机(Java 8)上不起作用。非常感谢你!我将继续查看原因(BadPaddingException)。
    【解决方案2】:
    while((read = input.read(buffer)) != -1){
           output.write(c.update(buffer, 0, read));
    }
    byte[] deryptedBytes = c.doFinal(buffer)
    

    您正在解密文件的输入,然后您使用相同的密码实例将最后读取的块(再次)解密为单独的数组而不是文件

    快速修复:

    while((read = input.read(buffer)) != -1){
           output.write(c.update(buffer, 0, read));
    }
    output.write(c.doFinal()); // write the padded block
    

    如果你想创建和打印一个解密的字符串,你需要创建一个新的 Cipher 实例(或者重新初始化实例就足够了,我不确定)假设缓冲区包含整个输入

    c.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
    // assuming the buffer contains the whole input again
    byte[] deryptedBytes = c.doFinal(buffer); // decrypting the whole file again
    

    正确做法:

    • IV 用于安全地重复使用相同的加密密钥进行多次加密。因此,如果您的密钥不是随机的,您应该为每次加密生成新的随机 IV(并将 IV 沿密文传递,通常是预先添加的)。否则,加密在语义上是不安全的,您可能会为两次填充攻击创建开口。所以从密钥中推导出 IV 可能不是很安全。

    • 我建议使用随密文传递的任何 MAC(身份验证码)以确保完整性(例如 HMAC)

    • 您仍在将所有文件输入完全读入内存,这对于非常大的文件不起作用。您可以将缓冲区初始化为任意长度(几 MB?)并将输入文件作为分块处理

    【讨论】:

      猜你喜欢
      • 2021-09-14
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2019-10-13
      • 1970-01-01
      • 1970-01-01
      相关资源
      最近更新 更多