Можно рассчитать MD5 (или другой) хеш с буферизованным чтением?
Мне нужно рассчитать контрольные суммы довольно больших файлов (гигабайт). Это может быть выполнено с использованием следующего метода:
private byte[] calcHash(string file)
{
System.Security.Cryptography.HashAlgorithm ha = System.Security.Cryptography.MD5.Create();
FileStream fs = new FileStream(file, FileMode.Open, FileAccess.Read);
byte[] hash = ha.ComputeHash(fs);
fs.Close();
return hash;
}
Однако файлы обычно пишутся заранее в буферном режиме (скажем, записывая 32 Мб за раз). Я настолько убежден, что видел переопределение хэш-функции, которая позволяла мне вычислять хеш MD5 (или другого) одновременно с записью, т.е. Вычислять хэш одного буфера, а затем подавать этот хэш в следующую итерацию.
Что-то вроде этого: (псевдокод-иш)
byte [] hash = new byte [] { 0,0,0,0,0,0,0,0 };
while(!eof)
{
buffer = readFromSourceFile();
writefile(buffer);
hash = calchash(buffer, hash);
}
хеш теперь аналогичен тому, что было бы достигнуто за счет запуска функции calcHash во всем файле.
Теперь, я не могу найти каких-либо переопределений, подобных этому в Framework 3.5 Framework, мне снится? Разве это никогда не существовало, или я просто отвратителен при поиске? Причина одновременного вычисления и записи и контрольной суммы заключается в том, что это имеет смысл из-за больших файлов.
Ответы
Ответ 1
Вы используете методы TransformBlock
и TransformFinalBlock
для обработки данных в кусках.
// Init
MD5 md5 = MD5.Create();
int offset = 0;
// For each block:
offset += md5.TransformBlock(block, 0, block.Length, block, 0);
// For last block:
md5.TransformFinalBlock(block, 0, block.Length);
// Get the has code
byte[] hash = md5.Hash;
Примечание. Он работает (по крайней мере, с поставщиком MD5), чтобы отправить все блоки в TransformBlock
, а затем отправить пустой блок на TransformFinalBlock
, чтобы завершить процесс.
Ответ 2
Мне нравится ответ выше, но для полноты и более общего решения обратитесь к классу CryptoStream
. Если вы уже обрабатываете потоки, легко переносить поток в CryptoStream
, передавая HashAlgorithm
в качестве параметра ICryptoTransform
.
var file = new FileStream("foo.txt", FileMode.Open, FileAccess.Write);
var md5 = MD5.Create();
var cs = new CryptoStream(file, md5, CryptoStreamMode.Write);
while (notDoneYet)
{
buffer = Get32MB();
cs.Write(buffer, 0, buffer.Length);
}
System.Console.WriteLine(BitConverter.ToString(md5.Hash));
Возможно, вам придется закрыть поток до получения хэша (так что HashAlgorithm
знает, что он сделал).
Ответ 3
Кажется, вы можете использовать TransformBlock
/TransformFinalBlock
, как показано в этом примере: Отображение обновлений прогресса при хэшировании больших файлов
Ответ 4
Предполагается, что алгоритмы хеширования будут обрабатывать эту ситуацию и, как правило, реализованы с тремя функциями:
hash_init()
- Вызывается для выделения ресурсов и начала хеша.
hash_update()
- Вызывается с новыми данными по мере поступления.
hash_final()
- Завершить расчет и бесплатные ресурсы.
Посмотрите http://www.openssl.org/docs/crypto/md5.html или http://www.openssl.org/docs/crypto/sha.html для хороших, стандартных примеров в C; Я уверен, что для вашей платформы есть похожие библиотеки.
Ответ 5
Мне просто нужно было сделать что-то подобное, но хотелось прочитать файл асинхронно. Он использует TransformBlock и TransformFinalBlock и дает мне ответы, соответствующие Azure, поэтому я считаю, что это правильно!
private static async Task<string> CalculateMD5Async(string fullFileName)
{
var block = ArrayPool<byte>.Shared.Rent(8192);
try
{
using (var md5 = MD5.Create())
{
using (var stream = new FileStream(fullFileName, FileMode.Open, FileAccess.Read, FileShare.Read, 8192, true))
{
int length;
while ((length = await stream.ReadAsync(block, 0, block.Length).ConfigureAwait(false)) > 0)
{
md5.TransformBlock(block, 0, length, null, 0);
}
md5.TransformFinalBlock(block, 0, 0);
}
var hash = md5.Hash;
return Convert.ToBase64String(hash);
}
}
finally
{
ArrayPool<byte>.Shared.Return(block);
}
}