问题 恒定时间等于


阻止 定时攻击,一个恒定的时间 equals 有时需要。有 MessageDigest.isEqual 没有记录是一个恒定的时间方法和 番石榴 HashCode.equals 和别的。他们都做了类似的事情

boolean areEqual = true;
for (int i = 0; i < this.bytes.length; i++) {
    areEqual &= (this.bytes[i] == that.getBytesInternal()[i]);
}
return areEqual;

要么

    int result = 0;
    for (int i = 0; i < digesta.length; i++) {
        result |= digesta[i] ^ digestb[i];
    }
    return result == 0;

谁说JIT在优化时不能引入短路?

发现这一点并不难 areEqual 永远不会再成为现实并打破循环。


我给了它 试试CR 通过计算取决于所有输入位的值并将其馈送到自制的 Blackhole


12003
2018-06-04 18:32


起源



答案:


你不可能知道未来

您从根本上无法预测未来的优化者可能会或可能不会使用任何语言。

展望未来,最好的机会是操作系统本身提供定时常数测试,这样他们就可以在所有环境中进行适当的测试和使用。

这已经持续了相当长的一段时间。例如。 libc中的timingsafe_bcmp()函数首先出现在OpenBSD 4.9中。 (2011年5月发布)

显然,编程环境需要选择它们和/或提供它们自己保证不会被优化的功能。

检查汇编代码

有一些关于优化器的讨论 这里。这是C(和C ++)的思想,但它确实与语言无关,你只能看看当前的优化者可以做什么,而不是未来的优化者可能会做什么。 无论如何,他们理所当然地建议检查汇编代码以了解优化器的功能。

对于那些不一定像C或C ++那样“容易”的Java,考虑到它的本质,但对于特定的安全功能而言,实际上不应该为当前环境做这些努力。

避免可能是可能的

您可以尝试避免定时攻击。

例如。:

虽然直观地增加随机时间似乎是不可能做到的,但它不会起作用:攻击者已经在定时攻击中使用统计分析,你只是增加了一些噪音。

https://security.stackexchange.com/questions/96489/can-i-prevent-timing-attacks-with-random-delays

仍然:如果您的应用程序足够慢,那并不意味着您无法实现时间常量。即:等待足够长的时间。例如。你可以等待计时器关闭然后继续处理比较结果,无论如何都要避免计时攻击。

发现

应该可以使用定时常数比较的实现将定时攻击漏洞的检测写入应用程序。

醚:

  • 在初始化期间运行的一些测试
  • 作为正常操作的一部分,定期进行相同的测试。

同样,优化器处理起来会很棘手,因为它可以(有时甚至会)改变事物的执行顺序。但是例如使用程序在其代码中没有的输入(例如外部文件),并运行两次:一次使用普通比较和相同的字符串,一次使用完全不同的字符串(例如xored)然后再使用这些输入但是使用恒定时间比较。你现在有4个时间:正常比较不应该相同,恒定时间比较应该更慢和相同。如果失败:警告应用程序的用户/维护者,生产使用中可能会破坏常量时间。

  • 理论上的选择是自己收集实际时间(记录失败/成功)并自己进行统计分析。但是在实践中进行操作会很棘手,因为你的测量需要非常精确,因为你不能将它循环几百万次,你只需要测量一次比较而没有足够精确测量它的分辨率。 ..

7
2018-06-07 00:03





JIT不仅仅是 允许 做这样的优化,但它 实际上是这样的 有时。

这是 一个示例错误 我在JMH中发现,短路优化会导致不稳定的基准分数。 JIT优化了评估 (bool == bool1 & bool == bool2), 尽管那样 & 使用而不是 &&,即使在 bool1 和 bool2 被宣布 volatile

JIT不保证它的优化和不优化。即使您验证它是否按预期工作,未来的JVM版本可能会破坏这些假设。理想情况下,核心JDK库中应该有针对此类重要安全原语的内在化方法。

您可以尝试通过某些技术避免不希望的优化,例如

  • 涉及易变的领域;
  • 适用增量积累;
  • 产生副作用,例如,写入共享内存等。

但它们也不是100%防弹,因此您必须验证生成的汇编代码并在每次主要Java更新后重新查看它。


5
2018-06-09 22:34





实际上,你无法预测优化器会做什么。不过,在这种情况下,您可以合理地执行以下操作:

  1. 计算要比较的值的异或。所用时间仅取决于值的长度。
  2. 计算结果字节的哈希值。使用返回单个整数的哈希函数。
  3. 使用这个哈希的独占或 预先计算 等长零序列的散列。

我认为这是一个非常安全的选择,哈希函数不会被优化掉。并且整数之间的异或是相同的速度而不管结果如何。


1
2018-06-12 12:18



回来了 int 会导致碰撞的概率 2**-32,即 2.3e-10对安全敏感的东西来说太过分了。你会减少猜测秘密(机会就像 2**-256猜测是不可能的。 +++使用 long 会更好(并且在64位JVM上也是恒定的时间),但仍然不够好。 - maaartinus


答案:


你不可能知道未来

您从根本上无法预测未来的优化者可能会或可能不会使用任何语言。

展望未来,最好的机会是操作系统本身提供定时常数测试,这样他们就可以在所有环境中进行适当的测试和使用。

这已经持续了相当长的一段时间。例如。 libc中的timingsafe_bcmp()函数首先出现在OpenBSD 4.9中。 (2011年5月发布)

显然,编程环境需要选择它们和/或提供它们自己保证不会被优化的功能。

检查汇编代码

有一些关于优化器的讨论 这里。这是C(和C ++)的思想,但它确实与语言无关,你只能看看当前的优化者可以做什么,而不是未来的优化者可能会做什么。 无论如何,他们理所当然地建议检查汇编代码以了解优化器的功能。

对于那些不一定像C或C ++那样“容易”的Java,考虑到它的本质,但对于特定的安全功能而言,实际上不应该为当前环境做这些努力。

避免可能是可能的

您可以尝试避免定时攻击。

例如。:

虽然直观地增加随机时间似乎是不可能做到的,但它不会起作用:攻击者已经在定时攻击中使用统计分析,你只是增加了一些噪音。

https://security.stackexchange.com/questions/96489/can-i-prevent-timing-attacks-with-random-delays

仍然:如果您的应用程序足够慢,那并不意味着您无法实现时间常量。即:等待足够长的时间。例如。你可以等待计时器关闭然后继续处理比较结果,无论如何都要避免计时攻击。

发现

应该可以使用定时常数比较的实现将定时攻击漏洞的检测写入应用程序。

醚:

  • 在初始化期间运行的一些测试
  • 作为正常操作的一部分,定期进行相同的测试。

同样,优化器处理起来会很棘手,因为它可以(有时甚至会)改变事物的执行顺序。但是例如使用程序在其代码中没有的输入(例如外部文件),并运行两次:一次使用普通比较和相同的字符串,一次使用完全不同的字符串(例如xored)然后再使用这些输入但是使用恒定时间比较。你现在有4个时间:正常比较不应该相同,恒定时间比较应该更慢和相同。如果失败:警告应用程序的用户/维护者,生产使用中可能会破坏常量时间。

  • 理论上的选择是自己收集实际时间(记录失败/成功)并自己进行统计分析。但是在实践中进行操作会很棘手,因为你的测量需要非常精确,因为你不能将它循环几百万次,你只需要测量一次比较而没有足够精确测量它的分辨率。 ..

7
2018-06-07 00:03





JIT不仅仅是 允许 做这样的优化,但它 实际上是这样的 有时。

这是 一个示例错误 我在JMH中发现,短路优化会导致不稳定的基准分数。 JIT优化了评估 (bool == bool1 & bool == bool2), 尽管那样 & 使用而不是 &&,即使在 bool1 和 bool2 被宣布 volatile

JIT不保证它的优化和不优化。即使您验证它是否按预期工作,未来的JVM版本可能会破坏这些假设。理想情况下,核心JDK库中应该有针对此类重要安全原语的内在化方法。

您可以尝试通过某些技术避免不希望的优化,例如

  • 涉及易变的领域;
  • 适用增量积累;
  • 产生副作用,例如,写入共享内存等。

但它们也不是100%防弹,因此您必须验证生成的汇编代码并在每次主要Java更新后重新查看它。


5
2018-06-09 22:34





实际上,你无法预测优化器会做什么。不过,在这种情况下,您可以合理地执行以下操作:

  1. 计算要比较的值的异或。所用时间仅取决于值的长度。
  2. 计算结果字节的哈希值。使用返回单个整数的哈希函数。
  3. 使用这个哈希的独占或 预先计算 等长零序列的散列。

我认为这是一个非常安全的选择,哈希函数不会被优化掉。并且整数之间的异或是相同的速度而不管结果如何。


1
2018-06-12 12:18



回来了 int 会导致碰撞的概率 2**-32,即 2.3e-10对安全敏感的东西来说太过分了。你会减少猜测秘密(机会就像 2**-256猜测是不可能的。 +++使用 long 会更好(并且在64位JVM上也是恒定的时间),但仍然不够好。 - maaartinus


可以阻止优化:

int res = 0;
for (int i = 0; i < this.bytes.length; i++) {
    res |= this.bytes[i] ^ that.getBytesInternal()[i];
}
Logger.getLogger(...).log(FINEST, "{0}", res);
return res == 0;

但在原始代码上:

使用旧代码,可能应该使用javap进行反汇编,以确保没有进行优化。对于另一个java编译器(如java 9),需要重复这一点。

JIT推迟了,但是你说得对,可能会发生优化:它需要在循环中进行额外的测试(这本身会减慢每个循环的速度)。

所以你是对的。人们可能只希望在整个测量中效果可以忽略不计。而其他一些安全防守有助于,如果只有一个 故障随机延迟,不平等,这总是一个很好的绊倒。


0
2018-06-04 19:00



外部的日志声明如何防止短路? - Sleiman Jneidi
@SleimanJneidi结果 res 记录,必须计算到最后,因此循环可能不会被短路。 - Joop Eggen
......除非你得到 res == -1 在某些时候,然后你可以退出循环。但这将是一个不同的测试,我无法想象编译器这样做。 - maaartinus
我认为你不需要记录。 JIT可以使回路短路,在回路中它只能使用对数线和回路中的短路值;或者它不能,在这种情况下,回报足够好。无论哪种方式,这都归结为试图通过向我们知道等同于&的逻辑提供智能JIT,但我们认为JIT无法识别。 (这不是一个坏方法!) - yshavit
如果结果不平等, res 将收敛于数字位的log2(约4)。所以可能存在一些潜在的优化。 /对于32位ARM,我相信一个足够先进的优化器可以反转 res, 使用 BICS (按位和带有设置标志的反转rhs)并使用条件指令进行优化而不增加内部循环指令计数。 - Tom Hawtin - tackline