Le bloc final n'est pas correctement rembourré


115

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

javax.crypto.BadPaddingException: le bloc final donné n'est pas correctement rempli

Quel pourrait être le problème?

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

Réponses:


197

Si vous essayez de décrypter les données remplies par PKCS5 avec la mauvaise clé, puis de les décompresser (ce qui est fait automatiquement par la classe Cipher), vous obtiendrez probablement l'exception BadPaddingException (avec probablement un peu moins de 255/256, environ 99,61% ), parce que le remplissage a une structure spéciale qui est validée pendant unpad et que très peu de touches produiraient un remplissage valide.

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

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

Bien sûr, un mauvais remplissage peut également se produire si vos données sont corrompues pendant le transport.

Cela dit, il y a quelques remarques de sécurité concernant votre système:

  • Pour le chiffrement basé sur un mot de passe, vous devez utiliser un SecretKeyFactory et PBEKeySpec au lieu d'utiliser un 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 crypte chaque bloc indépendamment, ce qui signifie que des blocs de texte brut identiques donnent également des blocs de texte chiffré toujours identiques.

    Utilisez de préférence un mode de fonctionnement sécurisé , comme CBC (Cipher block chaining) ou CTR (Counter). Vous pouvez également utiliser un mode qui inclut également l'authentification, comme GCM (mode Galois-Counter) ou CCM (compteur avec CBC-MAC), voir le point suivant.

  • Normalement, vous ne voulez pas seulement la confidentialité, mais aussi l'authentification, qui garantit que le message n'est pas falsifié. (Cela empêche également les attaques par texte chiffré choisi sur votre chiffrement, c'est-à-dire aide à la confidentialité.) Alors, ajoutez un MAC (code d'authentification de message) à votre message, ou utilisez un mode de chiffrement qui inclut l'authentification (voir point précédent).

  • DES a une taille de clé effective de seulement 56 bits. Cet espace clé est assez petit, il peut être forcé brutalement en quelques heures par un attaquant dédié. Si vous générez votre clé par mot de passe, cela deviendra encore plus rapide. En outre, DES a une taille de bloc de seulement 64 bits, ce qui ajoute quelques faiblesses supplémentaires dans les modes de chaînage. Utilisez plutôt un algorithme moderne comme AES, qui a une taille de bloc de 128 bits et une taille de clé de 128 bits (pour la variante standard).


1
Je veux juste confirmer. Je suis nouveau dans le cryptage et c'est mon scénario, j'utilise le cryptage AES. dans ma fonction de chiffrement / déchiffrement, j'utilise une clé de chiffrement. J'ai utilisé une mauvaise clé de chiffrement pour déchiffrer et j'ai obtenu ceci javax.crypto.BadPaddingException: Given final block not properly padded. Dois-je traiter cela comme une mauvaise clé?
kenicky

Pour être clair, cela peut également se produire lorsque vous fournissez un mot de passe incorrect pour un fichier de stockage de clés, tel qu'un fichier .p12, ce qui vient de m'arriver.
Warren Dew

2
@WarrenDew "Mauvais mot de passe pour un fichier de stockage de clés" est juste un cas particulier de "mauvaise clé".
Paŭlo Ebermann

@kenicky désolé, j'ai vu votre commentaire tout à l'heure ... oui, une mauvaise touche provoque presque toujours cet effet. (Bien sûr, les données corrompues sont une autre possibilité.)
Paŭlo Ebermann

@ PaŭloEbermann Je suis d'accord, mais je ne pense pas que ce soit forcément immédiatement évident, car c'est différent de la situation dans le post original où le programmeur contrôle la clé et le décryptage. J'ai cependant trouvé votre réponse suffisamment utile pour la voter.
Warren Dew

1

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 afin que la longueur du tableau d'octets soit multiple de la taille du bloc:

Plus précisément dans votre cas, le schéma de remplissage que vous avez choisi est PKCS5 qui est décrit ici: 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 cryptage symétrique pour crypter les mots de passe? Ne serait-il pas meilleur 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 rester avec un algorithme symétrique.


1
alors pourriez-vous s'il vous plaît poster le code qui tente de crypter / décrypter? (et vérifiez que le tableau d'octets que vous essayez de déchiffrer n'est pas plus grand que la taille du bloc)
fpacifici

1
Je suis très nouveau sur Java et aussi sur la cryptographie, donc je ne connais toujours pas de meilleures façons de faire du cryptage. Je veux juste faire celui-ci plutôt que de chercher probablement de meilleures façons de le mettre en œuvre.
Altrim

pouvez-vous mettre à jour le lien car cela ne fonctionne pas @fpacifici et j'ai mis à jour mon message J'ai inclus le test JUnit qui teste le cryptage et le décryptage
Altrim

Corrigé (désolé erreur de copier-coller). Quoi qu'il en soit, en effet, votre problème survient puisque vous décryptez avec une clé qui n'est pas la même que celle utilisée pour le cryptage comme expliqué par Paulo. Cela se produit puisque la méthode annotée avec @Before dans junit est exécutée avant chaque méthode de test, régénérant ainsi la clé à chaque fois. puisque la clé est initialisée aléatoirement, elle sera différente à chaque fois.
fpacifici

1

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

new SecureRandom(key.getBytes())

obtiendra la même valeur sous Windows, alors qu'elle est différente 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.


En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.