我正在使用 Rijndael 加密程序中的一些敏感数据。
当用户输入错误的密码时,大多数时候CryptographicException
会抛出 a 消息“填充无效,无法删除。”。
然而,在非常小的概率下,CryptStream 不会抛出错误密码的异常,而是返回一个错误解密的流。换句话说,它解密为垃圾。
知道如何检测/防止这种情况吗?我能想到的最简单的方法是在加密时在消息的开头放置一个“幻数”,并在解密后检查它是否仍然存在。
但如果有更简单的方法,我很想听听!
我正在使用 Rijndael 加密程序中的一些敏感数据。
当用户输入错误的密码时,大多数时候CryptographicException
会抛出 a 消息“填充无效,无法删除。”。
然而,在非常小的概率下,CryptStream 不会抛出错误密码的异常,而是返回一个错误解密的流。换句话说,它解密为垃圾。
知道如何检测/防止这种情况吗?我能想到的最简单的方法是在加密时在消息的开头放置一个“幻数”,并在解密后检查它是否仍然存在。
但如果有更简单的方法,我很想听听!
HMAC 是您所需要的。它正是为此目的而制造的。它结合了密钥和消息(在这种情况下,将是您的密码)并以确保内容的真实性和完整性的方式对它们进行散列,只要使用的散列函数是安全的。您可以将 HMAC 附加到加密数据,稍后可以使用它来验证解密是否正确。
校验和正是为此目的。在加密之前获取数据的哈希值。加密数据并将其与哈希一起存储。解密后,得到解密后数据的hash,并与前者进行比较。如果您使用加密级哈希(即 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 以 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
),然后删除输出文件。它对我有用。