40

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

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

代码似乎工作正常,除了在使用不正确的密钥解密数据时,我在解密字符串的 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

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

4

9 回答 9

27

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

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

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

  1. 给定模数的大小,您用 0 填充 k1 位,用随机数填充 k0 位。
  2. 然后通过对消息应用一些转换,您将获得加密并发送的填充消息。

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

于 2008-08-25T15:46:00.960 回答
17

我遇到了类似的“填充无效,无法删除”。例外,但在我的情况下,密钥 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();
于 2013-11-07T11:44:56.370 回答
6

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

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

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

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

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

于 2013-02-20T02:01:15.980 回答
4

如果您已经排除了键不匹配,那么除此之外FlushFinalBlock()(请参阅 Yaniv 的回答),调用will 也足够 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!
于 2016-10-13T10:40:01.477 回答
3

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

于 2008-08-14T23:25:22.470 回答
3

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

于 2016-10-10T10:43:12.170 回答
1

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

于 2010-08-04T09:16:27.087 回答
0

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

MemoryStream ms = new MemoryStream(cipherText)
于 2015-02-02T12:13:46.263 回答
-2

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

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

于 2015-04-15T20:12:47.603 回答