C#AES Rijndael-检测无效密码
我正在使用Rijndael加密程序中的一些敏感数据 当用户输入错误的密码时,大多数情况下会抛出C#AES Rijndael-检测无效密码,c#,encryption,rijndaelmanaged,rijndael,encryption-symmetric,C#,Encryption,Rijndaelmanaged,Rijndael,Encryption Symmetric,我正在使用Rijndael加密程序中的一些敏感数据 当用户输入错误的密码时,大多数情况下会抛出加密异常,并显示消息“填充无效,无法删除” 但是,CryptStream很可能不会抛出密码错误的异常,而是返回错误解密的流。换句话说,它解密为垃圾 知道如何检测/防止这种情况吗?我能想到的最简单的方法是在加密时在消息的开头放一个“幻数”,然后检查解密后它是否还在 但是如果有更简单的方法,我很想听 校验和正是为了这个目的。在加密之前获取数据的散列。加密数据并将其与散列一起放入存储。解密后,获取解密数据的哈
加密异常
,并显示消息“填充无效,无法删除”
但是,CryptStream很可能不会抛出密码错误的异常,而是返回错误解密的流。换句话说,它解密为垃圾
知道如何检测/防止这种情况吗?我能想到的最简单的方法是在加密时在消息的开头放一个“幻数”,然后检查解密后它是否还在
但是如果有更简单的方法,我很想听 校验和正是为了这个目的。在加密之前获取数据的散列。加密数据并将其与散列一起放入存储。解密后,获取解密数据的哈希值,并将其与前者进行比较。如果您使用加密级哈希(即SHA512),您的数据将是安全的。毕竟,这正是加密压缩软件所做的
为了获得最终的安全性,您可以分别加密散列和数据,然后解密和比较。如果数据和散列都被解密为损坏的数据,那么它们匹配的可能性很小。尽管我在某种程度上同意Teoman Soygul关于CRC/散列的帖子,但有一件非常重要的事情需要注意。永远不要加密散列,因为这样会更容易找到结果密钥。即使没有加密散列,你仍然给了他们一个简单的方法来测试他们是否成功地获得了正确的密码;然而,让我们假设这已经是可能的。因为我知道您加密了什么类型的数据,无论是文本还是序列化对象,或者其他什么,所以我可能可以编写代码来识别它 也就是说,我使用了以下代码的派生来加密/解密数据:
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();
}
}
}
HMAC是您所需要的。它正是为这个目的而制造的。它将密钥和消息(在本例中,它将是您的密码)结合起来,并以一种确保内容真实性和完整性的方式对它们进行散列,只要使用的散列函数是安全的。您可以将HMAC附加到加密数据,稍后可以使用它验证解密是否正确
- 我喜欢;没有HMAC,您无法真正验证解密
但是,如果你有一个非常大的明文,那么解密可能会非常昂贵。你可能会做大量的工作来发现密码是无效的。它将是很好的,能够做一个快速拒绝错误的密码,而不经过所有的工作。有一种方法可以使用。(在中标准化,可在中访问您的c#程序)
通常情况下,数据协议使用PBKDF2从密码和salt生成密钥,周期为1000个周期或某个指定的数字。然后,也可以(可选地)通过相同算法的连接来初始化向量
要实现快速密码检查,请通过PBKDF2再生成两个字节。如果不生成并使用IV,则只生成32个字节并保留最后2个字节。在加密文本附近存储或传输这对字节。在解密端,获取密码,生成密钥和(可能是一次性的)IV,然后生成额外的2个字节,并根据存储的数据检查它们。如果两个密码对不匹配,您就知道您的密码错误,并且没有进行任何解密
如果它们匹配,则不能保证密码正确。您仍然需要完整的纯文本HMAC。但是,在大多数“密码错误”的情况下,您可以节省大量的工作,也许还可以节省挂钟时间,而且不会影响整个系统的安全性
附言:你写道: 我能想到的最简单的方法是在加密时在消息的开头放一个“幻数”,然后检查解密后它是否还在 避免将明文放入加密文本中。它只暴露另一个攻击向量,使攻击者更容易消除错误的转弯。我上面提到的密码验证是另一种动物,不会暴露这种风险
Public子解密文件(ByVal输入为字符串,ByVal输出为字符串)
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
inputFile=newfilestream(输入,FileMode.Open,FileAccess.Read)
outputFile=newfilestream(输出,FileMode.OpenOrCreate,FileAccess.Write)
outputFile.SetLength(0)
Dim缓冲区(4096)作为字节
Dim字节处理为Long=0
Dim fileLength As Long=inputFile.Length
作为整数的Dim字节块
Dim rijandael作为新的Rijndael管理
Dim cryptoStream As cryptoStream=新加密流(输出文件,rijandael.CreateDecryptor(encryptionKey,encryptionIV),CryptoStreamMode.Write)
而字节处理<文件长度
ByteInsirrentBlock=inputFile.Read(缓冲区,0,4096)
cryptoStream.Write(缓冲区,0,字节块)
bytesProcessed=bytesProcessed+CLng(bytesInCurrentBlock)
结束时
尝试
cryptoStream.Close()'如果使用了错误的密码,则会引发错误
inputFile.Close()
outputFile.Close()
文件删除(输入)
成功+=1
特例
失败+=1
inputFile.Close()
outputFile.Close()
outputFile=无
文件删除(输出)
结束尝试
我用这些代码来解密任何文件。在
cryptostream.close()上检测到错误的密码。当使用错误的密钥解密文件时,将此行作为错误捕获。发生错误时,只需关闭输出流并释放它(将outputFile
设置为Nothing
),然后删除输出文件。这对我有用。检查您使用的密码是否正确
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