Pourquoi un mot de passe incorrect provoque-t-il «le remplissage est invalide et ne peut pas être supprimé»?

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

// create and initialize a crypto algorithm private static SymmesortingcAlgorithm getAlgorithm(ssortingng password) { SymmesortingcAlgorithm 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; } /* * encryptSsortingng * provides simple encryption of a ssortingng, with a given password */ public static ssortingng encryptSsortingng(ssortingng clearText, ssortingng password) { SymmesortingcAlgorithm 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.ToBase64Ssortingng(ms.ToArray()); } /* * decryptSsortingng * provides simple decryption of a ssortingng, with a given password */ public static ssortingng decryptSsortingng(ssortingng cipherText, ssortingng password) { SymmesortingcAlgorithm algorithm = getAlgorithm(password); byte[] cipherBytes = Convert.FromBase64Ssortingng(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.GetSsortingng(ms.ToArray()); } 

Le code semble fonctionner correctement, sauf que lors du déchiffrement des données avec une clé incorrecte, je reçois une exception CryptographicException – “Le remplissage est invalide et ne peut pas être supprimé” – sur la ligne cs.Close () de decryptSsortingng.

exemple de code:

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

Ma question est la suivante: est-ce à prévoir? J’aurais pensé que le déchiffrement avec un mot de passe incorrect entraînerait simplement une sortie non-sens, plutôt qu’une exception.

Bien que cela ait déjà été répondu, 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écurisés sur le plan sémantique et empêchent certaines formes de cryptoatacks. Par exemple, le schéma de remplissage OAEP est généralement utilisé dans RSA pour empêcher certaines attaques (par exemple, une attaque de texte en clair ou un aveuglement ).

Un schéma de remplissage ajoute certains messages (généralement) aléatoires au message m avant l’envoi du message. Dans la méthode OAEP, par exemple, deux oracles sont utilisés (il s’agit d’une explication simpliste):

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

Cela vous fournit une randomisation pour les messages et un moyen de tester si le message est vide ou non. Comme le schéma de remplissage est réversible, lorsque vous décryptez le message alors que vous ne pouvez rien dire sur l’intégrité du message, vous pouvez en fait affirmer le remplissage et ainsi savoir si le message a été correctement déchiffré. ou vous faites quelque chose de mal (c.-à-d. quelqu’un a falsifié le message ou vous utilisez la mauvaise clé)

J’ai vécu une expérience similaire “Le rembourrage est invalide et ne peut pas être supprimé.” exception, mais dans mon cas, la clé IV et le remplissage étaient corrects.

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

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 souhaitez que votre utilisation soit correcte, vous devez append une authentification à votre texte chiffré afin de vérifier qu’il s’agit du mot de passe correct ou que le texte chiffré n’a pas été modifié. Le remplissage que vous utilisez ISO10126 ne lancera qu’une exception si le dernier octet ne décrypte pas comme l’une des 16 valeurs valides pour le remplissage (0x01-0x10). Vous avez donc une chance sur 1/16 de ne pas lancer l’exception avec un mot de passe erroné. Si vous l’authentifiez, vous disposez d’une méthode déterministe pour déterminer si votre déchiffrement est valide.

Utiliser des cryptos api semble facile, mais il est plutôt facile de faire des erreurs. Par exemple, vous utilisez un sel fixe pour votre clé et la dérivation iv, ce qui signifie que chaque texte chiffré avec le même mot de passe sera ré-utilisé avec cette clé, ce qui brise la sécurité sémantique avec le mode CBC. une clé donnée.

Pour cette raison, facile à faire des erreurs, j’ai un extrait de code, que j’essaye de garder à jour et à jour (commentaires, problèmes bienvenus):

Exemples modernes de cryptage authentifié symésortingque d’une chaîne C #.

Si vous utilisez son AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password) lorsque le mot de passe incorrect est utilisé, null est renvoyé, si le cryptogramme ou iv a été modifié, la valeur null est renvoyée.

Oui, c’est à prévoir, ou du moins, c’est exactement ce qui se produit lorsque nos routines cryptographiques obtiennent des données non déchiffrables

Si vous avez exclu une non-concordance de clé, alors, outre FlushFinalBlock() (voir la réponse de Yaniv), l’appel de Close() sur le CryptoStream sera également suffisant.

Si vous nettoyez les ressources à l’ using blocs, veillez à 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! 

Une autre raison de cette exception pourrait être une situation de concurrence entre plusieurs threads utilisant la logique de décryptage – les implémentations natives d’ICryptoTransform ne sont pas sûres pour les threads (par exemple SymmesortingcAlgorithm). S’il vous plaît se 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 la lecture complète du stream était à l’origine de 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 stream de mémoire vide. quand il a fonctionné quand je l’ai initialisé avec le tableau d’octets de texte de chiffrement 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 rembourrage mais de la clé qui était différente lors du chiffrement et du déchiffrement. La clé et l’iv doivent être identiques lors de l’encryptage et du décryptage de la même valeur.