簡體   English   中英

如何處理BadPaddingException在C#中的AES256加密和Java中的解密期間

[英]How to handle BadPaddingException During AES256 encryption in C# and decryption in Java

我不知道為什么會出現錯誤。

線程“main”中的異常javax.crypto.BadPaddingException:給定最終塊未正確填充。 如果在解密期間使用壞密鑰,則可能出現此類問題。

我知道在解密過程中使用了錯誤的密鑰時會發生此錯誤。 但是,如果查看下面的測試結果結果,可以看到C#和Java都是相同的(Key,IV,Salt是Base64編碼的)。

  1. C#測試結果

C#測試結果

  1. Java測試結果

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所說的那樣!

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

如果你想創建並打印一個解密的String,你需要創建一個新的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,最常見的是前置)。 否則加密在語義上不安全,您可以為兩個pad攻擊創建開放。 因此從密鑰中導出IV可能不是很安全。

  • 我建議使用沿密文傳遞的任何MAC(認證碼)來確保完整性(例如HMAC)

  • 你還在將所有文件輸入完全讀入內存,這對於REALLY LARGE文件無效。 您可以將緩沖區初始化為任意長度(幾MB?)並將輸入文件處理為chunked

1)在C# Encrypt -method中,首先加密純文本,然后加密Base64編碼。 因此,在解密過程中,數據必須首先進行Base64解碼然后解密。 目前,這是以錯誤的順序處理的,即首先解密數據然后解碼。 因此,在Java fileProcessing -method中替換

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 -method,因為這已經在update -method中完成了。 從而,

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的倍數的緩沖區大小可以很容易地證明這一點。如果針對較大的文件明確定義了緩沖區大小,則還必須考慮這一點。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM