有没有办法通过研究源代码找出哪个哈希标准?

逆向工程 C 静态分析 加密 密码学 散列函数
2021-06-13 08:39:08

我们有一个嵌入式产品,自 5 年前以来,我们一直在进行多次硬件迭代。我们拥有所有源代码,其中大部分都有很好的文档记录。由于该产品正在积极销售并需要升级,因此我的任务是设计下一代改进版本。

虽然我们拥有所有源代码,并且其中大部分都有详细记录,但安全部分不是,尤其是其最慢的部分,不是一个 72 轮的 128 位哈希函数。我可以在我们的调试器中逐步调试它,执行它,并且代码运行良好,但它很慢。如果我知道它是哪种密码学标准,我可以尝试升级它以在硬件中执行,这将大大加快它的速度。(原来写这个功能的顾问不在我们公司,我找不到她)。

有什么合乎逻辑的方法可以找出我们正在执行哪种加密?我不需要帮助来完成我的工作,如果有任何如何查看代码的策略(“未优化的混乱”),我正在寻求建议;学习它的原理(像这个有 72 轮和 128 位散列);然后寻找候选标准做同样的事情。

我认为在找到记录的标准后,我应该能够通过使用我们的哈希和标准哈希来对各种数字进行哈希处理,然后简单地查看结果是否匹配?

我在网上查找有关使用 72 轮的已知函数的信息,例如Skein 算法,但找不到任何类似的信息。

是否有已知标准的任何已知散列值,例如散列 0 的结果?此散列将 0 转换为这 16 个十六进制字节:

ae c5 40 44 df 2d 91 1c 87 ab 1a ff 59 09 aa b7

函数开始和结束在下面,正如你们中的一些人所要求的,完整的函数在: full_function_code

看起来以前的程序员让旧的 C 编译器对其进行转换,然后以“汇编”样式使用它。也许是为了避免编译器优化,只是我的猜测。

有 72 轮,每轮都以调用 function 结束TricoreDextr()这是英飞凌 TriCore DEXTR 指令的 C 等效项。将 dextr 视为将 64 位数字分成两部分(在选定的位位置)并交换它们。

任何概念解释都是一个很大的帮助,谢谢!

/*
 * @param in_arr    16 byte long array, input data (128 bits)
 * @param out_arr   16 byte long array, output data (128 bits)
 */
void Hash16bytes(unsigned char *in_arr, unsigned char *out_arr) 
{
  long d0, d1, d2, d3, d4, d5, d6, d7, d8, d9, d10, d11, d12, d13, d15;
  long IN[16];

  d4 = 0xB12E8FB5;

  int i;
  for (i = 0; i < 16; i++)
  {
      IN[i] = (long) (*(in_arr + i));
  }

  d11 = (IN[3] << 24) | (IN[2] << 16) | (IN[1] << 8) | IN[0];
  d10 = (IN[7] << 24) | (IN[6] << 16) | (IN[5] << 8) | IN[4];
  d12 = (IN[0xB] << 24) | (IN[0xA] << 16) | (IN[9] << 8) | IN[8];
  d13 = (IN[0xF] << 24) | (IN[0xE] << 16) | (IN[0xD] << 8) | IN[0xC];

  /* block like this below repeats 72 times... */
  d3 = 0x7025BD47;
  d8 = 0xCCEE4EFE;
  d8 += d13; 
  d8 += d3;
  d8 = TricoreDextr(d8, 6);

  /* 
     etc., TricoreDextr is called total of 72 times 
   */

  d0 += d15;
  d0 += 0x5C4E0000;
  d0 -= 0x2EDC;
  d0 = TricoreDextr(d0, 5);

  *(out_arr + 3) = (unsigned char) ((d5 >> 24) & 0xFF);
  *(out_arr + 2) = (unsigned char) ((d5 >> 16) & 0xFF);
  *(out_arr + 1) = (unsigned char) ((d5 >> 8) & 0xFF);
  *(out_arr + 0) = (unsigned char) ((d5) & 0xFF);

  *(out_arr + 7) = (unsigned char) ((d6 >> 24) & 0xFF);
  *(out_arr + 6) = (unsigned char) ((d6 >> 16) & 0xFF);
  *(out_arr + 5) = (unsigned char) ((d6 >> 8) & 0xFF);
  *(out_arr + 4) = (unsigned char) ((d6) & 0xFF);

  d8 += d0;
  d8 += 0x10320000;
  d8 += 0x5476;

  *(out_arr + 0xB) = (unsigned char) ((d8 >> 24) & 0xFF);
  *(out_arr + 0xA) = (unsigned char) ((d8 >> 16) & 0xFF);
  *(out_arr + 9) = (unsigned char) ((d8 >> 8) & 0xFF);
  *(out_arr + 8) = (unsigned char) ((d8) & 0xFF);

  *(out_arr + 0xF) = (unsigned char) ((d7 >> 24) & 0xFF);
  *(out_arr + 0xE) = (unsigned char) ((d7 >> 16) & 0xFF);
  *(out_arr + 0xD) = (unsigned char) ((d7 >> 8) & 0xFF);
  *(out_arr + 0xC) = (unsigned char) ((d7) & 0xFF);
}
1个回答

这很可能是一个拙劣的 RIPEMD128 或非常相似的东西,正如 otus 所评论的那样。

你想知道如何处理这样的任务,所以我会解释我做了什么。

通常,在尝试识别与加密相关的代码时,您依赖于发现常量。在这种情况下,常量似乎是故意混淆的,所以你需要玩弄数字,把它扔到谷歌上,希望能得到一些结果。

假设这可能是 RIPE 家族(通过不断寻找),我试图找到一些代码。由于输入/输出是 128 位,我从这里下载了 RIPE128 的代码:

https://homes.esat.kuleuven.be/~bosselae/ripemd160/ps/AB-9601/rmd128.c

我接着看了看。我注意到操作compress()似乎使用左旋转(检查宏定义的头文件),并且奇怪的TricoreDextr()是实际上类似的东西。

然后我搜索了所有TricoreDextr()行的完整代码,并将其与中的操作进行了比较,发现了compress()一些相似之处:

GG(aa, bb, cc, dd, X[ 7],  7);
GG(dd, aa, bb, cc, X[ 4],  6);
GG(cc, dd, aa, bb, X[13],  8);
GG(bb, cc, dd, aa, X[ 1], 13);

相比于:

d8 = TricoreDextr(d8, 7);
d6 = TricoreDextr(d6, 6);
d5 = TricoreDextr(d5, 8);
d7 = TricoreDextr(d7, 0xD);

例如。这些TricoreDextr调用并不完全匹配成熟的 128 实现。我还注意到,在您的代码中只出现了这些操作中使用的一些幻数。

例如

#define GG(a, b, c, d, x, s)        {\
  (a) += G((b), (c), (d)) + (x) + 0x5a827999UL;\
  (a) = ROL((a), (s));\
}

确实出现:

d5 += 0x5A820000;
d5 += 0x7999;

但其他人没有,所以如果是 RIPE128应该存在的一些操作肯定会丢失。

阅读维基百科告诉我,在 RIPE160(和 RIPE128)之前,有一个 128 位 RIPEMD 哈希函数的原始设计,但我无法找到当时的任何源代码。

对原始 128 位 RIPEMD 哈希函数的唯一引用指向

https://www.springer.com/in/book/9783540606406

我想其中应该包含原始算法,但这是一本要花钱的书。

知道所有这些,我最好的猜测是,这是故意混淆的原始 RIPEMD 128 位哈希函数,因为现代实现中的一些但并非所有操作都在那里,暗示它们可能是第二个版本的附加,这将解释为什么他们失踪了。或者有人使用了现代 RIPEMD128 实现并对其做了奇怪的事情。