Заданий остаточний блок не прокладений належним чином


115

Я намагаюся реалізувати алгоритм шифрування на основі пароля, але я отримую такий виняток:

javax.crypto.BadPaddingException: Даний остаточний блок не є належним чином

У чому може бути проблема?

Ось мій код:

public class PasswordCrypter {

    private Key key;

    public PasswordCrypter(String password)  {
        try{
            KeyGenerator generator;
            generator = KeyGenerator.getInstance("DES");
            SecureRandom sec = new SecureRandom(password.getBytes());
            generator.init(sec);
            key = generator.generateKey();
        } catch (Exception e) {
            e.printStackTrace();
        }
    }


    public byte[] encrypt(byte[] array) throws CrypterException {
        try{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.ENCRYPT_MODE, key);

            return cipher.doFinal(array);
        } catch (Exception e) { 
            e.printStackTrace();
        }
        return null;
    }

    public byte[] decrypt(byte[] array) throws CrypterException{
        try{
            Cipher cipher = Cipher.getInstance("DES/ECB/PKCS5Padding");
            cipher.init(Cipher.DECRYPT_MODE, key);

            return cipher.doFinal(array);
        } catch(Exception e ){
            e.printStackTrace();
        }
        return null;
    }
}

(Тест JUnit)

public class PasswordCrypterTest {

    private static final byte[] MESSAGE = "Alpacas are awesome!".getBytes();
    private PasswordCrypter[] passwordCrypters;
    private byte[][] encryptedMessages;

    @Before
    public void setUp() {
        passwordCrypters = new PasswordCrypter[] {
            new PasswordCrypter("passwd"),
            new PasswordCrypter("passwd"),
            new PasswordCrypter("otherPasswd")
        };

        encryptedMessages = new byte[passwordCrypters.length][];
        for (int i = 0; i < passwordCrypters.length; i++) {
            encryptedMessages[i] = passwordCrypters[i].encrypt(MESSAGE);
        }
    }

    @Test
    public void testEncrypt() {
        for (byte[] encryptedMessage : encryptedMessages) {
            assertFalse(Arrays.equals(MESSAGE, encryptedMessage));
        }

        assertFalse(Arrays.equals(encryptedMessages[0], encryptedMessages[2]));
        assertFalse(Arrays.equals(encryptedMessages[1], encryptedMessages[2]));
    }

    @Test
    public void testDecrypt() {
        for (int i = 0; i < passwordCrypters.length; i++) {
            assertArrayEquals(MESSAGE, passwordCrypters[i].decrypt(encryptedMessages[i]));
        }

        assertArrayEquals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[1]));
        assertArrayEquals(MESSAGE, passwordCrypters[1].decrypt(encryptedMessages[0]));

        try {
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[0].decrypt(encryptedMessages[2])));
        } catch (CrypterException e) {
            // Anything goes as long as the above statement is not true.
        }

        try {
            assertFalse(Arrays.equals(MESSAGE, passwordCrypters[2].decrypt(encryptedMessages[1])));
        } catch (CrypterException e) {
            // Anything goes as long as the above statement is not true.
        }
    }
}

Відповіді:


197

Якщо ви спробуєте розшифрувати вкладені PKCS5 дані неправильним ключем, а потім розблокувати їх (що робиться класом Cipher автоматично), ви, швидше за все, отримаєте BadPaddingException (мабуть, трохи менше 255/256, приблизно 99,61% ), оскільки підкладка має спеціальну структуру, яка перевіряється під час unpad, і дуже мало клавіш створює дійсну прокладку.

Отже, якщо ви отримаєте цей виняток, спіймайте його і трактуйте його як "неправильний ключ".

Це також може статися, коли ви вказали неправильний пароль, який потім використовується для отримання ключа з магазину ключів або який перетворюється в ключ за допомогою функції генерації ключів.

Звичайно, погані прокладки також можуть трапитися, якщо ваші дані будуть пошкоджені в транспорті.

Втім, є кілька зауважень щодо безпеки щодо вашої схеми:

  • Для шифрування на основі паролів слід використовувати SecretKeyFactory та PBEKeySpec замість SecureRandom із KeyGenerator. Причина полягає в тому, що SecureRandom може бути різним алгоритмом для кожної реалізації Java, надаючи вам інший ключ. SecretKeyFactory робить виведення ключів у визначений спосіб (і спосіб, який вважається безпечним, якщо вибрати правильний алгоритм).

  • Не використовуйте режим ECB. Він шифрує кожен блок незалежно, це означає, що однакові блоки простого тексту також дають завжди однакові блоки шифротексту.

    Переважно використовувати захищений режим роботи , такий як CBC (ланцюговий блок шифру) або CTR (лічильник). Крім того, використовуйте режим, який також включає аутентифікацію, наприклад GCM (режим Galois-Counter) або CCM (лічильник з CBC-MAC), див. Наступний пункт.

  • Зазвичай ви не хочете лише конфіденційності, але і автентифікації, яка не дозволяє підробляти повідомлення. (Це також запобігає атакам вибраного шифротексту на ваш шифр, тобто сприяє конфіденційності.) Отже, додайте до свого повідомлення MAC (код автентифікації повідомлення) або використовуйте режим шифрування, який включає аутентифікацію (див. Попередній пункт).

  • DES має ефективний розмір ключа всього 56 біт. Цей ключовий простір зовсім невеликий, його може за кілька годин жорстоко змусити відданий нападник. Якщо ви генеруєте свій ключ паролем, це стане ще швидше. Також DES має розмір блоку всього 64 біти, що додає ще деякі слабкі місця в режимах ланцюга. Замість цього використовуйте сучасний алгоритм типу AES, який має розмір блоку 128 біт і розмір ключа 128 біт (для стандартного варіанту).


1
Я просто хочу підтвердити. Я новачок в шифруванні, і це мій сценарій, я використовую шифрування AES. у своїй функції шифрування / дешифрування я використовую ключ шифрування. Я використав неправильний ключ шифрування в дешифруванні, і я отримав це javax.crypto.BadPaddingException: Given final block not properly padded. Чи слід ставитися до цього як до неправильного ключа?
kenicky

Щоб було зрозуміло, це може статися і при наданні неправильного пароля для файлу зберігання ключів, такого як .p12-файл, що саме зі мною трапилося.
Warren Dew

2
@WarrenDew "Неправильний пароль для файлу зберігання ключів" - це лише особливий випадок "неправильного ключа".
Paŭlo Ebermann

@kenicky Вибачте, я побачив ваш коментар саме зараз ... так, неправильна клавіша майже завжди викликає цей ефект. (Звичайно, пошкоджені дані - інша можливість.)
Paŭlo Ebermann

@ PaŭloEbermann Я погоджуюся, але не думаю, що це обов'язково відразу очевидно, оскільки це інше, ніж ситуація в початковій публікації, де програміст має контроль над ключем і розшифровкою. Але я знайшов вашу відповідь достатньо корисною, щоб підтвердити її.
Warren Dew

1

залежно від алгоритму криптографії, який ви використовуєте, вам, можливо, доведеться додати кілька байтів підкладки в кінці перед шифруванням байтового масиву, щоб довжина байтового масиву була кратною розміру блоку:

Зокрема, у вашому випадку обрана вами схема прокладки - це PKCS5, описана тут: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Я припускаю, що у вас виникає проблема при спробі шифрування)

Ви можете вибрати схему накладки, коли інстанціюєте об'єкт Cipher. Підтримувані значення залежать від постачальника безпеки, якого ви використовуєте.

До речі, ви впевнені, що хочете використовувати симетричний механізм шифрування для шифрування паролів? Не було б краще в один спосіб хеш? Якщо вам дійсно потрібно мати можливість розшифровувати паролі, DES є досить слабким рішенням, вам може бути цікаво використовувати щось сильніше, як AES, якщо вам потрібно дотримуватися симетричного алгоритму.


1
Ви можете, будь ласка, опублікувати код, який намагається зашифрувати / розшифрувати? (і переконайтеся, що байтовий масив, який ви намагаєтеся розшифрувати, не перевищує розмір блоку)
fpacifici

1
Я дуже новачок у Java, а також криптографії, тому я ще не знаю кращих способів шифрування. Я просто хочу зробити це, ніж, мабуть, шукати кращі способи його реалізації.
Альтрім

Ви можете оновити посилання, оскільки воно не працює @fpacifici, і я оновив свою допис, я включив тест JUnit, який перевіряє шифрування та дешифрування
Altrim

Виправлено (вибачте помилку копіювання вставлення). У будь-якому випадку, справді ваша проблема трапляється, оскільки ви розшифровуєте ключ, який не є тим, який використовується для шифрування, як пояснив Пауло. Це трапляється, оскільки метод, позначений за допомогою @Before у червні, виконується перед кожним тестовим методом, таким чином щоразу регенеруючи ключ. оскільки ключ ініціалізується випадковим чином, він буде кожен раз різним.
fpacifici

1

Я зустрів цю проблему через операційну систему, просту для різних платформ щодо впровадження JRE.

new SecureRandom(key.getBytes())

отримає однакове значення в Windows, тоді як в Linux. Тому в Linux потрібно змінити на

SecureRandom secureRandom = SecureRandom.getInstance("SHA1PRNG");
secureRandom.setSeed(key.getBytes());
kgen.init(128, secureRandom);

"SHA1PRNG" - це алгоритм, який використовується, ви можете посилатися тут для отримання додаткової інформації про алгоритми.


Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.