实锤:写高质量代码,不然加密货币仍然可被攻击并篡改

29a371b4ca2ad3129c313eda8deea331e68d7fc5

策划|Tina编译|核子可乐 区块链前哨导语:  我们知道区块链是一个个 Block 组成,而 Block 由校验值和实际数据组成,通常 Block 的头部存放着前一个 Block 的 Hash 校验值。适合编写区块链的数据结构一般首选是梅克尔树(Merkle Tree),可通过该结构进行溯源校验。最近有人再次验证了如何对梅克尔树进行攻击,从而可制造假的中间叶子,达到篡改目的。造成这一切的原因很可能是因为程序员在实现梅克尔树时造成的。该问题在 Reddit 上也引起了不少讨论,并有人在 github 上提供了可被攻击的代码写法。

更多干货内容请关注微信公众号“区块链前哨”,(ID:blockchain-666)


区块链结构

比特币区块包含两种数据结构。上面的部分是基于哈希的区块链。每个区块的区块头部都包含一个哈希指针指向上一个区块。第二种数据结构是梅克尔树(Merkle tree),它将包含的交易用高效的方式(log(n))组织起来。

57d9b01d987487c87effe442b6809ed98210526b

梅克尔树是一种非常简单的数据结构,其允许将数据块(无论是文件等初始数据块,抑或是有意拆分的数据块)以独立及分布方式进行计算。

举个例子:如果您拥有 2000 个大小为 1 GB 的文件,并希望为数据计算 SHA256,大家通常需要将所有文件连接在一起,并获取单一数据哈希值。出于各种实际原因,这样的处理方式往往会引发问题——例如如果单一文件中的单一字节发生变化,则需要对这部分总体积高达 2 TB 的数据重新运行 SHA256 散列计算。另外,除非您拥有全部 2000 个文件,否则您将无法计算该哈希值——换言之,您只能从头开始计算新的散列值。

梅克尔树能够对单一数据块进行散列计算(在以上示例中,即各个 1 GB 文件),而后构建散列二叉树的方式得出能够代表整体数据块的单一哈希值。这意味着如果单一文件发生变化,您只需要重新对该文件进行散列计算,而后得出哈希结果即可。很明显,这是一种效率更高、速度更快的处理方式。

以下为梅克尔树在维基百科页面中的原理示意图:

1c90102ecc67f0da9e43c5dc3820ab3424fca422


如何进行攻击

我们把这个攻击说明原文进行了翻译:

下面将概述梅克尔树实现中存在的一种常见缺陷,并演示当前最受欢迎的各相关 Python 库可能因此遭遇的潜在威胁。

我们利用的是次原像攻击。所谓次原像攻击,是指攻击者提供一个哈希指纹,并借此找到任何与该值相符的数据哈希。

举例来说,如果给定 MD5 值为 5EB63BBBC01EEED093CB22BB8F5ACDC3,而您可以发现实际生成该哈希值的数据,则意味着您利用次原像攻击破解了该 MD5 值。备注:MD5 为 128 位哈希函数,而目前最强大的原像攻击能够将其强度削减为 123 位——但这样的复杂度对原像攻击而言仍然过高。事实上,导致 MD5 被弃用的重大缺陷体现为碰撞攻击——攻击者可以生成两条输入内容,二者可通过散列处理得到同样的哈希值,但不具备可控性或可预测性。

而所谓次原像攻击,则意味着为我们提供一些数据,并要求我们找到能够产生与之生成同样哈希值的第二组数据。其与普通原像攻击非常相似,但我们拥有一项额外的提示信息——即已经掌握了一组可产生目标哈希值的数据样本。次原像攻击在实施难度上甚至高于碰撞攻击,因为其中一组数据样本并不在攻击者的控制范围之内。


针对梅克尔树的次原像攻击

但在另一方面,针对梅克尔树的攻击其实非常简单。在这里,我们拥有一项基本前提,即如果将一系列输入内容传递至梅克尔树并获取 root 哈希值,则不可能有其它输入结果能够得出同样的哈希值。不过遗憾的是,由于实现方案中存在的一项小缺陷,这样的前提并不成立。

在以上示例当中,我们的输入内容为 L1、L2、L3 以及 L4。其最终在示意图上方输出了 root 哈希值。不过如大家所见,中间层当中的输入内容为上一层的串连哈希,而我们可以将这两个值直接输入克尔树以获得同样的 root 哈希值。

这里需要强调的是,此种攻击在底层散列函数不存在已知安全缺陷的前提下也同样可行。事实上,这属于梅克尔树构建层面的一种本质性问题。

下面我们将通过另一个示例进一步作出解释。


示例攻击

这里我们不再使用示例代码,而选择“python merkle tree”解释攻击方法,并通过 GitHub repo 及配合 MT 加以实现。此外,我们还编写了简单的概念验证代码以演示这项缺陷。

第一个示例为:

https://github.com/JaeDukSeo/Simple-Merkle-Tree-in-Python。

首先,我们导入梅克尔树实现方案以及用于显示输出结果的 json 库。在此之后,我编写了一条简单的函数以构造一个 MT 对象,分配一系列“transaction”(即数据块),最后利用这些数据构造梅克尔树并输出 root 节点,同时提取该树的哈希值以展示 root 节点的生成过程。

82068e89cdc2a63297a86ff82a9183921167d6b2

在这里,我们展示了被传递至这套实现方案中的三个值,其都将输出同样的哈希值:

6bb156f8fe762d4094cd507ca705b46e083fdd0f

在 transaction1 当中,我传递四个值:a、b、c、d。

在 transaction2 当中,我传递两个值,其各自为先前值的串连哈希值——例如 hash(a)+hash(b) 与 hash(c)+hash(d),这里我使用加号来代表串连。

在 transaction3 当中,我仅传递一个值:来自上一层输出结果的哈希串连。

最终输出结果(其中完整的树提取结果被注释掉了)如下所示:

0f4ae7d9d98a4ace5ebbe535823c8e30ea44f135

第二个示例为:https://github.com/Tierion/pymerkletools ,可使用“pip install merkletools”命令进行安装。以下代码采用类似的方法,且内容相当明确。

0fb6293d48c8ee162cc61b21352383693beaf94f

其将生成以下输出结果:

2811c463605ef110c66f5a1c097777f85c630b87


攻击局限性

在现实世界中(例如比特币场景下),这种攻击往往不会受到关注。这是因为其存在严重的局限性,即尽管我们可以找到能够与输入内容 X 生成相同哈希值的输入内容 Y,但我们对 Y 的值及其格式毫无控制能力。这意味着对于任何伪造梅克尔树进行数据完整性检查的应用程序而言,这些人为制作的输入内容无法以有用的方式进行解释。但这种方式仍能够用于实施拒绝服务攻击,这是由于匹配的哈希值将无法保证数据完整性,进而导致数据遭到 覆盖或替换


如何修复

幸运的是,这个问题的修复方法同样相当简单。其基本思路在于为叶节点及中间节点预先设置一个不同的字节值(例如在证书透明度实现中设置 0x00 以及 0x01),从而对这两类节点进行区分。另外,大家也可以将树深度或节点深度作为数据结构的组成部分进行记录,如此一来攻击者将无法直接传递中间值。




原文发布时间为:2018-03-14
本文作者:关注安全的
本文来源:微信公众号-区块链前哨,如需转载请联系原作者。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值