Angesichts des letzten Blocks nicht richtig gepolstert


115

Ich versuche, einen kennwortbasierten Verschlüsselungsalgorithmus zu implementieren, erhalte jedoch die folgende Ausnahme:

javax.crypto.BadPaddingException: Der letzte Block ist nicht richtig aufgefüllt

Was könnte das Problem sein?

Hier ist mein Code:

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

(Der JUnit-Test)

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

Antworten:


197

Wenn Sie versuchen, PKCS5-aufgefüllte Daten mit dem falschen Schlüssel zu entschlüsseln und dann zu entpacken (was von der Cipher-Klasse automatisch durchgeführt wird), erhalten Sie höchstwahrscheinlich die BadPaddingException (mit wahrscheinlich etwas weniger als 255/256, ungefähr 99,61%) ), da die Polsterung eine spezielle Struktur hat, die beim Entpacken validiert wird und nur sehr wenige Tasten eine gültige Polsterung erzeugen würden.

Wenn Sie diese Ausnahme erhalten, fangen Sie sie ab und behandeln Sie sie als "falschen Schlüssel".

Dies kann auch passieren, wenn Sie ein falsches Kennwort eingeben, das dann zum Abrufen des Schlüssels aus einem Schlüsselspeicher verwendet wird oder das mithilfe einer Schlüsselgenerierungsfunktion in einen Schlüssel konvertiert wird.

Natürlich kann es auch zu einer schlechten Auffüllung kommen, wenn Ihre Daten beim Transport beschädigt werden.

Es gibt jedoch einige Sicherheitshinweise zu Ihrem System:

  • Für die kennwortbasierte Verschlüsselung sollten Sie eine SecretKeyFactory und PBEKeySpec verwenden, anstatt ein SecureRandom mit KeyGenerator zu verwenden. Der Grund dafür ist, dass SecureRandom bei jeder Java-Implementierung ein anderer Algorithmus sein kann, der Ihnen einen anderen Schlüssel gibt. Die SecretKeyFactory führt die Schlüsselableitung auf definierte Weise durch (und auf eine Weise, die als sicher angesehen wird, wenn Sie den richtigen Algorithmus auswählen).

  • Verwenden Sie nicht den EZB-Modus. Es verschlüsselt jeden Block unabhängig, was bedeutet, dass identische Klartextblöcke auch immer identische Chiffretextblöcke ergeben.

    Verwenden Sie vorzugsweise einen sicheren Betriebsmodus wie CBC (Cipher Block Chaining) oder CTR (Counter). Alternativ können Sie einen Modus verwenden, der auch die Authentifizierung umfasst, z. B. GCM (Galois-Counter-Modus) oder CCM (Counter mit CBC-MAC) (siehe nächster Punkt).

  • Normalerweise möchten Sie nicht nur Vertraulichkeit, sondern auch Authentifizierung, um sicherzustellen, dass die Nachricht nicht manipuliert wird. (Dies verhindert auch Angriffe mit ausgewähltem Chiffretext auf Ihre Chiffre, dh hilft der Vertraulichkeit.) Fügen Sie Ihrer Nachricht also einen MAC (Nachrichtenauthentifizierungscode) hinzu, oder verwenden Sie einen Chiffriermodus, der die Authentifizierung umfasst (siehe vorherigen Punkt).

  • DES hat eine effektive Schlüsselgröße von nur 56 Bit. Dieser Schlüsselraum ist recht klein und kann in einigen Stunden von einem engagierten Angreifer brutal erzwungen werden. Wenn Sie Ihren Schlüssel mit einem Passwort generieren, wird dies noch schneller. Außerdem hat DES eine Blockgröße von nur 64 Bit, was einige weitere Schwächen in den Verkettungsmodi hinzufügt. Verwenden Sie stattdessen einen modernen Algorithmus wie AES mit einer Blockgröße von 128 Bit und einer Schlüsselgröße von 128 Bit (für die Standardvariante).


1
Ich möchte nur bestätigen. Ich bin neu in der Verschlüsselung und dies ist mein Szenario. Ich verwende die AES-Verschlüsselung. In meiner Verschlüsselungs- / Entschlüsselungsfunktion verwende ich einen Verschlüsselungsschlüssel. Ich habe beim Entschlüsseln einen falschen Verschlüsselungsschlüssel verwendet und diesen erhalten javax.crypto.BadPaddingException: Given final block not properly padded. Sollte ich dies als falschen Schlüssel behandeln?
Kenicky

Um ganz klar zu sein, kann dies auch passieren, wenn Sie ein falsches Kennwort für eine Schlüsselspeicherdatei eingeben, z. B. eine .p12-Datei, was mir gerade passiert ist.
Warren Dew

2
@WarrenDew "Falsches Passwort für eine Schlüsselspeicherdatei" ist nur ein Sonderfall von "falschem Schlüssel".
Paŭlo Ebermann

@kenicky Entschuldigung, ich habe Ihren Kommentar gerade gesehen ... ja, ein falscher Schlüssel verursacht fast immer diesen Effekt. (Natürlich sind beschädigte Daten eine weitere Möglichkeit.)
Paŭlo Ebermann

@ PaŭloEbermann Ich stimme zu, aber ich denke nicht, dass dies unbedingt sofort offensichtlich ist, da es sich von der Situation im ursprünglichen Beitrag unterscheidet, in der der Programmierer die Kontrolle über den Schlüssel und die Entschlüsselung hat. Ich fand Ihre Antwort jedoch nützlich genug, um sie zu verbessern.
Warren Dew

1

Abhängig von dem von Ihnen verwendeten Kryptografiealgorithmus müssen Sie möglicherweise am Ende einige Auffüllbytes hinzufügen, bevor Sie ein Bytearray verschlüsseln, damit die Länge des Bytearrays ein Vielfaches der Blockgröße beträgt:

Speziell in Ihrem Fall ist das von Ihnen gewählte Auffüllschema PKCS5, das hier beschrieben wird: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_ CJ _SYM__PAD.html

(Ich gehe davon aus, dass Sie das Problem haben, wenn Sie versuchen zu verschlüsseln)

Sie können Ihr Auffüllschema auswählen, wenn Sie das Cipher-Objekt instanziieren. Unterstützte Werte hängen vom verwendeten Sicherheitsanbieter ab.

Sind Sie übrigens sicher, dass Sie einen symmetrischen Verschlüsselungsmechanismus zum Verschlüsseln von Passwörtern verwenden möchten? Wäre ein Einweg-Hash nicht besser? Wenn Sie wirklich in der Lage sein müssen, Passwörter zu entschlüsseln, ist DES eine ziemlich schwache Lösung. Sie könnten daran interessiert sein, etwas Stärkeres wie AES zu verwenden, wenn Sie bei einem symmetrischen Algorithmus bleiben müssen.


1
Könnten Sie bitte den Code posten, der versucht zu verschlüsseln / entschlüsseln? (und überprüfen Sie, ob das Byte-Array, das Sie zu entschlüsseln versuchen, nicht größer als die Blockgröße ist)
fpacifici

1
Ich bin sehr neu in Java und auch in der Kryptografie, daher kenne ich immer noch keine besseren Möglichkeiten zur Verschlüsselung. Ich möchte nur dieses Problem lösen, als nach besseren Möglichkeiten zu suchen, es umzusetzen.
Altrim

Können Sie den Link aktualisieren, weil er nicht funktioniert? @fpacifici und ich habe meinen Beitrag aktualisiert. Ich habe den JUnit-Test aufgenommen, der die Verschlüsselung und Entschlüsselung
testet

Korrigiert (sorry Fehler beim Kopieren und Einfügen). In der Tat tritt Ihr Problem auf, da Sie mit einem Schlüssel entschlüsseln, der nicht mit dem von Paulo erläuterten Schlüssel übereinstimmt. Dies geschieht, da die mit @Before in junit annotierte Methode vor jeder Testmethode ausgeführt wird, wodurch der Schlüssel jedes Mal neu generiert wird. Da der Schlüssel zufällig initialisiert wird, ist er jedes Mal anders.
fpacifici

1

Ich bin auf dieses Problem aufgrund des Betriebssystems gestoßen, das einfach zu einer anderen Plattform für die JRE-Implementierung gehört.

new SecureRandom(key.getBytes())

wird unter Windows den gleichen Wert erhalten, während es unter Linux anders ist. Also muss unter Linux auf geändert werden

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

„SHA1PRNG“ ist der Algorithmus verwendet, Sie verweisen hier für weitere Informationen über Algorithmen.


Durch die Nutzung unserer Website bestätigen Sie, dass Sie unsere Cookie-Richtlinie und Datenschutzrichtlinie gelesen und verstanden haben.
Licensed under cc by-sa 3.0 with attribution required.