Как обрабатывать BadPaddingException во время шифрования AES256 в C # и дешифрования в Java - PullRequest
0 голосов
/ 11 января 2019

Я не знаю, почему появляется ошибка.

Исключение в потоке "main" javax.crypto.BadPaddingException: данный последний блок заполнен неправильно. Такие проблемы могут возникнуть, если при расшифровке используется плохой ключ.

Я понимаю, что эта ошибка возникает, когда во время расшифровки используется неверный ключ. Однако, если вы посмотрите на результаты теста ниже, вы увидите, что и C #, и Java одинаковы (Key, IV, Salt в кодировке Base64).

  1. C # Результат теста

C# Test Result

  1. Результат теста Java

Java Test Result

Это то же самое! (Ключ, IV, Соль)

Но генерируется текущая ошибка 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. Ключ подтверждения и IV в C #

    //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. Ключ подтверждения и IV в Java

    //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 сказал!

Ответы [ 2 ]

0 голосов
/ 11 января 2019

1) В методе C # Encrypt простой текст сначала шифруется, а затем кодируется Base64. Таким образом, в процессе дешифрования данные должны сначала декодироваться Base64, а затем расшифровываться. В настоящее время это обрабатывается в неправильном порядке, то есть данные сначала дешифруются, а затем декодируются. Поэтому в Java fileProcessing -метод заменить

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, все строки, следующие за утверждением doFinal, должны быть удалены. В целом, это приводит к

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. Этот момент также следует учитывать, если размер буфера явно определен с учетом к большим файлам.

0 голосов
/ 11 января 2019
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)

  • вы все еще полностью читаете введенный файл в память, что не сработает для ДЕЙСТВИТЕЛЬНО БОЛЬШИХ файлов. Вы можете инициализировать буфер произвольной длины (несколько МБ?) И обработать входной файл как chunked

...