2

我想计算一个大小超过 1M 的文件的 SHA256 值。为了使用 mbedtls 库获取此哈希值,我需要将整个文件复制到内存中。但是我的内存大小只有100K。所以我想知道是否有一些方法可以分段计算文件哈希值。

4

1 回答 1

6

为了使用 mbedtls 库获取此哈希值,我需要将整个文件复制到内存中。

这是不准确的。mbedtls 库支持散列值的增量计算。

要使用 mbedtls 计算 SHA-256 哈希,您必须采取以下步骤(参考):

  • 创建 mbedtls_sha256_context结构的实例。
  • mbedtls_sha256_init用然后初始化上下文mbedtls_sha256_starts_ret
  • 将数据输入到散列函数中mbedtls_sha256_update_ret
  • 用 计算最终的哈希和mbedtls_sha256_finish_ret
  • 释放上下文mbedtls_sha256_free

请注意,这并不意味着该结构在被调用mbedtls_sha256_context之前会保存整个数据。mbedtls_sha256_finish_ret相反,mbedtls_sha256_context只保存哈希计算的中间结果。当使用 向散列函数提供额外数据时mbedtls_sha256_update_ret,计算的状态会更新,新的中间结果存储在 mbedtls_sha256_context.

mbedtls_sha256_context由 确定的 a 的总大小sizeof( mbedtls_sha256_context)在我的系统上为 108 个字节。我们还可以从 mbedtls 源代码(参考)中看到这一点:

typedef struct mbedtls_sha256_context
{
    uint32_t total[2];          /*!< The number of Bytes processed.  */
    uint32_t state[8];          /*!< The intermediate digest state.  */
    unsigned char buffer[64];   /*!< The data block being processed. */
    int is224;                  /*!< Determines which function to use:
                                     0: Use SHA-256, or 1: Use SHA-224. */
}
mbedtls_sha256_context;

我们可以看到该结构包含一个大小计数器,2*32 bit = 8 byte用于跟踪到目前为止处理的字节总数。8*32 bit = 32 byte用于跟踪哈希计算的中间结果。64 byte用于跟踪当前正在处理的数据块。如您所见,这是一个固定大小的缓冲区,不会随着被散列的数据量而增长。最后一个 int 用于区分 SHA-224 和 SHA-256。在我的系统sizeof(int) == 4上。所以总的来说,我们得到8+32+64+4 = 108 byte.

考虑以下示例程序,它逐步将文件读取到大小为 4096 的缓冲区中,并在每一步中将缓冲区馈送到散列函数中:

#include <mbedtls/sha256.h>

#include <stdio.h>
#include <stdlib.h>

#define BUFFER_SIZE 4096
#define HASH_SIZE 32

int main(void) {
  int ret;

  // Initialize hash
  mbedtls_sha256_context ctx;
  mbedtls_sha256_init(&ctx);
  mbedtls_sha256_starts_ret(&ctx, /*is224=*/0);

  // Open file
  FILE *fp = fopen("large_file", "r");
  if (fp == NULL) {
    ret = EXIT_FAILURE;
    goto exit;
  }

  // Read file in chunks of size BUFFER_SIZE
  uint8_t buffer[BUFFER_SIZE];
  size_t read;
  while ((read = fread(buffer, 1, BUFFER_SIZE, fp)) > 0) {
    mbedtls_sha256_update_ret(&ctx, buffer, read);
  }

  // Calculate final hash sum
  uint8_t hash[HASH_SIZE];
  mbedtls_sha256_finish_ret(&ctx, hash);

  // Simple debug printing. Use MBEDTLS_SSL_DEBUG_BUF in a real program.
  for (size_t i = 0; i < HASH_SIZE; i++) {
    printf("%02x", hash[i]);
  }
  printf("\n");

  // Cleanup
  fclose(fp);
  ret = EXIT_SUCCESS;

exit:
  mbedtls_sha256_free(&ctx);
  return ret;
}

在大型示例文件上运行程序时,可以观察到以下行为:

$ dd if=/dev/random of=large_file bs=1024 count=1000000
1000000+0 records in
1000000+0 records out
1024000000 bytes (1.0 GB, 977 MiB) copied, 5.78353 s, 177 MB/s
$ sha256sum large_file 
ae2d3b46eec018e006533da47a80e933a741a8b1320cfce7392a5472faae0216  large_file
$ gcc -O3 -static test.c /usr/lib/libmbedcrypto.a
$ ./a.out 
ae2d3b46eec018e006533da47a80e933a741a8b1320cfce7392a5472faae0216

我们可以看到程序计算了正确的 SHA-256 哈希值。我们还可以检查程序使用的内存:

$ command time -v ./a.out
...
Maximum resident set size (kbytes): 824
...

我们可以看到程序最多消耗了 824 KB 的内存。因此,我们计算了内存小于 1MB 的 1 GB 文件的哈希值。这表明我们不必一次将整个文件加载到内存中以使用 mbedtls 计算其哈希值。

请记住,此测量是在 64 位台式计算机上完成的,而不是嵌入式平台。-O3此外,除了静态链接(后者大约将程序的内存使用量减半)之外,没有进行进一步的优化。我希望内存占用在具有较小地址大小和执行进一步优化的工具链的嵌入式设备上更小。

于 2020-09-01T16:33:32.600 回答