C# aes Rijndael - Обнаружение недопустимых паролей
-
25-10-2019 - |
Вопрос
Я использую Rijndael, чтобы зашифровать некоторые конфиденциальные данные в моей программе.
Когда пользователь вводит неправильный пароль, большую часть времени CryptographicException
брошен с сообщением «Надое неверно и не может быть удалено».
Однако, с очень небольшой вероятностью, CryptStream не бросает исключение с неправильным паролем, а вместо этого дает неправильно расшифрованную поток. Другими словами, это расшифровывает мусор.
Есть идеи, как обнаружить/предотвратить это? Самым простым способом, который я могу придумать, было бы поместить «волшебный номер» в начале сообщения при шифровании, и проверить, есть ли он все еще там после расшифровки.
Но если есть более простой способ, я бы хотел услышать это!
Решение
HMAC - это то, что вам нужно. Это точно сделано для этой цели. Он объединяет ключ и сообщение (которое в данном случае будет вашим паролем) и хэшируют их таким образом, чтобы обеспечить подлинность и целостность содержания, если используемая функция хеш безопасна. Вы можете прикрепить 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();
}
}
}
Мне нравится Может ли ответ Генсера; Вы не можете действительно проверить дешифрование без HMAC.
Но если у вас очень большой открытый текст, то дешифрование может быть очень дорогим. Вы можете сделать тонну работы, чтобы узнать, что пароль был недействительным. Было бы неплохо иметь возможность сделать быстрый отказ от неправильных паролей, не проходя через всю эту работу. Есть способ использования PKCS#5 PBKDF2. Анкет (стандартизировано в RFC2898, который доступен для вашей программы C# в Rfc2898deivebytes).
Обычно протокол данных требует генерации ключа из пароля и соли с использованием PBKDF2, 1000 циклов или некоторого указанного номера. Тогда, возможно, также (необязательно) вектор инициализации посредством продолжения того же алгоритма.
Чтобы реализовать быструю проверку пароля, генерируйте Еще два байта через PBKDF2. Если вы не генерируете и используете IV, просто генерируйте 32 байта и сохраните последний 2. Храните или передайте эту пару байтов, прилегающих к вашему Cryptotext. На стороне расшифровки получите пароль, генерируйте ключ и (возможно, выброс) IV, затем сгенерируйте 2 дополнительных байта и проверьте их на сохраненные данные. Если пары вам не совпадают знать У вас неправильный пароль, без какого -либо дешифрования.
Если они соответствуют, это не гарантия того, что пароль правильный. Вам все еще нужен HMAC полного открытого текста для этого. Но вы можете сэкономить кучу работы и, возможно, время на стене, в большинстве случаев «неправильного пароля» и без ущерба для безопасности общей системы.
пса: вы написали:
Самым простым способом, который я могу придумать, было бы поместить «волшебный номер» в начале сообщения при шифровании, и проверить, есть ли он все еще там после расшифровки.
Избегайте открытого текста в Cryptotext. Он только выявляет еще один вектор атаки, облегчает злоумышленнику устранить неправильные повороты. Проверка пароля, которую я упомянул выше, - это другое животное, не подвергает этот риск.
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
), затем удалите выходной файл. Это работает для меня.