基于哈希的代理键冲突风险

原文:https://towardsdatascience.com/collision-risk-in-hash-based-surrogate-keys-4c87b716cbcd

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/eca9da1350bea366af9245c3237d1d38.png

多维空间中低概率游戏的抽象可视化。不要被奇形怪状的骰子所迷惑!| 图像由 DALL·E 提供

数据库(尤其是在数据仓库和湖仓中)生成代理键的一种方法依赖于哈希函数从自然键计算哈希键。这种方法有很多优点,但也伴随着一个显著的风险:哈希函数不能保证输出唯一,导致哈希键冲突的可能性。

哈希冲突是指不同的输入值通过哈希函数返回相同的输出值(即相同的哈希)。这种事件发生的概率很大程度上取决于特定哈希函数生成的哈希键的长度。哈希键越长,冲突的风险越低。

哈希函数

目前使用最广泛的三个哈希函数是:

  • MD5(消息摘要算法 5)——由罗纳德·里维斯特(Ronald Rivest)于 1991 年开发,是一种广为人知的哈希函数,它生成一个 128 位(16 字节)的哈希值。最初设计用于数据完整性和认证,MD5 由于其简单性和速度而迅速流行。

  • SHA-1(安全哈希算法 1)——由国家安全局(NSA)创建,并由国家标准与技术研究院(NIST)于 1993 年作为数字签名算法(DSA)的一部分发布。它生成一个 160 位(20 字节)的哈希值,比 MD5 提供了更好的碰撞抵抗性。

  • SHA-256(256 位安全哈希算法)——也由国家安全局(NSA)开发并于 2001 年发布。它生成一个 256 位(32 字节)的哈希值,与 SHA-1 相比提供了显著的碰撞抵抗和安全性的改进。SHA-256 在加密应用、数字证书和数据完整性验证中常用,由于其强大的安全特性和在加密库和平台上的广泛支持,它仍然是一个流行且可信的哈希算法。

所有这些函数都是最受欢迎的数据库引擎的原生支持,因此它们是计算基于哈希的代理键的好候选。

下表总结了使用这三个函数可以生成的唯一值的数量以及从两个随机值生成哈希时的理论哈希冲突概率。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d7a05219902d5714376fc73da7c1cd47.png

MD5、SHA-1 和 SHA-256 哈希函数的关键特征 | 图像由作者提供

虽然碰撞概率(对于两个随机值)很容易计算,但在考虑数据库中 SK 生成时并不实用。在实践中,我们处理存储大量这些哈希值作为 SK 的数据库表,特别是主键(每个表中的每行都必须是唯一的)。那么如何以有用的方式计算碰撞概率呢?这就是所谓的“生日悖论”派上用场的地方。

生日悖论

“生日悖论”是概率论中一个有趣的概念,它探讨了在群体中至少有两个人共享相同生日的可能性。尽管它的名字听起来像是一个真正的悖论,但实际上它是一个令人惊讶的结果,挑战了我们的直觉。大多数人预期一个群体需要非常大,共享生日的可能性才会增加。实际上,只要房间里只有 23 个人,就有超过 50%的几率会有两个人共享相同的生日。

如果你想了解更多关于悖论的信息,这里有一篇很好的阅读材料:理解生日悖论 – BetterExplained

为了这篇文章的目的,从悖论中重要的是要计算在一个有n行、每行都有一个唯一的自然键(哈希计算由此键生成)的表中,使用返回b位长哈希的哈希函数时的哈希碰撞风险:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/d81b8da2539cb7d35bece7a1905224bf.png

哈希碰撞概率的精确公式 | 图片由作者提供

对于大量可能的哈希值(在我们的情况下,128 位或更长的值肯定是这样),精确公式可以简化为以下形式:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/4ab4ef5912be16fcdb234f6b0c78e795.png

哈希碰撞概率的近似公式 | 图片由作者提供

代理键的碰撞概率

有数学公式,我们可以计算不同哈希函数(生成不同长度的哈希键)和不同表大小的哈希碰撞风险(即概率)。

下表展示了将第n条记录插入表中时,MD5、SHA-1 和 SHA-256 函数的 SK 哈希碰撞概率。概率以 12 位小数精度显示。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/40a53f0a981473fd0cf843cb7ef6340c.png

基于哈希的代理键碰撞概率,针对不同记录数和哈希函数 | 图片由作者提供

但是如何解释这些概率呢?0.000000000001 的概率是可以接受的吗?你每天可以加载多少条记录而不超过这个概率?让我们尝试使用不同的现实类比来可视化这个问题。

可视化 SK 碰撞风险

为了将这些非常低的概率简化为易于理解的类比,我们应该简化我们的问题空间,即减少我们拥有的变量数量。让我们从一个假设开始,即我们使用 MD5 算法,该算法生成 128 位的哈希值。因此,我们的b(哈希值的二进制长度)是 128。我们将在后面做出更多的假设。

EuroMillions 和 Powerball 类比

你是否曾经玩过任何大规模的彩票游戏,如 EuroMillions 或 Powerball,当你正确预测中奖号码时,你可以赢得数百万欧元或美元?我没有,但在我家里,我有一个经常玩 Lotto 的人(这是波兰最受欢迎的国家彩票)。他总是下注他的“幸运”数字。已经超过 30 年了,他仍然没有赢得奖金池。

让我们专注于 EuroMillions 和 Powerball。赢得奖金池的概率是:

  • EuroMillion:139,838,160 分之一

  • Powerball:292,201,338 分之一

这些概率分别是比十亿分之一概率的哈希碰撞高约 7,000 倍和 3,500 倍。

将边界哈希碰撞风险降低 1,000,000 倍至十亿分之一(即 0.000000000000000001),使其变得如此之小,以至于即使两次赢得 EuroMillions 或 Powerball 的奖金池的可能性也要大得多。并且请注意,没有证据表明有任何个人在这些游戏的常规版本中两次赢得奖金池。

数据摄入类比

假设你有一个非常密集的记录量,你需要将这些记录摄入到你的表中。并且你需要确保在未来的 20 年内,你不会超过哈希碰撞的十亿分之一概率。你可以在你的表中加载多少条记录?

下表总结了使用近似公式计算出的哈希碰撞概率的结果。

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/091dda2472fbfef038d53fe25f7c5fe3.png

基于哈希代理键达到边界碰撞概率为十亿分之一的记录数和相关摄入速度 | 图由作者提供

结论:

  • 要在 20 年的时间跨度内超过 MD5 哈希碰撞的十亿分之一概率,你必须每天插入超过360 万条记录(或超过每秒 41 条记录)。

  • 对于SHA-1函数,你大约需要每秒插入2.7 百万条记录,这对于一个非常大的数据库集群来说是可行的,但只有在某些应用中才可能。

  • 使用SHA-256算法,所需的速率是每天 760 十亿条记录,这远远超出了当前的技术能力。

因此,如果你每天处理的数据量在几百万条记录(每张表)或以下,使用 MD5 是可行的。超过这个量,你可能需要考虑依赖 SHA-1。

让我们再换一个角度。你是否考虑使用一个使用bigint数据类型的增量 SK?如果是这样,你可能不怕达到数据类型值限制(对于有符号bigint来说,这是一个巨大的 9,223,372,036,854,775,807)。你的潜在最大记录数永远不会超过它的 1/10,000,对吧(所以你有几个数量级的安全缓冲区)?如果是这样,在使用 MD5 哈希时,即使达到最大记录数,碰撞概率也会是 0,00000000125(大约是八亿分之一,所以比中得欧洲百万或 Powerball 的奖金还要低)。这是一个可接受的风险吗?

让金钱说话

在进行定量风险分析时评估风险及其各种避免/缓解计划,通常使用所谓的**预期货币价值(EMV)**来衡量风险,以下公式如下:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/1fc7173f7678801214c20cafe9df9703.png

预期货币价值(EMV)公式风险 | 图片由作者提供

其中 P 是风险的概率,C 是其影响(即成本)。

然后,可以将风险实现后的 EMV 与缓解策略的成本进行比较。

让我们考虑以下简化的例子:一家公司使用单盘硬盘存储关键数据库数据。存在硬盘故障的风险,这可能导致数据丢失和运营中断。行业数据显示,单盘硬盘的年故障率大约为 2%。在单盘故障的情况下,数据恢复、停机损失和潜在声誉损害的估计成本为 500,000 EUR。因此,风险的 EMV 为 10,000 EUR。

一个示例缓解策略是建立一个 RAID 系统来降低因硬盘故障导致数据丢失的风险。其成本估计为 5,000 EUR,这比风险的 EMV 低 2 倍。因此,投资 RAID 系统是完美的选择。

现在让我们将相同的方法应用于基于哈希的代理键碰撞风险,并重新使用之前计算中的一些数字。以下是我们的结果:

  • 使用的哈希算法:MD5

  • 记录摄入速度:每秒 41 条记录

  • 哈希碰撞风险概率:十亿分之一

  • 处理哈希碰撞发生后的事后影响的估计成本:约 100M EUR(可能被大大夸张了,但没关系)

对于上述数字,EMV 远低于 0.01 EUR。由于 EMV 接近零,投资任何避免策略(如避免哈希碰撞的复杂解决方案)是不合理的。

处理 SK 碰撞风险

预防风险

预防哈希冲突的风险是一种理想化的方法。除了从风险的成本角度来看没有正当理由之外,实施起来也具有挑战性。预防风险意味着即时检查任何新计算的哈希值是否存在冲突。为了实现这一点,你必须检查新计算的哈希值及其原始自然键与所有已生成的哈希值对及其原始 NK。你已生成的哈希值越多,操作的成本就越高。你可以采用一些高级技术来优化检查(如布隆过滤器),但仍然必须考虑:

  • 实施此类检查存在显著的额外成本,

  • 它将延迟整体解决方案的交付日期,

  • 它将从一开始就减慢数据处理速度(并且已经处理的数据越多,对性能的影响就越大)。

因此,考虑到你的数据特性和你所处的业务环境,你必须自己回答这个问题:预防是否值得所有这些麻烦。

接受风险

考虑到碰撞发生的极低风险,我的首选方法是接受风险。但这并不意味着无所作为。能够对风险的出现做出反应非常重要。为了做到这一点,你仍然需要监控表以检测潜在的哈希冲突。

不幸的是,没有一种通用的方法可以在保持机制简单的同时,最小化性能和成本影响。不同的技术可以根据实际用例使用,并且它们依赖于:

  • 数据特性 - 例如,你是否预期会发生更新,如果是这样,你是否跟踪变更的历史(如 SCD1 与 SCD2),

  • 技术能力 - 例如,当在目标表中插入/更新记录时,它是否可以生成事件或通知特定条件发生;或者也许它可以在插入/更新目标表中的记录时,将满足特定标准的记录重定向到专用表中,

  • 业务环境 - 例如,你是否需要一发生潜在的重复项就做出反应(因此你需要即时检测冲突)或者可以延迟一些时间做出反应(因此可以在数据写入和可能使用后异步处理数据,并可能进行检测)。

在设计哈希冲突的监控机制时,请记住碰撞发生的极低风险,使其保持简单,不要过度设计。如果业务坚持要求具有同步检测能力(这实际上等同于预防机制),并且实现异步过程要简单得多,那么从经济角度来考虑:计算这两种情况下的预期货币价值,让金钱来决定。

发生冲突后的反应

好吧!让我们最终讨论当风险最终实现并且发生哈希碰撞时可以做什么。我们的监控系统通知我们这一情况,我们需要做出反应。

生活小窍门:如果你容易遇到这种极其罕见的事件,即哈希碰撞发生在你身上 🤯,也许你应该考虑玩像欧洲百万或 Powerball 这样的彩票游戏! 💰 🤑💰

遵循 KISS 原则,我的首选方法是简单地扩展哈希生成函数包装器(你应该使用一个!)并添加一个简单的条件逻辑来处理碰撞。

考虑一下来自 dbt-utils 包中相当流行的 generate_surrogate_key 宏。它的关键片段是:

{{ dbt.hash(dbt.concat(fields)) }}

它调用了哈希函数,将组成自然键的字段值的连接列表作为参数传递。

假设我们有一个以下复合自然键(以连接形式)导致哈希键碰撞:“XXX-YYY-ZZZ”。我们已经测试过,向其追加 “-#” 就足以确保哈希的唯一性。为了避免碰撞,可以在宏体中进行以下简单更改——用下面的代码块替换上述提到的行(请注意,dbt 宏是用 Jinja 模板定义的,因此这种特定的代码形式):

{%- set concat_fields = dbt.concat(fields) -%}

{%- set modified_concat_fields = (
    "case when " ~ concat_fields ~ " = 'XXX-YYY-ZZZ' then "
    ~ dbt.concat([concat_fields, "'-#'"]) ~ " else "
    ~ concat_fields ~ " end"
) -%}

{{ dbt.hash(modified_concat_fields) }}

如果你看到代码块中硬编码的自然键值感到痛苦,我能理解你的感受。但请注意,这个解决方案是多么简单且高效,尤其是在与一个替代方案相比时,该替代方案是在存储碰撞自然键及其无碰撞等价物的专用配置表中执行一些查找。如果需要处理数十个碰撞键,这种扩展逻辑是合理的。但这种情况的可能性有多大呢?

修复了哈希生成逻辑后,你只需要修复数据库中已经写入的具有碰撞哈希值的记录。要识别需要更新 SK 的记录,你需要依赖于自然键。这就是为什么在目标表中存储自然键列与代理键一起很重要的原因。

最后,请记住,你很可能永远不需要执行这种操作,所以……这只是以防万一。


🖐 🤓👉 有趣的事实

你知道吗?在将大约 500 万条记录(具有唯一自然键)插入到你的表中,并使用 MD5 算法作为代理键生成哈希函数后,哈希碰撞的概率大致等同于从地球上所有水中两次挑选出相同水滴的概率?

根据美国地质调查局(USGS)的数据,地球上的总水量大约为 1.386 × 10⁹立方千米(km³)。平均雨滴体积可以假设为大约 0.05 毫升(mL)。因此,水滴的数量大约为 2.772 × 10²⁵。假设每个水滴被抽取的概率相同,且抽取是独立的,抽取相同随机水滴第二次的概率是:

https://github.com/OpenDocCN/towardsdatascience-blog-zh-2024/raw/master/docs/img/b9d2b99781ce5022ea39a310f83e4cb6.png

地球上所有水体中两次抽取相同水滴的概率公式 | 图片由作者提供

使用生日悖论的近似公式,我们可以计算出需要将多少条记录插入表中,以达到不同哈希算法的哈希冲突的临界概率。对于 MD5,大约需要 500 万条记录,对于 SHA-1,则需要巨大的 3250 亿(3.25 × 10¹¹)条记录,而对于 SHA-256,则需要天文数字的 9200 万(9.2 × 10²⁵)条记录。

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值