Podany ostatni blok nie jest odpowiednio wypełniony

115

Próbuję zaimplementować algorytm szyfrowania oparty na haśle, ale pojawia się ten wyjątek:

javax.crypto.BadPaddingException: podany końcowy blok nie jest odpowiednio wypełniony

Co może być problemem?

Oto mój kod:

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

(Test 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.
        }
    }
}
Altrim
źródło

Odpowiedzi:

197

Jeśli spróbujesz odszyfrować dane wypełnione PKCS5 niewłaściwym kluczem, a następnie rozpakujesz je (co jest wykonywane automatycznie przez klasę Cipher), najprawdopodobniej otrzymasz BadPaddingException (z prawdopodobnie nieco mniej niż 255/256, około 99,61% ), ponieważ dopełnienie ma specjalną strukturę, która jest sprawdzana podczas rozpakowywania, a bardzo niewiele klawiszy dałoby prawidłowe wypełnienie.

Jeśli więc dostaniesz ten wyjątek, złap go i potraktuj jako „zły klucz”.

Może się to również zdarzyć, gdy podasz nieprawidłowe hasło, które jest następnie używane do uzyskania klucza z magazynu kluczy lub które jest konwertowane na klucz za pomocą funkcji generowania klucza.

Oczywiście złe wypełnienie może się również zdarzyć, jeśli dane zostaną uszkodzone podczas transportu.

To powiedziawszy, istnieją pewne uwagi dotyczące bezpieczeństwa dotyczące twojego systemu:

  • W przypadku szyfrowania opartego na haśle należy użyć SecretKeyFactory i PBEKeySpec zamiast używać SecureRandom z KeyGenerator. Powodem jest to, że SecureRandom może być innym algorytmem w każdej implementacji Java, dając inny klucz. SecretKeyFactory wykonuje wyprowadzanie klucza w zdefiniowany sposób (i w sposób, który jest uważany za bezpieczny, jeśli wybierzesz odpowiedni algorytm).

  • Nie używaj trybu EBC. Szyfruje każdy blok niezależnie, co oznacza, że ​​identyczne bloki zwykłego tekstu również dają zawsze identyczne bloki szyfrogramu.

    Najlepiej użyj bezpiecznego trybu działania , takiego jak CBC (łańcuch bloków szyfrów) lub CTR (licznik). Alternatywnie, użyj trybu, który obejmuje również uwierzytelnianie, na przykład GCM (tryb Galois-Counter) lub CCM (Counter z CBC-MAC), patrz następny punkt.

  • Zwykle nie chcesz tylko poufności, ale także uwierzytelniania, które zapewnia, że ​​wiadomość nie zostanie zmieniona. (Zapobiega to również atakom wybranym szyfrogramem na twój szyfr, tj. Pomaga zachować poufność.) Tak więc dodaj MAC (kod uwierzytelniania wiadomości) do swojej wiadomości lub użyj trybu szyfrowania, który obejmuje uwierzytelnianie (patrz poprzedni punkt).

  • DES ma efektywny rozmiar klucza tylko 56 bitów. Ta kluczowa przestrzeń jest dość mała, może zostać brutalnie wymuszona w ciągu kilku godzin przez dedykowanego napastnika. Jeśli wygenerujesz klucz za pomocą hasła, będzie to jeszcze szybsze. Ponadto DES ma rozmiar bloku wynoszący tylko 64 bity, co dodaje kilka słabych punktów w trybach łączenia. Zamiast tego użyj nowoczesnego algorytmu, takiego jak AES, który ma rozmiar bloku 128 bitów i rozmiar klucza 128 bitów (dla wariantu standardowego).

Paŭlo Ebermann
źródło
1
Chcę tylko potwierdzić. Jestem nowy w szyfrowaniu i to jest mój scenariusz, używam szyfrowania AES. w mojej funkcji szyfrowania / odszyfrowywania używam klucza szyfrowania. Użyłem niewłaściwego klucza szyfrowania podczas odszyfrowywania i otrzymałem to javax.crypto.BadPaddingException: Given final block not properly padded. Czy powinienem traktować to jako zły klucz?
kenicky
Dla jasności może się to również zdarzyć, gdy podasz nieprawidłowe hasło do pliku kluczy, takiego jak plik .p12, co właśnie mi się przydarzyło.
Warren Dew
2
@WarrenDew „Nieprawidłowe hasło do pliku magazynu kluczy” to tylko specjalny przypadek „niewłaściwego klucza”.
Paŭlo Ebermann
@kenicky przepraszam, właśnie widziałem twój komentarz ... tak, zły klawisz prawie zawsze powoduje ten efekt. (Oczywiście uszkodzone dane to kolejna możliwość.)
Paŭlo Ebermann
@ PaŭloEbermann Zgadzam się, ale nie wydaje mi się, żeby to było od razu oczywiste, ponieważ jest inna niż sytuacja w oryginalnym poście, w której programista ma kontrolę nad kluczem i deszyfrowaniem. Jednak twoja odpowiedź była na tyle przydatna, że ​​ją poparłem.
Warren Dew,
1

w zależności od używanego algorytmu kryptograficznego może być konieczne dodanie kilku bajtów wypełniających na końcu przed zaszyfrowaniem tablicy bajtów, aby długość tablicy bajtów była wielokrotnością rozmiaru bloku:

Konkretnie w Twoim przypadku wybrany schemat dopełnienia to PKCS5, który jest opisany tutaj: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Zakładam, że masz problem podczas próby zaszyfrowania)

Możesz wybrać schemat dopełniania podczas tworzenia wystąpienia obiektu Cipher. Obsługiwane wartości zależą od używanego dostawcy zabezpieczeń.

A tak przy okazji, czy na pewno chcesz używać mechanizmu szyfrowania symetrycznego do szyfrowania haseł? Czy nie byłby lepszy hasz w jedną stronę? Jeśli naprawdę potrzebujesz możliwości odszyfrowania haseł, DES jest dość słabym rozwiązaniem, możesz być zainteresowany użyciem czegoś silniejszego, takiego jak AES, jeśli chcesz pozostać przy algorytmie symetrycznym.

fpacifici
źródło
1
więc czy mógłbyś wysłać kod, który próbuje zaszyfrować / odszyfrować? (i sprawdź, czy tablica bajtów, którą próbujesz odszyfrować, nie jest większa niż rozmiar bloku)
fpacifici
1
Jestem bardzo nowy w Javie, a także w kryptografii, więc nadal nie znam lepszych sposobów szyfrowania. Chcę tylko to zrobić, niż prawdopodobnie szukać lepszych sposobów na jego wdrożenie.
Altrim
czy możesz zaktualizować link, ponieważ nie działa @fpacifici, a ja zaktualizowałem swój post Dodałem test JUnit, który testuje szyfrowanie i deszyfrowanie
Altrim
Poprawione (przepraszam, błąd kopiowania wklejania). W każdym razie, rzeczywiście problem występuje, ponieważ odszyfrowujesz kluczem, który nie jest tym samym, co ten używany do szyfrowania, jak wyjaśnił Paulo. Dzieje się tak, ponieważ metoda z adnotacją @Before w junit jest wykonywana przed każdą metodą testową, a tym samym za każdym razem ponownie generuje klucz. ponieważ klucz jest inicjowany losowo, za każdym razem będzie inny.
fpacifici
1

Spotkałem się z tym problemem ze względu na system operacyjny, prostą na inną platformę o implementacji JRE.

new SecureRandom(key.getBytes())

otrzyma tę samą wartość w systemie Windows, podczas gdy jest inna w systemie Linux. Więc w Linuksie należy zmienić na

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

Użyty algorytm to „SHA1PRNG”. Więcej informacji na temat algorytmów można znaleźć tutaj .

Bejond
źródło
0

Może to również stanowić problem, gdy wprowadzisz nieprawidłowe hasło do klucza podpisu.

Jedyny ja
źródło
To naprawdę komentarz, a nie odpowiedź. Z nieco większym przedstawicielem będziesz mógł publikować komentarze .
Adrian Mole
to nie jest odpowiedź
zig razor