web-dev-qa-db-fra.com

Étant donné que le dernier bloc n'est pas correctement rembourré

J'essaie d'implémenter l'algorithme de chiffrement basé sur mot de passe, mais j'obtiens cette exception:

javax.crypto.BadPaddingException: Le dernier bloc n'est pas correctement rempli

Quel pourrait être le problème? (Je suis nouveau sur Java.)

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

(Le 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.
        }
    }
}
93
Altrim

Si vous essayez de déchiffrer des données enrichies en PKCS5 avec la mauvaise clé, puis de les décompresser (ce que fait automatiquement la classe Cipher), vous obtiendrez probablement la BadPaddingException (avec probablement un peu moins de 255/256, environ 99,61% ), car le padding a une structure spéciale qui est validée pendant unpad et très peu de clés produiraient un padding valide.

Donc, si vous obtenez cette exception, récupérez-la et traitez-la comme une "mauvaise clé".

Cela peut également se produire lorsque vous fournissez un mot de passe incorrect, qui est ensuite utilisé pour extraire la clé d'un magasin de clés ou qui est converti en clé à l'aide d'une fonction de génération de clé.

Bien entendu, un remplissage incorrect peut également survenir si vos données sont corrompues lors du transport.

Cela dit, il y a quelques remarques de sécurité à propos de votre stratagème:

  • Pour le cryptage basé sur un mot de passe, vous devez utiliser SecretKeyFactory et PBEKeySpec au lieu d’utiliser SecureRandom avec KeyGenerator. La raison en est que SecureRandom pourrait être un algorithme différent sur chaque implémentation Java, vous donnant une clé différente. SecretKeyFactory effectue la dérivation de clé d'une manière définie (et d'une manière jugée sécurisée si vous sélectionnez le bon algorithme).

  • N'utilisez pas le mode ECB. Il chiffre chaque bloc indépendamment, ce qui signifie que des blocs de texte brut identiques donnent également des blocs de texte crypté toujours identiques.

    Utilisez de préférence un mode de fonctionnement sécurisé, comme CBC (chaînage de blocs chiffrés) ou CTR (compteur). Vous pouvez également utiliser un mode incluant également une authentification, tel que GCM (mode Galois-Counter) ou CCM (Counter with CBC-MAC), voir point suivant.

  • Normalement, vous ne voulez pas seulement la confidentialité, mais également l’authentification, ce qui garantit que le message n’est pas falsifié. (Cela évite également les attaques de texte chiffré choisies sur votre chiffre, c’est-à-dire aide à la confidentialité.) Ajoutez donc un MAC (code d’authentification du message) à votre message ou utilisez un mode de chiffrement incluant l’authentification (voir le point précédent).

  • DES a une taille de clé effective de 56 bits seulement. Cet espace clé est assez petit, il peut être forcé en quelques heures par un attaquant dédié. Si vous générez votre clé à l'aide d'un mot de passe, cela ira encore plus vite… .. De plus, la taille de bloc de DES n'est que de 64 bits, ce qui ajoute quelques faiblesses supplémentaires dans les modes d'enchaînement . Utilisez un algorithme moderne. AES à la place, qui a une taille de bloc de 128 bits et une taille de clé de 128 bits (pour la variante standard).

177
Paŭlo Ebermann

en fonction de l'algorithme de cryptographie que vous utilisez, vous devrez peut-être ajouter des octets de remplissage à la fin avant de chiffrer un tableau d'octets de sorte que la longueur du tableau d'octets soit multiple de la taille du bloc:

Dans votre cas, le schéma de remplissage que vous avez choisi est PKCS5, qui est décrit ci-après: http://www.rsa.com/products/bsafe/documentation/cryptoj35html/doc/dev_guide/group_CJ _SYM__PAD.html

(Je suppose que vous avez le problème lorsque vous essayez de chiffrer)

Vous pouvez choisir votre schéma de remplissage lorsque vous instanciez l'objet Cipher. Les valeurs prises en charge dépendent du fournisseur de sécurité que vous utilisez. 

Au fait, êtes-vous sûr de vouloir utiliser un mécanisme de chiffrement symétrique pour chiffrer les mots de passe? Ne serait-ce pas mieux un hash à sens unique? Si vous avez vraiment besoin de pouvoir déchiffrer les mots de passe, DES est une solution assez faible, vous voudrez peut-être utiliser quelque chose de plus fort comme AES si vous devez conserver un algorithme symétrique.

1
fpacifici

J'ai rencontré ce problème en raison du système d'exploitation, simple à la plate-forme différente sur la mise en œuvre de JRE. 

        new SecureRandom(key.getBytes())

aura la même valeur dans Windows, alors que c'est différent sous Linux. Donc, sous Linux, il faut changer pour

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

"SHA1PRNG" est l'algorithme utilisé, vous pouvez vous référer ici pour plus d'informations sur les algorithmes.

0
Bejond