Учитывая, что последний блок не заполнен неправильно

Я пытаюсь реализовать алгоритм шифрования на основе пароля, но я получаю следующее исключение:

javax.crypto.BadPaddingException: данный последний блок не заполнен должным образом

В чем может быть проблема? (Я новичок в Java.)

Вот мой код:

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.
        }
    }
}

Ответ 1

Если вы попытаетесь расшифровать данные с запасом 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 бит (для стандартного варианта).

Ответ 2

в зависимости от алгоритма шифрования, который вы используете, вам, возможно, придется добавить некоторые байты заполнения в конце перед шифрованием массива байтов, чтобы длина массива байтов была кратной размеру блока:

В частности, в вашем случае выбранная вами схема дополнений - это PKCS5, которая описана здесь: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ_SYM__PAD.html

(Я предполагаю, что у вас есть проблема при попытке шифрования)

Вы можете выбрать схему заполнения, когда вы создаете экземпляр объекта Cipher. Поддерживаемые значения зависят от поставщика безопасности, который вы используете.

Кстати, вы уверены, что хотите использовать симметричный механизм шифрования для шифрования паролей? Разве не было бы лучшего варианта хэша? Если вам действительно нужно расшифровать пароли, DES - довольно слабое решение, вам может быть интересно использовать что-то более сильное, как AES, если вам нужно остановиться с симметричным алгоритмом.

Ответ 3

Я столкнулся с этой проблемой из-за операционной системы, простой для другой платформы о реализации JRE.

        new SecureRandom(key.getBytes())

получит одинаковое значение в Windows, в то время как в Linux оно будет другим. Так что в Linux нужно поменять на

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

"SHA1PRNG" - используемый алгоритм, вы можете обратиться сюда для получения дополнительной информации об алгоритмах.