C# 为什么密码错误会导致“Padding 无效且无法删除”?

声明:本页面是StackOverFlow热门问题的中英对照翻译,遵循CC BY-SA 4.0协议,如果您需要使用它,必须同样遵循CC BY-SA许可,注明原文地址和作者信息,同时你必须将它归于原作者(不是我):StackOverFlow 原文地址: http://stackoverflow.com/questions/11762/
Warning: these are provided under cc-by-sa 4.0 license. You are free to use/share it, But you must attribute it to the original authors (not me): StackOverFlow

提示:将鼠标放在中文语句上可以显示对应的英文。显示中英文
时间:2020-07-31 17:39:42  来源:igfitidea点击:

Why does a bad password cause "Padding is invalid and cannot be removed"?

提问by Blorgbeard is out

I needed some simple string encryption, so I wrote the following code (with a great deal of "inspiration" from here):

我需要一些简单的字符串加密,所以我写了下面的代码(从这里得到了很多“灵感” ):

    // 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());
    }

The code appears to work fine, except that when decrypting data with an incorrect key, I get a CryptographicException - "Padding is invalid and cannot be removed" - on the cs.Close() line in decryptString.

该代码似乎工作正常,除了在使用不正确的密钥解密数据时,我在解密字符串的 cs.Close() 行上收到 CryptographicException -“填充无效且无法删除”。

example 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

My question is, is this to be expected? I would have thought that decrypting with the wrong password would just result in nonsense output, rather than an exception.

我的问题是,这是意料之中的吗?我原以为用错误的密码解密只会导致无意义的输出,而不是异常。

采纳答案by Jorge Córdoba

Although this have been already answered I think it would be a good idea to explain whyit is to be expected.

虽然这已经得到了回答,但我认为解释为什么会出现这种情况是个好主意。

A padding scheme is usually applied because most cryptographic filters are not semantically secure and to prevent some forms of cryptoatacks. For example, usually in RSA the OAEPpadding scheme is used which prevents some sorts of attacks (such as a chosen plaintext attack or blinding).

填充方案通常被应用,因为大多数加密过滤器在语义上并不安全并且可以防止某些形式的密码攻击。例如,通常在 RSA 中使用OAEP填充方案来防止某些类型的攻击(例如选择的明文攻击或致盲)。

A padding scheme appends some (usually) random garbage to the message m before the message is sent. In the OAEP method, for example, two Oracles are used (this is a simplistic explanation):

在发送消息之前,填充方案将一些(通常)随机垃圾附加到消息 m 中。例如在OAEP方法中,使用了两个Oracles(这是一个简单的解释):

  1. Given the size of the modulus you padd k1 bits with 0 and k0 bits with a random number.
  2. Then by applying some transformation to the message you obtain the padded message wich is encrypted and sent.
  1. 给定模数的大小,您用 0 和 k0 位填充 k1 位随机数。
  2. 然后通过对消息应用一些转换,您可以获得加密并发送的填充消息。

That provides you with a randomization for the messages and with a way to test if the message is garbage or not. As the padding scheme is reversible, when you decrypt the message whereas you can't say anything about the integrity of the message itself you can, in fact, make some assertion about the padding and thus you can know if the message has been correctly decrypted or you're doing something wrong (i.e someone has tampered with the message or you're using the wrong key)

这为您提供了消息的随机化,并提供了一种测试消息是否垃圾的方法。由于填充方案是可逆的,当您解密消息而您无法说明消息本身的完整性时,实际上您可以对填充进行一些断言,因此您可以知道消息是否已被正确解密或者您做错了什么(即有人篡改了消息或您使用了错误的密钥)

回答by David Wengier

Yes, this is to be expected, or at least, its exactly what happens when our crypto routines get non-decryptable data

是的,这是意料之中的,或者至少,当我们的加密例程获得不可解密的数据时会发生什么

回答by R D

There may be some unread bytes in the CryptoStream. Closing before reading the stream completely was causing the error in my program.

CryptoStream 中可能有一些未读字节。在完全读取流之前关闭导致我的程序出错。

回答by jbtule

If you want your usage to be correct, you should add authenticationto your ciphertext so that you can verify that it is the correct pasword or that the ciphertext hasn't been modified. The padding you are using ISO10126will only throw an exception if the last byte doesn't decrypt as one of 16 valid values for padding (0x01-0x10). So you have a 1/16 chance of it NOT throwing the exception with the wrong password, where if you authenticate it you have a deterministic way to tell if your decryption is valid.

如果你希望你的使用是正确的,你应该为你的密文添加身份验证,以便你可以验证它是正确的密码还是密文没有被修改。如果最后一个字节未解密为 16 个有效填充值 (0x01-0x10) 之一,则您使用ISO10126的填充只会引发异常。因此,您有 1/16 的机会不会使用错误的密码抛出异常,如果您对其进行身份验证,您就有一种确定性的方法来判断您的解密是否有效。

Using crypto api's while seemingly easy, actually is rather is easy to make mistakes. For example you use a fixed salt for for you key and iv derivation, that means every ciphertext encrypted with the same password will reuse it's IV with that key, that breaks semantic security with CBC mode, the IV needs to be both unpredictable and unique for a given key.

使用crypto api看似简单,其实很容易出错。例如,您为密钥和 iv 派生使用固定盐,这意味着使用相同密码加密的每个密文都将使用该密钥重用它的 IV,这破坏了 CBC 模式的语义安全性,IV 需要既不可预测又独一无二给定的键。

For that reason of easy to make mistakes, I have a code snippet, that I try to keep reviewed and up to date (comments, issues welcome):

出于容易犯错误的原因,我有一个代码片段,我试图保持审查和更新(欢迎评论,问题):

Modern Examples of Symmetric Authenticated Encryption of a string C#.

字符串 C# 的对称认证加密的现代示例。

If you use it's AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)when the wrong password is used, nullis returned, if the ciphertext or iv has been modified post encryption nullis returned, you will never get junk data back, or a padding exception.

如果您使用它AESThenHMAC.AesSimpleDecryptWithPassword(ciphertext, password)时使用了错误的密码,null则返回,如果密文或 iv 被修改后加密null返回,您将永远不会得到垃圾数据,或填充异常。

回答by Yaniv

I experienced a similar "Padding is invalid and cannot be removed." exception, but in my case the key IV and padding were correct.

我遇到过类似的“Padding 无效,无法删除”。例外,但在我的情况下,关键 IV 和填充是正确的。

It turned out that flushing the crypto stream is all that was missing.

事实证明,缺少的只是刷新加密流。

Like this:

像这样:

            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();

回答by Mina Samy

I had a similar problem, the issue in decrypt method was initializing an empty memory stream. when it worked when I initialized it with the cipher text byte array like this:

我有一个类似的问题,decrypt 方法的问题是初始化一个空的内存流。当我用这样的密文字节数组初始化它时它工作时:

MemoryStream ms = new MemoryStream(cipherText)

回答by RoopzD

The answer updated by the user "atconway" worked for me.

用户“atconway”更新的答案对我有用。

The problem was not with the padding but the key which was different during encryption and decryption. The key and iv should be same during encypting and decrypting the same value.

问题不在于填充,而在于加密和解密期间不同的密钥。在加密和解密相同的值时,key 和 iv 应该相同。

回答by Denis Tikhomirov

Another reason of the exception might be a race condition between several threads using decryption logic - native implementations of ICryptoTransform are not thread-safe(e.g. SymmetricAlgorithm), so it should be put to exclusive section, e.g. using lock. Please refer here for more details: http://www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/

异常的另一个原因可能是使用解密逻辑的多个线程之间的竞争条件 - ICryptoTransform 的本机实现不是线程安全的(例如 SymmetricAlgorithm),因此应该将其放入独占部分,例如使用lock。请参阅此处了解更多详情:http: //www.make-awesome.com/2011/07/system-security-cryptography-and-thread-safety/

回答by Marc L.

If you've ruled out key-mismatch, then besides FlushFinalBlock()(see Yaniv's answer), calling Close()on the CryptoStreamwill also suffice.

如果您已经排除了键不匹配,那么除此之外FlushFinalBlock()(参见 Yaniv 的回答),调用Close()theCryptoStream也足够了。

If you are cleaning up resources strictly with usingblocks, be sure to nest the block for the CryptoStreamitself:

如果您严格使用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();
}

I've been bitten by this (or similar):

我被这个(或类似的)咬了:

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!