为什么在 Signal 中计算安全数时要迭代 5200 次?

信息安全 信号
2021-08-11 20:58:47

Signal中的安全号码来自对话的用户公钥和他们的电话号码的哈希值。安全号码用于确保对话不是 MITM 版。

在推导安全数字时,SHA-512 迭代了 5200 次根据Signal 安全博客,存在隐私问题 re:hash 中嵌入的电话号码。然而,这不可能是原因,因为可能的电话号码集相对较小。

源代码中的注释:

迭代次数越高,安全级别越高:

  • 1024 ~ 109.7 位
  • 1400 > 110 位
  • 5200 > 112 位

那么:故意减慢安全数字计算的原因是什么?

奖励:大致如何计算安全级别(1024 SHA-512 哈希 ~ 109.7 位)?

3个回答

评论没有很好地解释,但我相信我已经确定了它背后的数学。安全号码是 60 个以 10 位为基数的数字,但它由两个 30 位数字组成:一个基于您的电话号码和公钥,另一个基于您正在与之交谈的人的电话号码和公钥。

假设将高熵值转换为 30 位数字而没有不必要的熵损失,它将包含 log 2 (10 30 ) ≈ 99.66 位熵,相当于 99.66“安全位”(意味着攻击者将有 50%在 2 99.66 /2 = 2 98.66哈希后匹配该安全编号的机会)。多次迭代增加了安全性(因为它增加了攻击者每次尝试的哈希操作数):

对数2 (10 30 × 1024) ≈ 109.66

对数2 (10 30 × 1400) ≈ 110.11

日志2 (10 30 × 5200) ≈ 112.00

这是攻击者必须执行多少哈希才能匹配特定的安全号码,但如果攻击者希望能够读取您发送给他们的消息,他们需要知道与他们的公钥匹配的私钥在哈希中使用。生成 RSA 密钥对的计算成本很高,但 ECC 更快。如果密钥对的生成速度足够快,那么迭代哈希以增加对安全数的攻击下限是有意义的。

安全号码是稳定标识符和用户公钥的派生。为对话中的两个人计算安全号码。

真正重要的代码是这段代码

byte[] publicKey = getLogicalKeyBytes(unsortedIdentityKeys);  
byte[] hash = ByteUtil.combine(ByteUtil.shortToByteArray(FINGERPRINT_VERSION), publicKey, stableIdentifier.getBytes());  

for (int i=0;i<iterations;i++) {
        digest.update(hash);
        hash = digest.digest(publicKey);
      }

发生的情况是,我们将指纹版本、公钥和稳定标识符作为起始输入,并使用 SHA-512 对其进行哈希处理。第二次迭代将公钥附加到我们刚刚生成的散列中,然后对其进行第二次散列。

这个添加公钥和重复散列的过程将持续指定的迭代次数。

为什么我们需要比过去做更多的迭代?

这是由于散列的一个基本问题。这是哈希冲突的可能性。

假设我是攻击者(夏娃)。Alice 想与 Bob 交谈,因此 Signal 将她的公钥发送给 Bob,但 Eve 截获了公钥并替换了她自己的公钥。通常会显示密钥已更改,并且安全编号已更改。

如果 Eve 有足够的资源,她可以构造一个与安全号码相匹配的公钥。为了对抗这种威胁,我们使 Eve 需要找到在 5200 轮散列后发生的冲突,并且每轮都添加相同的密钥。

这在计算上变得不可行,因为每一轮散列使得线性地寻找碰撞在计算上更加昂贵。当前选择的迭代次数通常是根据感知到的威胁的资源根据这种风格的攻击需要多长时间来计算的。

我无法从 Signal 中找到任何关于他们选择 5200 的具体原因的计算。

源代码中的这些注释是错误的。开发人员并没有真正理解他写的内容。

这是他的评论:

迭代次数越高,安全级别越高

这种说法是部分正确的。也就是说,它需要更多的资源来进行暴力破解,并且从这个角度来看更安全。但是散列冲突的概率仅取决于散列空间的大小,即散列的长度(假设散列是均匀分布的)。它不依赖于迭代次数。意味着,从哈希冲突的角度来看,它并不更安全。

一个简单的例子。假设散列由单个字节组成,即 8 位。实际上没有人会使用它,因为它不安全。但是让我们更容易理解发生了什么。8 位意味着有 256 个不同的哈希值。无论您进行多少次迭代(5200 或 1000000),最终都会得到 256 个哈希值之一。得到 256 个值之一的概率是多少?它是 1/256。

那为什么是smb。谈论 5200 次迭代 -> 112 位?

同样,让我们​​首先取一个 8 位 = 256 个不同值的哈希值。您需要多少条消息才能尝试获得产生给定哈希的消息?在最坏的情况下,您需要 256 次计算。假设现在我们使用一个哈希函数,它使用原始哈希函数的 2 次迭代。要暴力破解它,在最坏的情况下,您需要再次进行 256 次迭代,每次迭代都比原始迭代长 x2。意味着您需要时间,例如 512 个原始哈希,即 2^9。这相当于使用原始散列函数对 2^9 值进行暴力破解。如果我们使用 8 次迭代,则需要的时间是 256 x 8 = 2^11,这就像对 2^11 个值使用原始散列函数,即将散列长度从 8 位增加到 11 位。对于 5200 次迭代,这就像增加 log2(5200) ~= 12 位中的哈希值。

在 OP 中,熵是 99.7 位。暴力破解 5200 次迭代的哈希与暴力破解 1 次迭代的时间大致相同,但要长约 12 位。日志2(5200)~=12.3;99.7 + 12.3 = 112。

再说一遍:说增加迭代次数等于增加哈希大小或增加熵是不正确的。它只会增加所需的 CPU 时间。但是熵保持不变,哈希冲突的概率保持不变。