Mengingat blok terakhir tidak dilapisi dengan benar

115

Saya mencoba menerapkan algoritma enkripsi berbasis kata sandi, tetapi saya mendapatkan pengecualian ini:

javax.crypto.BadPaddingException: Mengingat blok terakhir tidak diisi dengan benar

Apa masalahnya?

Ini kode saya:

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

(Tes 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
sumber

Jawaban:

197

Jika Anda mencoba mendekripsi data berlapis PKCS5 dengan kunci yang salah, lalu melepasnya (yang dilakukan oleh kelas Cipher secara otomatis), kemungkinan besar Anda akan mendapatkan BadPaddingException (dengan kemungkinan sedikit kurang dari 255/256, sekitar 99,61% ), karena padding memiliki struktur khusus yang divalidasi selama unpad dan sangat sedikit kunci yang akan menghasilkan padding yang valid.

Jadi, jika Anda mendapatkan pengecualian ini, tangkap dan perlakukan sebagai "kunci yang salah".

Hal ini juga dapat terjadi jika Anda memberikan sandi yang salah, yang kemudian digunakan untuk mendapatkan kunci dari keystore, atau yang diubah menjadi kunci menggunakan fungsi pembuatan kunci.

Tentu saja, padding yang buruk juga dapat terjadi jika data Anda rusak dalam pengangkutan.

Meskipun demikian, ada beberapa pernyataan keamanan tentang skema Anda:

  • Untuk enkripsi berbasis kata sandi, Anda harus menggunakan SecretKeyFactory dan PBEKeySpec daripada menggunakan SecureRandom dengan KeyGenerator. Alasannya adalah SecureRandom bisa menjadi algoritme yang berbeda pada setiap implementasi Java, memberi Anda kunci yang berbeda. SecretKeyFactory melakukan derivasi kunci dengan cara yang ditentukan (dan cara yang dianggap aman, jika Anda memilih algoritma yang tepat).

  • Jangan gunakan mode ECB. Ini mengenkripsi setiap blok secara independen, yang berarti bahwa blok teks biasa yang identik juga selalu memberikan blok ciphertext yang identik.

    Lebih disukai menggunakan mode operasi aman , seperti CBC (Rantai blok sandi) atau CTR (Penghitung). Alternatifnya, gunakan mode yang juga menyertakan otentikasi, seperti GCM (Galois-Counter mode) atau CCM (Counter with CBC-MAC), lihat poin berikutnya.

  • Anda biasanya tidak hanya menginginkan kerahasiaan, tetapi juga otentikasi, yang memastikan pesan tidak dirusak. (Ini juga mencegah serangan teks sandi yang dipilih pada sandi Anda, yaitu membantu kerahasiaan.) Jadi, tambahkan MAC (kode otentikasi pesan) ke pesan Anda, atau gunakan mode sandi yang mencakup otentikasi (lihat poin sebelumnya).

  • DES memiliki ukuran kunci efektif hanya 56 bit. Ruang kunci ini cukup kecil, dapat dipaksakan secara brutal dalam beberapa jam oleh penyerang khusus. Jika Anda membuat kunci dengan kata sandi, ini akan menjadi lebih cepat. Selain itu, DES memiliki ukuran blok hanya 64 bit, yang menambahkan beberapa kelemahan lagi dalam mode rantai. Gunakan algoritme modern seperti AES, yang memiliki ukuran blok 128 bit, dan ukuran kunci 128 bit (untuk varian standar).

Paŭlo Ebermann
sumber
1
Saya hanya ingin memastikan. Saya baru mengenal enkripsi dan ini skenario saya, saya menggunakan enkripsi AES. dalam fungsi enkripsi / dekripsi saya, saya menggunakan kunci enkripsi. Saya menggunakan kunci enkripsi yang salah dalam mendekripsi dan saya mendapatkannya javax.crypto.BadPaddingException: Given final block not properly padded. Haruskah saya memperlakukan ini sebagai kunci yang salah?
kenicky
Untuk memperjelas, ini juga dapat terjadi saat memberikan kata sandi yang salah untuk file penyimpanan kunci, seperti file .p12, yang baru saja terjadi pada saya.
Warren Dew
2
@WarrenDew "Kata sandi salah untuk file penyimpanan kunci" hanyalah kasus khusus dari "kunci yang salah".
Paŭlo Ebermann
@kenicky maaf, saya melihat komentar Anda barusan ... ya, kunci yang salah hampir selalu menyebabkan efek ini. (Tentu saja, data yang rusak adalah kemungkinan lain.)
Paŭlo Ebermann
@ PaŭloEbermann Saya setuju, tapi menurut saya itu tidak langsung terlihat jelas, karena ini berbeda dengan situasi di pos asli di mana pemrogram memiliki kendali atas kunci dan dekripsi. Saya menemukan jawaban Anda cukup berguna untuk memberi suara positif.
Warren Dew
1

tergantung pada algoritma kriptografi yang Anda gunakan, Anda mungkin harus menambahkan beberapa padding byte di akhir sebelum mengenkripsi array byte sehingga panjang array byte adalah kelipatan dari ukuran blok:

Khususnya dalam kasus Anda skema padding yang Anda pilih adalah PKCS5 yang dijelaskan di sini: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Saya berasumsi Anda memiliki masalah saat mencoba mengenkripsi)

Anda dapat memilih skema padding Anda saat Anda membuat instance objek Cipher. Nilai yang didukung bergantung pada penyedia keamanan yang Anda gunakan.

Ngomong-ngomong, apakah Anda yakin ingin menggunakan mekanisme enkripsi simetris untuk mengenkripsi sandi? Bukankah lebih baik hash satu arah? Jika Anda benar-benar harus dapat mendekripsi kata sandi, DES adalah solusi yang cukup lemah, Anda mungkin tertarik untuk menggunakan sesuatu yang lebih kuat seperti AES jika Anda ingin tetap menggunakan algoritme simetris.

fpacifici
sumber
1
jadi bisakah Anda memposting kode yang mencoba mengenkripsi / mendekripsi? (dan periksa apakah byte array yang Anda coba dekripsi tidak lebih besar dari ukuran blok)
fpacifici
1
Saya sangat baru mengenal Java dan juga Kriptografi jadi saya masih belum tahu cara yang lebih baik untuk melakukan enkripsi. Saya hanya ingin menyelesaikan ini daripada mungkin mencari cara yang lebih baik untuk menerapkannya.
Altrim
dapatkah Anda memperbarui tautan karena tidak berfungsi @fpacifici dan saya memperbarui posting saya, saya menyertakan tes JUnit yang menguji enkripsi dan dekripsi
Altrim
Diperbaiki (maaf kesalahan salin tempel). Bagaimanapun, memang masalah Anda terjadi karena Anda mendekripsi dengan kunci yang tidak sama dengan yang digunakan untuk enkripsi seperti yang dijelaskan oleh Paulo. Ini terjadi karena metode yang dianotasi dengan @Before di junit dijalankan sebelum setiap metode pengujian, sehingga meregenerasi kunci setiap saat. karena kuncinya diinisialisasi secara acak, itu akan berbeda setiap kali.
fpacifici
1

Saya menemui masalah ini karena sistem operasi, platform sederhana untuk berbeda tentang implementasi JRE.

new SecureRandom(key.getBytes())

akan mendapatkan nilai yang sama di Windows, sedangkan di Linux berbeda. Jadi di Linux perlu diubah menjadi

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

"SHA1PRNG" adalah algoritme yang digunakan, Anda dapat merujuk di sini untuk info selengkapnya tentang algoritme.

Bejond
sumber
0

Ini juga bisa menjadi masalah ketika Anda memasukkan kata sandi yang salah untuk kunci tanda Anda.

The Only Me
sumber
Ini benar-benar sebuah komentar, bukan jawaban. Dengan sedikit lebih banyak perwakilan, Anda akan dapat mengirim komentar .
Adrian Mole
ini bukan jawaban
zig razor