Возможно ли скопировать .NET HashAlgorithm (для повторных инкрементных хэш-результатов)?

У меня есть следующий прецедент:

  • Чтение n байтов из файла
  • Вычислить (MD5) хэш для этих n байтов
  • Считать следующие m байтов из файла
  • Вычислить (MD5) хэш для файла до n + m байтов

Постепенное хэширование файла не является проблемой, просто вызовите TransformBlock и TransformFinalBlock.

Проблема в том, что мне нужно несколько хэшей данных, которые разделяют начальные байты, но после того, как я вызвал TransformFinalBlock для чтения Hash из первых байтов n, я не могу продолжать хеш с тем же объектом и нужен новый.

В поисках проблемы я увидел, что Python, а также OpenSSL имеют возможность скопировать хеширующий объект именно для этой цели:

hash.copy()

Вернуть копию ( "клонировать" ) хэш-объекта. Этот можно использовать для эффективного вычисления дайджестов строк, которые имеют общую начальную подстроку.

 

EVP_MD_CTX_copy_ex() может использоваться для копирования состояния дайджеста сообщения из в выход. Это полезно, если нужно хэшировать большие объемы данных которые отличаются только в последних байтах. выход должен быть инициализирован перед вызовом этой функции.

Как бы то ни было, я не могу найти ничего, что могло бы иметь значение с С# HashAlgorithm, что позволило бы мне эффективно Clone() == скопируйте такой объект перед вызовом его метода TransformFinalBlock - и затем продолжайте хешировать остальную часть данных с помощью клона.

Я нашел ссылку С# для MD5, которая может быть тривиально адаптирована для поддержки клонирования (*), но настоятельно предпочла бы использовать то, что вместо того, чтобы вводить такую ​​вещь в кодовую базу.

(*) Действительно, насколько я понимаю, любой алгоритм Хеширования (в отличие от шифрования/дешифрования), который я пытался проверить, тривиально можно скопировать, потому что все состояние, которое имеет такой алгоритм, является формой дайджеста.

Итак, я что-то пропустил или стандартный интерфейс С#/.NET на самом деле не предлагает способ скопировать хеш-объект?


Другая точка данных:

Microsoft собственный API-интерфейс для криптографические службы имеет функцию CryptDuplicateHash, в документах которого указано:

Функция CryptDuplicateHash может использоваться для создания отдельных хэшей двух разных содержимого, которые начинаются с того же содержимого.

Работает с Windows XP.: - |


Примечание. MD5: Вариант использования не является криптографически чувствительным. Просто надежный контроль файлов.

Ответ 1

SIGH

Фондовая библиотека .NET не позволяет этого. Грустный. В любом случае, есть несколько альтернатив:

  • MD5Managed чистый .NET ( "стандартная" лицензия MD5 RSA)
  • ClonableHash, который переносит API MS Crypto через PInvoke (может потребоваться некоторое извлечение работы из пространства имен Org.Mentalis, но лицензия разрешительна)

Также возможно, например, обернуть С++-реализацию в обертке С++/CLI - предварительные тесты показали, что это похоже быстрее, чем обычная библиотека .NET, но не верьте мне на слово.


Так как я сам написал/адаптировал решение на основе С++: https://github.com/bilbothebaggins/md5cpp

Он не вошел в производство, потому что требования изменились, но это было отличное упражнение, и мне нравится думать, что он работает очень хорошо. (Кроме того, что это не чистая реализация С#.)

Ответ 2

Я понимаю, что это не совсем то, о чем вы просите, но если это соответствует проблеме, которую вы пытаетесь решить, это альтернативный подход, который даст вам те же гарантии и аналогичные характеристики производительности потоковой передачи. Я использовал это в прошлом для протокола передачи файлов от сервера к серверу, где отправитель/получатель не всегда были доступны/надежны. Конечно, я контролировал код по обеим сторонам провода, и я понимаю, что вы не можете. В этом случае, пожалуйста, игнорируйте; -)

Мой подход состоял в том, чтобы установить 1 HashAlgorithm, который касался всего файла, а другой - для хэширования блоков фиксированного размера файла, а не для хеширования (избегает вашей проблемы), но автономных хэшей. Представьте себе файл размером 1034 МБ (1 ГБ + 10 МБ), логически разделенный на 32 МБ. Отправитель загружал файл, одновременно вызывая TransformBlock как на уровне файла, так и на уровне уровня HashAlgorithm. Когда он достиг конца 32 МБ, он назывался TransformFinalBlock на блочном уровне, записал хэш для этого блока и reset/создал новый HashAlgorithm для следующего блока. Когда он достиг конца файла, он называется TransformFinalBlock на файловом и блочном уровне хэширования. Теперь отправитель имеет "план" для передачи, включающий имя файла, размер файла, хэш файла и смещение, длину и хэш каждого блока.

Он отправил план получателю, который либо выделил место для нового файла (размер файла%% размера блока говорит, что последний блок меньше 32 МБ), либо открыл существующий файл. Если файл уже был там, он запускал тот же алгоритм для вычисления хэша блоков одинакового размера. Любые несоответствия плана заставляют его запрашивать отправителя только для этих блоков (это будет учитывать еще не переданные блоки/все 0 и поврежденные блоки). Он сделал это (проверьте, попросите блоки) работать в цикле, пока нечего было просить. Затем он проверил хэш файла на уровне плана. Если хэш-код уровня файла был недействителен, но хэши уровня блока были действительны, это, вероятно, означало бы либо хеширование или плохое ОЗУ (оба очень редкие... Я использовал SHA-512). Это позволило ресиверу восстановиться из неполных блоков или поврежденных блоков с худшим сценарием, связанным с необходимостью перезагрузки 1 плохого блока, что можно было бы компенсировать путем настройки размера блока.