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

    // 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

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

有帮助吗?

解决方案

虽然这已经得到回答,但我认为解释一下是个好主意 为什么 这是可以预料的。

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

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

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

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

其他提示

我经历了类似的“填充无效,无法删除”。例外,但是就我而言,钥匙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 需要既不可预测又是唯一的给定的密​​钥。

由于容易犯错误,我有一个代码片段,我尝试对其进行审查和更新(欢迎评论、问题):

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

如果你使用它 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!

是的,这是可以预料的,或者至少,这正是当我们的加密例程获取不可解密数据时发生的情况

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

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

我遇到了类似的问题,解密方法中的问题是初始化空内存流。当我使用密文字节数组初始化它时,它起作用了,如下所示:

MemoryStream ms = new MemoryStream(cipherText)

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

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

许可以下: CC-BY-SA归因
不隶属于 StackOverflow
scroll top