Pourquoi un mauvais mot de passe provoque-t-il le message « Le remplissage n'est pas valide et ne peut pas être supprimé » ?

StackOverflow https://stackoverflow.com/questions/11762

  •  08-06-2019
  •  | 
  •  

Question

J'avais besoin d'un simple cryptage de chaîne, j'ai donc écrit le code suivant (avec beaucoup "d'inspiration" de ici):

    // create and initialize a crypto algorithm
    private static SymmetricAlgorithm getAlgorithm(string password) {
        SymmetricAlgorithm algorithm = Rijndael.Create();
        Rfc2898DeriveBytes rdb = new Rfc2898DeriveBytes(
            password, new byte[] {
            0x53,0x6f,0x64,0x69,0x75,0x6d,0x20,             // salty goodness
            0x43,0x68,0x6c,0x6f,0x72,0x69,0x64,0x65
        }
        );
        algorithm.Padding = PaddingMode.ISO10126;
        algorithm.Key = rdb.GetBytes(32);
        algorithm.IV = rdb.GetBytes(16);
        return algorithm;
    }

    /* 
     * encryptString
     * provides simple encryption of a string, with a given password
     */
    public static string encryptString(string clearText, string password) {
        SymmetricAlgorithm algorithm = getAlgorithm(password);
        byte[] clearBytes = System.Text.Encoding.Unicode.GetBytes(clearText);
        MemoryStream ms = new MemoryStream();
        CryptoStream cs = new CryptoStream(ms, algorithm.CreateEncryptor(), CryptoStreamMode.Write);
        cs.Write(clearBytes, 0, clearBytes.Length);
        cs.Close();
        return Convert.ToBase64String(ms.ToArray());
    }

    /*
     * decryptString
     * provides simple decryption of a string, with a given password
     */
    public static string decryptString(string cipherText, string password) {
        SymmetricAlgorithm algorithm = getAlgorithm(password);
        byte[] cipherBytes = Convert.FromBase64String(cipherText);
        MemoryStream ms = new MemoryStream();
        CryptoStream cs = new CryptoStream(ms, algorithm.CreateDecryptor(), CryptoStreamMode.Write);
        cs.Write(cipherBytes, 0, cipherBytes.Length);
        cs.Close();            
        return System.Text.Encoding.Unicode.GetString(ms.ToArray());
    }

Le code semble fonctionner correctement, sauf que lors du déchiffrement des données avec une clé incorrecte, j'obtiens une CryptographicException - "Le remplissage n'est pas valide et ne peut pas être supprimé" - sur la ligne cs.Close() dans decryptString.

exemple de code :

    string password1 = "password";
    string password2 = "letmein";
    string startClearText = "The quick brown fox jumps over the lazy dog";
    string cipherText = encryptString(startClearText, password1);
    string endClearText = decryptString(cipherText, password2);     // exception thrown

Ma question est la suivante : est-ce normal ?J'aurais pensé que le décryptage avec un mauvais mot de passe entraînerait simplement une sortie absurde, plutôt qu'une exception.

Était-ce utile?

La solution

Bien que cette question ait déjà reçu une réponse, je pense que ce serait une bonne idée d'expliquer pourquoi il faut s'y attendre.

Un schéma de remplissage est généralement appliqué car la plupart des filtres cryptographiques ne sont pas sémantiquement sécurisés et pour empêcher certaines formes de cryptoattaques.Par exemple, généralement en RSA, le OAEP Un schéma de remplissage est utilisé pour empêcher certains types d'attaques (telles qu'une attaque en texte clair choisie ou aveuglant).

Un schéma de remplissage ajoute des déchets (généralement) aléatoires au message m avant que le message ne soit envoyé.Dans la méthode OAEP par exemple, deux Oracles sont utilisés (c'est une explication simpliste) :

  1. Compte tenu de la taille du module, vous remplissez k1 bits avec 0 et k0 bits avec un nombre aléatoire.
  2. Ensuite, en appliquant une transformation au message, vous obtenez le message complété qui est crypté et envoyé.

Cela vous fournit une randomisation des messages et un moyen de tester si le message est inutile ou non.Comme le schéma de remplissage est réversible, lorsque vous déchiffrez le message alors que vous ne pouvez rien dire sur l'intégrité du message lui-même, vous pouvez en fait faire une affirmation sur le remplissage et ainsi savoir si le message a été correctement déchiffré. ou vous faites quelque chose de mal (c'est-à-dire que quelqu'un a falsifié le message ou que vous utilisez la mauvaise clé)

Autres conseils

J'ai connu un «rembourrage similaire n'est pas valide et ne peut pas être supprimé». Exception, mais dans mon cas, la clé IV et le rembourrage étaient corrects.

Il s’est avéré que tout ce qui manquait était de vider le flux cryptographique.

Comme ça:

            MemoryStream msr3 = new MemoryStream();
            CryptoStream encStream = new CryptoStream(msr3, RijndaelAlg.CreateEncryptor(), CryptoStreamMode.Write);
            encStream.Write(bar2, 0, bar2.Length);
            // unless we flush the stream we would get "Padding is invalid and cannot be removed." exception when decoding
            encStream.FlushFinalBlock();
            byte[] bar3 = msr3.ToArray();

Si vous voulez que votre utilisation soit correcte, vous devez ajouter authentification à votre texte chiffré afin que vous puissiez vérifier qu'il s'agit du bon mot de passe ou que le texte chiffré n'a pas été modifié.Le rembourrage que vous utilisez ISO10126 ne lèvera une exception que si le dernier octet n'est pas déchiffré comme l'une des 16 valeurs valides pour le remplissage (0x01-0x10).Vous avez donc 1/16 de chance que l'exception ne soit PAS levée avec le mauvais mot de passe, où si vous l'authentifiez, vous disposez d'un moyen déterministe de savoir si votre décryptage est valide.

Utiliser les API de cryptographie, bien qu'apparemment facile, est en réalité plutôt facile à commettre des erreurs.Par exemple, vous utilisez un sel fixe pour votre clé et votre dérivation iv, cela signifie que chaque texte chiffré avec le même mot de passe réutilisera son IV avec cette clé, ce qui brise la sécurité sémantique avec le mode CBC, le IV doit être à la fois imprévisible et unique pour une clé donnée.

En raison des erreurs faciles à commettre, j'ai un extrait de code, que j'essaie de garder révisé et à jour (commentaires, problèmes bienvenus) :

Exemples modernes de chiffrement symétrique authentifié d’une chaîne C#.

Si vous l'utilisez, c'est AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password) lorsque le mauvais mot de passe est utilisé, null est renvoyé si le texte chiffré ou iv a été modifié après le chiffrement null est renvoyé, vous ne récupérerez jamais de données indésirables ni d'exception de remplissage.

Si vous avez exclu une incompatibilité de clé, alors en plus FlushFinalBlock() (voir la réponse de Yaniv), appelant Close() sur le CryptoStream suffira également.

Si vous nettoyez les ressources strictement avec using blocs, assurez-vous d'imbriquer le bloc pour le CryptoStream lui-même :

using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
{
  using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
  {
    encStream.Write(bar2, 0, bar2.Length);
  } // implicit close
  byte[] encArray = ms.ToArray();
}

J'ai été mordu par ceci (ou similaire):

using (MemoryStream ms = new MemoryStream())
using (var enc = RijndaelAlg.CreateEncryptor())
using (CryptoStream encStream = new CryptoStream(ms, enc, CryptoStreamMode.Write))
{
  encStream.Write(bar2, 0, bar2.Length);
  byte[] encArray = ms.ToArray();
} // implicit close -- too late!

Oui, c’est normal, ou du moins, c’est exactement ce qui se passe lorsque nos routines cryptographiques obtiennent des données non déchiffrables.

Une autre raison de l'exception pourrait être une condition de concurrence entre plusieurs threads utilisant la logique de déchiffrement - les implémentations natives d'ICryptoTransform sont pas thread-safe (par exemple.SymmetricAlgorithm), il doit donc être placé dans une section exclusive, par ex.en utilisant verrouillage.Veuillez vous référer ici pour plus de détails : http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/

Il peut y avoir des octets non lus dans le CryptoStream.La fermeture avant de lire complètement le flux provoquait l'erreur dans mon programme.

J'ai eu un problème similaire, le problème dans la méthode de décryptage était l'initialisation d'un flux mémoire vide.quand cela a fonctionné lorsque je l'ai initialisé avec le tableau d'octets de texte chiffré comme ceci :

MemoryStream ms = new MemoryStream(cipherText)

La réponse mise à jour par l'utilisateur "atconway" a fonctionné pour moi.

Le problème ne venait pas du remplissage mais de la clé qui était différente lors du cryptage et du déchiffrement.La clé et iv doivent être identiques lors du cryptage et du déchiffrement de la même valeur.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top