Данный последний блок не заполнен должным образом

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%). ), потому что заполнение имеет особую структуру, которая проверяется во время распаковки, и очень немногие клавиши будут давать допустимое заполнение.

Итак, если вы получили это исключение, перехватите его и обработайте как «неправильный ключ».

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

Конечно, плохое заполнение также может произойти, если ваши данные повреждены при транспортировке.

Тем не менее, есть некоторые замечания по безопасности вашей схемы:

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

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

    Предпочтительно использовать безопасный режим работы , такой как CBC (цепочка блоков шифра) или CTR (счетчик). В качестве альтернативы используйте режим, который также включает аутентификацию, например GCM (режим счетчика Галуа) или CCM (счетчик с CBC-MAC), см. Следующий пункт.

  • Обычно вам нужна не только конфиденциальность, но и аутентификация, которая гарантирует, что сообщение не будет подделано. (Это также предотвращает атаки с выбранным зашифрованным текстом на ваш шифр, то есть помогает обеспечить конфиденциальность.) Итак, добавьте MAC (код аутентификации сообщения) к вашему сообщению или используйте режим шифрования, который включает аутентификацию (см. Предыдущий пункт).

  • DES имеет эффективный размер ключа всего 56 бит. Это ключевое пространство довольно маленькое, оно может быть взломано за несколько часов специальным злоумышленником. Если вы сгенерируете свой ключ по паролю, это станет еще быстрее. Кроме того, DES имеет размер блока всего 64 бита, что добавляет еще несколько недостатков в режимах объединения. Вместо этого используйте современный алгоритм, такой как AES, который имеет размер блока 128 бит и размер ключа 128 бит (для стандартного варианта).

Пало Эберманн
источник
1
Я просто хочу подтвердить. Я новичок в шифровании, и это мой сценарий, я использую шифрование AES. в моей функции шифрования / дешифрования я использую ключ шифрования. Я использовал неправильный ключ шифрования при расшифровке, и я получил это javax.crypto.BadPaddingException: Given final block not properly padded. Должен ли я рассматривать это как неправильный ключ?
kenicky
Чтобы быть ясным, это также может произойти при указании неправильного пароля для файла хранилища ключей, такого как файл .p12, что только что произошло со мной.
Уоррен Дью
2
@WarrenDew «Неверный пароль для файла хранилища ключей» - это просто частный случай «неправильного ключа».
Паоло Эберманн
@kenicky извините, я только что видел ваш комментарий ... да, неправильный ключ почти всегда вызывает такой эффект. (Конечно, поврежденные данные - еще одна возможность.)
Пало Эберманн
@ PaŭloEbermann Я согласен, но я не думаю, что это сразу очевидно, поскольку это отличается от ситуации в исходном сообщении, где программист контролирует ключ и расшифровку. Тем не менее, я нашел ваш ответ достаточно полезным, чтобы проголосовать за него.
Уоррен Дью
1

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

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

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

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

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

fpacifici
источник
1
так что не могли бы вы опубликовать код, который пытается зашифровать / расшифровать? (и убедитесь, что массив байтов, который вы пытаетесь расшифровать, не превышает размер блока)
fpacifici
1
Я новичок в Java, а также в криптографии, поэтому до сих пор не знаю более эффективных способов шифрования. Я просто хочу сделать это, чем, вероятно, ищу лучшие способы его реализации.
Altrim 08
Можете ли вы обновить ссылку, потому что она не работает @fpacifici, и я обновил свой пост, я включил тест JUnit, который проверяет шифрование и дешифрование
Altrim
Исправлено (извините, ошибка копирования и вставки). В любом случае, действительно, ваша проблема возникает из-за того, что вы расшифровываете ключ, который не совпадает с тем, который используется для шифрования, как объяснил Пауло. Это происходит из-за того, что метод, помеченный @Before в junit, выполняется перед каждым тестовым методом, таким образом каждый раз восстанавливая ключ. поскольку ключ инициализируется случайным образом, он каждый раз будет другим.
fpacifici
1

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

new SecureRandom(key.getBytes())

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

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

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

Bejond
источник
0

Это также может быть проблемой при вводе неправильного пароля для ключа подписи.

Единственный я
источник
Это действительно комментарий, а не ответ. Если у вас немного больше репутации, вы сможете публиковать комментарии .
Адриан Моул
это не ответ
zig razor