Dato il blocco finale non adeguatamente imbottito


115

Sto cercando di implementare un algoritmo di crittografia basato su password, ma ottengo questa eccezione:

javax.crypto.BadPaddingException: dato il blocco finale non adeguatamente riempito

Quale potrebbe essere il problema?

Ecco il mio codice:

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

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

Risposte:


197

Se provi a decrittografare i dati riempiti con PKCS5 con la chiave sbagliata e quindi decomprimerlo (operazione eseguita automaticamente dalla classe Cipher), molto probabilmente otterrai BadPaddingException (con probabilmente leggermente inferiore a 255/256, circa il 99,61% ), perché il padding ha una struttura speciale che viene convalidata durante unpad e pochissimi tasti producono un padding valido.

Quindi, se ottieni questa eccezione, prendila e trattala come "chiave sbagliata".

Ciò può accadere anche quando si fornisce una password errata, che viene quindi utilizzata per ottenere la chiave da un keystore o che viene convertita in una chiave utilizzando una funzione di generazione della chiave.

Ovviamente, un riempimento errato può anche verificarsi se i dati vengono danneggiati durante il trasporto.

Detto questo, ci sono alcune osservazioni di sicurezza sul tuo schema:

  • Per la crittografia basata su password, dovresti usare SecretKeyFactory e PBEKeySpec invece di usare SecureRandom con KeyGenerator. Il motivo è che SecureRandom potrebbe essere un algoritmo diverso su ciascuna implementazione Java, dandoti una chiave diversa. SecretKeyFactory esegue la derivazione della chiave in un modo definito (e in un modo ritenuto sicuro, se si seleziona l'algoritmo corretto).

  • Non utilizzare la modalità ECB. Crittografa ogni blocco in modo indipendente, il che significa che blocchi di testo normale identici forniscono anche blocchi di testo cifrato sempre identici.

    Utilizzare preferibilmente una modalità operativa sicura , come CBC (Cipher block chaining) o CTR (Counter). In alternativa, usa una modalità che includa anche l'autenticazione, come GCM (modalità Galois-Counter) o CCM (Counter with CBC-MAC), vedi punto successivo.

  • Normalmente non vuoi solo riservatezza, ma anche autenticazione, che assicura che il messaggio non venga manomesso. (Ciò impedisce anche gli attacchi con testo cifrato scelto al tuo codice, ad esempio aiuta per la riservatezza.) Quindi, aggiungi un MAC (codice di autenticazione del messaggio) al tuo messaggio o usa una modalità di cifratura che include l'autenticazione (vedi punto precedente).

  • DES ha una dimensione della chiave effettiva di soli 56 bit. Questo spazio chiave è piuttosto piccolo, può essere forzato in alcune ore da un aggressore dedicato. Se generi la tua chiave con una password, sarà ancora più veloce. Inoltre, DES ha una dimensione del blocco di soli 64 bit, il che aggiunge ulteriori punti deboli nelle modalità di concatenamento. Utilizza invece un algoritmo moderno come AES, che ha una dimensione del blocco di 128 bit e una dimensione della chiave di 128 bit (per la variante standard).


1
Voglio solo confermare. Sono nuovo nella crittografia e questo è il mio scenario, sto usando la crittografia AES. nella mia funzione di crittografia / decrittografia, sto utilizzando una chiave di crittografia. Ho usato una chiave di crittografia sbagliata nel decrittografare e ho ottenuto questo javax.crypto.BadPaddingException: Given final block not properly padded. Dovrei trattarlo come una chiave sbagliata?
kenicky

Per essere chiari, questo può accadere anche quando si fornisce la password errata per un file di archivio chiavi, come un file .p12, che è quello che è appena successo a me.
Warren Dew,

2
@WarrenDew "Password errata per un file di archivio chiavi" è solo un caso speciale di "chiave sbagliata".
Paŭlo Ebermann

@kenicky scusa, ho visto il tuo commento solo ora ... si, una chiave sbagliata quasi sempre provoca questo effetto. (Ovviamente, i dati corrotti sono un'altra possibilità.)
Paŭlo Ebermann

@ PaŭloEbermann Sono d'accordo, ma non credo che sia necessariamente immediatamente ovvio, poiché è diversa dalla situazione nel post originale in cui il programmatore ha il controllo della chiave e della decrittazione. Tuttavia, ho trovato la tua risposta abbastanza utile da votarla positivamente.
Warren Dew

1

a seconda dell'algoritmo di crittografia in uso, potrebbe essere necessario aggiungere alcuni byte di riempimento alla fine prima di crittografare un array di byte in modo che la lunghezza dell'array di byte sia multipla della dimensione del blocco:

Nello specifico nel tuo caso lo schema di riempimento che hai scelto è PKCS5 che è descritto qui: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Presumo che tu abbia il problema quando provi a crittografare)

È possibile scegliere lo schema di riempimento quando si crea un'istanza dell'oggetto Cipher. I valori supportati dipendono dal provider di sicurezza che stai utilizzando.

A proposito, sei sicuro di voler utilizzare un meccanismo di crittografia simmetrica per crittografare le password? Non sarebbe meglio un hashish a senso unico? Se hai davvero bisogno di essere in grado di decrittografare le password, DES è una soluzione piuttosto debole, potresti essere interessato a usare qualcosa di più forte come AES se hai bisogno di rimanere con un algoritmo simmetrico.


1
quindi potresti inserire il codice che cerca di crittografare / decrittografare? (e controlla che l'array di byte che cerchi di decifrare non sia più grande della dimensione del blocco)
fpacifici

1
Sono molto nuovo in Java e anche nella crittografia, quindi non conosco ancora modi migliori per eseguire la crittografia. Voglio solo farlo, piuttosto che probabilmente cercare modi migliori per implementarlo.
Altrim

puoi aggiornare il link perché non funziona @fpacifici e ho aggiornato il mio post ho incluso il test JUnit che verifica la crittografia e la decrittografia
Altrim

Corretto (spiacente errore copia incolla). Ad ogni modo, in effetti il ​​tuo problema si verifica poiché decifri con una chiave diversa da quella utilizzata per la crittografia come spiegato da Paulo. Ciò accade poiché il metodo annotato con @Before in junit viene eseguito prima di ogni metodo di test, rigenerando così la chiave ogni volta. poiché la chiave viene inizializzata in modo casuale, sarà diversa ogni volta.
fpacifici

1

Ho riscontrato questo problema a causa del sistema operativo, semplice per piattaforma diversa sull'implementazione di JRE.

new SecureRandom(key.getBytes())

otterrà lo stesso valore in Windows, mentre è diverso in Linux. Quindi in Linux è necessario modificare in

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

"SHA1PRNG" è l'algoritmo utilizzato, puoi fare riferimento qui per maggiori informazioni sugli algoritmi.


Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.