C#AES Rijndael-检测无效密码

本文关键字:密码 无效 检测 Rijndael- C#AES | 更新日期: 2023-09-27 17:53:39

我正在使用Rijndael加密程序中的一些敏感数据。

当用户输入错误的密码时,大多数时候会抛出一个CryptographicException,并显示消息"Padding is invalid and cannot be remove"。

然而,CryptStream不会抛出密码错误的异常,而是返回一个解密错误的流。换句话说,它解密为垃圾。

知道如何检测/防止这种情况吗?我能想到的最简单的方法是在加密时在消息的开头放一个"幻数",并在解密后检查它是否还在。

但如果有更简单的方法,我很乐意听!

C#AES Rijndael-检测无效密码

HMAC就是您所需要的。它正是为了这个目的而制作的。它将密钥和消息(在这种情况下,将是您的密码(结合在一起,并对它们进行哈希处理,以确保内容的真实性和完整性,只要使用的哈希函数是安全的。您可以将HMAC附加到加密数据,稍后可以使用它来验证解密是否正确。

  • HMAC,维基百科
  • System.Security.Cryptography.HMAC

校验和正是用于此目的。加密前获取数据的哈希。对数据进行加密,并将其与哈希一起放入存储器。解密后,得到解密数据的哈希值,并将其与前者进行比较。如果您使用加密级别的哈希(即SHA512(,您的数据将是安全的。毕竟,这正是加密压缩软件所做的。

为了获得最终的安全性,您可以分别加密哈希和数据,然后解密和比较。如果数据和哈希都解密为损坏的数据,那么它们匹配的可能性非常小。

要检查您使用的密码是否正确,您可以使用以下代码

            Dim decryptedByteCount As Integer
            Try
                decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
            Catch exp As System.Exception
                Return "Password Not Correct"
            End Try

本质上,检查在解密过程中是否生成了错误消息。

我报告以下的所有解码代码

    Public Shared Function Decrypt(ByVal cipherText As String) As String
    If System.Web.HttpContext.Current.Session("Crypto") = "" Then
        HttpContext.Current.Response.Redirect("http://yoursite.com")
    Else
        If cipherText <> "" Then
            'Setto la password per criptare il testo
            Dim passPhrase As String = System.Web.HttpContext.Current.Session("Crypto")
            'Ottieni lo stream completo di byte che rappresentano: [32 byte di Salt] + [32 byte di IV] + [n byte di testo cifrato]
            Dim cipherTextBytesWithSaltAndIv = Convert.FromBase64String(cipherText)
            'Ottieni i Salt bytes estraendo i primi 32 byte dai byte di testo cifrato forniti
            Dim saltStringBytes = cipherTextBytesWithSaltAndIv.Take((Keysize)).ToArray
            'Ottieni i IV byte estraendo i successivi 32 byte dai byte testo cifrato forniti.
            Dim ivStringBytes = cipherTextBytesWithSaltAndIv.Skip((Keysize)).Take((Keysize)).ToArray
            'Ottieni i byte del testo cifrato effettivo rimuovendo i primi 64 byte dal testo cifrato.
            Dim cipherTextBytes = cipherTextBytesWithSaltAndIv.Skip(((Keysize) * 2)).Take((cipherTextBytesWithSaltAndIv.Length - ((Keysize) * 2))).ToArray
            Dim password = New Rfc2898DeriveBytes(passPhrase, saltStringBytes, DerivationIterations)
            Dim keyBytes = password.GetBytes((Keysize))
            Dim symmetricKey = New RijndaelManaged
            symmetricKey.BlockSize = 256
            symmetricKey.Mode = CipherMode.CBC
            symmetricKey.Padding = PaddingMode.PKCS7
            Dim decryptor = symmetricKey.CreateDecryptor(keyBytes, ivStringBytes)
            Dim memoryStream = New MemoryStream(cipherTextBytes)
            Dim cryptoStream = New CryptoStream(memoryStream, decryptor, CryptoStreamMode.Read)
            Dim plainTextBytes = New Byte((cipherTextBytes.Length) - 1) {}
            Dim decryptedByteCount As Integer
            Try
                decryptedByteCount = cryptoStream.Read(plainTextBytes, 0, plainTextBytes.Length)
            Catch exp As System.Exception
                Return "La password di Cryptazione non è corretta"
            End Try
            memoryStream.Close()
            cryptoStream.Close()
            Return Encoding.UTF8.GetString(plainTextBytes, 0, decryptedByteCount)
        Else
            Decrypt = ""
        End If
    End If
End Function

虽然我在一定程度上同意Teoman Soygul关于CRC/Hash的帖子,但有一件事需要注意。永远不要加密哈希,因为这样可以更容易地找到生成的密钥。即使没有加密哈希,你仍然为他们提供了一种简单的方法来测试他们是否成功获得了正确的密码;然而,让我们假设这已经是可能的。既然我知道你加密了什么样的数据,无论是文本,还是序列化对象,或者其他什么,我很可能可以写代码来识别它

也就是说,我使用了以下代码的派生来加密/解密数据:

    static void Main()
    {
        byte[] test = Encrypt(Encoding.UTF8.GetBytes("Hello World!"), "My Product Name and/or whatever constant", "password");
        Console.WriteLine(Convert.ToBase64String(test));
        string plain = Encoding.UTF8.GetString(Decrypt(test, "My Product Name and/or whatever constant", "passwords"));
        Console.WriteLine(plain);
    }
    public static byte[] Encrypt(byte[] data, string iv, string password)
    {
        using (RijndaelManaged m = new RijndaelManaged())
        using (SHA256Managed h = new SHA256Managed())
        {
            m.KeySize = 256;
            m.BlockSize = 256;
            byte[] hash = h.ComputeHash(data);
            byte[] salt = new byte[32];
            new RNGCryptoServiceProvider().GetBytes(salt);
            m.IV = h.ComputeHash(Encoding.UTF8.GetBytes(iv));
            m.Key = new Rfc2898DeriveBytes(password, salt) { IterationCount = 10000 }.GetBytes(32);
            using (MemoryStream ms = new MemoryStream())
            {
                ms.Write(hash, 0, hash.Length);
                ms.Write(salt, 0, salt.Length);
                using (CryptoStream cs = new CryptoStream(ms, m.CreateEncryptor(), CryptoStreamMode.Write))
                {
                    cs.Write(data, 0, data.Length);
                    cs.FlushFinalBlock();
                    return ms.ToArray();
                }
            }
        }
    }
    public static byte[] Decrypt(byte[] data, string iv, string password)
    {
        using (MemoryStream ms = new MemoryStream(data, false))
        using (RijndaelManaged m = new RijndaelManaged())
        using (SHA256Managed h = new SHA256Managed())
        {
            try
            {
                m.KeySize = 256;
                m.BlockSize = 256;
                byte[] hash = new byte[32];
                ms.Read(hash, 0, 32);
                byte[] salt = new byte[32];
                ms.Read(salt, 0, 32);
                m.IV = h.ComputeHash(Encoding.UTF8.GetBytes(iv));
                m.Key = new Rfc2898DeriveBytes(password, salt) { IterationCount = 10000 }.GetBytes(32);
                using (MemoryStream result = new MemoryStream())
                {
                    using (CryptoStream cs = new CryptoStream(ms, m.CreateDecryptor(), CryptoStreamMode.Read))
                    {
                        byte[] buffer = new byte[1024];
                        int len;
                        while ((len = cs.Read(buffer, 0, buffer.Length)) > 0)
                            result.Write(buffer, 0, len);
                    }
                    byte[] final = result.ToArray();
                    if (Convert.ToBase64String(hash) != Convert.ToBase64String(h.ComputeHash(final)))
                        throw new UnauthorizedAccessException();
                    return final;
                }
            }
            catch
            {
                //never leak the exception type...
                throw new UnauthorizedAccessException();
            }
        }
    }

我喜欢Can Gencer的回答;如果没有HMAC,您就无法真正验证解密。

但是,如果你有一个非常非常大的明文,那么解密可能会非常昂贵。你可能会做大量的工作来发现密码无效。如果能够快速拒绝错误的密码,而不需要经历所有这些工作,那就太好了。有一种方法可以使用PKCS#5 PBKDF2。(在RFC2898中标准化,您的c#程序可以访问Rfc2898DeriveBytes(。

通常情况下,数据协议要求使用PBKDF2从密码和salt生成密钥,周期为1000个周期或某个指定数量。然后也可以(可选地(通过相同算法的连接来初始化向量。

要实现快速密码检查,请通过PBKDF2再生成两个字节。如果您不生成并使用IV,那么只生成32个字节并保留最后2个字节。存储或传输与您的密文相邻的这对字节。在解密方面,获取密码,生成密钥和(可能是一次性的(IV,然后生成2个额外的字节,并根据存储的数据进行检查。如果配对不匹配,就知道您的密码错误,没有任何解密。

如果它们匹配,则不能保证密码是正确的。您仍然需要完整明文的HMAC。但是,在大多数"错误密码"的情况下,你可以为自己节省大量的工作,甚至可能节省墙上的时钟时间,而且不会损害整个系统的安全性。


ps:您写道:

我能想到的最简单的方法是在加密时在消息的开头放一个"幻数",并在解密后检查它是否还在。

避免将明文放入密文中。它只暴露另一个攻击向量,使攻击者更容易消除错误的转弯。我上面提到的密码验证是另一种动物,不会暴露这种风险。

 Public Sub decryptFile(ByVal input As String, ByVal output As String)
        inputFile = New FileStream(input, FileMode.Open, FileAccess.Read)
        outputFile = New FileStream(output, FileMode.OpenOrCreate, FileAccess.Write)
        outputFile.SetLength(0)
        Dim buffer(4096) As Byte
        Dim bytesProcessed As Long = 0
        Dim fileLength As Long = inputFile.Length
        Dim bytesInCurrentBlock As Integer
        Dim rijandael As New RijndaelManaged
        Dim cryptoStream As CryptoStream = New CryptoStream(outputFile, rijandael.CreateDecryptor(encryptionKey, encryptionIV), CryptoStreamMode.Write)
        While bytesProcessed < fileLength
            bytesInCurrentBlock = inputFile.Read(buffer, 0, 4096)
            cryptoStream.Write(buffer, 0, bytesInCurrentBlock)
            bytesProcessed = bytesProcessed + CLng(bytesInCurrentBlock)
        End While
        Try
            cryptoStream.Close() 'this will raise error if wrong password used
            inputFile.Close()
            outputFile.Close()
            File.Delete(input)
            success += 1
        Catch ex As Exception
            fail += 1
            inputFile.Close()
            outputFile.Close()
            outputFile = Nothing
            File.Delete(output)
        End Try

我用那个代码来解密任何文件。在cryptostream.close()上检测到错误的密码。当使用错误的密钥解密文件时,将此行捕获为错误。当出现错误时,只需关闭输出流并释放它(将outputFile设置为Nothing(,然后删除输出文件。它对我有效。