パスワードが間違っていると「パディングが無効なので削除できません」というメッセージが表示されるのはなぜですか?
-
08-06-2019 - |
質問
単純な文字列暗号化が必要だったので、次のコードを書きました(からの多くの「インスピレーション」をもとに) ここ):
// 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());
}
コードは正常に動作しているように見えますが、間違ったキーでデータを復号化すると、decryptString の cs.Close() 行で CryptographicException (「パディングが無効なので削除できません」) が発生する点が異なります。
コード例:
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
私の質問は、これは予想されることですか?ということです。間違ったパスワードを使用して復号化すると、例外が発生するのではなく、意味のない出力が返されるだけだと考えていたでしょう。
解決
これはすでに回答されていますが、説明するのが良いと思います なぜ それは予想されることです。
ほとんどの暗号フィルターは意味的に安全ではないため、また、ある種の暗号攻撃を防ぐために、通常はパディング スキームが適用されます。たとえば、通常 RSA では、 OAEP ある種の攻撃(選択された平文攻撃や 目がくらむ).
パディング スキームは、メッセージが送信される前に、メッセージ m に (通常は) ランダムなガベージを追加します。たとえば、OAEP 方式では、2 つの Oracle が使用されます (これは単純化した説明です)。
- モジュラスのサイズを考慮して、k1 ビットに 0 を埋め込み、k0 ビットに乱数を埋め込みます。
- 次に、メッセージに何らかの変換を適用すると、暗号化されて送信される埋め込みメッセージが得られます。
これにより、メッセージがランダム化され、メッセージがガベージかどうかをテストする方法が提供されます。パディング スキームは可逆であるため、メッセージを復号化すると、メッセージ自体の整合性については何も言えませんが、実際にはパディングについて何らかの主張を行うことができ、メッセージが正しく復号化されたかどうかを知ることができます。または、何か間違ったことをしています (つまり、誰かがメッセージを改ざんしたか、間違ったキーを使用しているなど)
他のヒント
私は同様の「パディングが無効であり、取り外すことはできません」を経験しました。例外ですが、私の場合、キーIVとパディングは正しかったです。
不足しているのは暗号ストリームをフラッシュすることだけであることが判明しました。
このような:
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();
使用法を正しくしたい場合は、以下を追加する必要があります 認証 これにより、パスワードが正しいかどうか、または暗号文が変更されていないことを確認できます。使用しているパッド ISO10126 最後のバイトがパディングの 16 の有効な値 (0x01 ~ 0x10) のいずれかとして復号化されない場合にのみ例外をスローします。したがって、間違ったパスワードで例外をスローしない可能性は 1/16 あり、認証すれば、復号化が有効かどうかを決定的に知ることができます。
暗号 API の使用は簡単そうに見えますが、実際には間違いを犯しやすいものです。たとえば、キーと IV 導出に固定ソルトを使用すると、同じパスワードで暗号化されたすべての暗号文がそのキーで IV を再利用することになり、CBC モードでのセマンティック セキュリティが破られます。IV は予測不可能であり、一意である必要があります。指定されたキー。
間違いやすいという理由から、コード スニペットを用意し、常に見直して最新の状態に保つようにしています (コメント、問題は歓迎です)。
使用する場合は、 AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)
間違ったパスワードが使用された場合、 null
暗号文または iv が暗号化後に変更された場合は、返されます。 null
が返されると、ジャンク データが返されたり、パディング例外が返されたりすることはありません。
キーの不一致を除外した場合は、さらに FlushFinalBlock()
(Yaniv の回答を参照)、呼び出し Close()
で CryptoStream
も十分です。
リソースを厳密にクリーンアップしている場合は、 using
ブロックの場合は、必ずブロックをネストしてください。 CryptoStream
自体:
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();
}
私はこれ(または同様のもの)に噛まれました:
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!
はい、これは予想されることです。少なくとも、暗号化ルーチンが復号化不可能なデータを取得したときに起こることです。
例外のもう 1 つの理由は、復号化ロジックを使用した複数のスレッド間の競合状態である可能性があります。ICryptoTransform のネイティブ実装は次のとおりです。 スレッドセーフではない (例えば。SymmetricAlgorithm) なので、排他的なセクションに置く必要があります。を使用して ロック。詳細については、こちらを参照してください。 http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/
CryptoStream には未読のバイトがいくつかある可能性があります。ストリームを完全に読み取る前に閉じると、プログラムでエラーが発生していました。
私も同様の問題を抱えていました。復号化メソッドの問題は、空のメモリストリームを初期化することでした。次のように暗号テキストのバイト配列で初期化すると機能しました:
MemoryStream ms = new MemoryStream(cipherText)
ユーザー「atconway」によって更新された回答が私にとっては役に立ちました。
問題はパディングにあるのではなく、暗号化と復号化中に異なるキーにありました。同じ値を暗号化および復号化するとき、キーと iv は同じである必要があります。