【问题标题】:Constant time equals恒定时间等于
【发布时间】:2016-06-04 18:32:11
【问题描述】:

为了防止timing attacks,有时需要固定时间equalsMessageDigest.isEqual 没有记录为恒定时间方法,guava 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 永远不会再次变为真并打破循环。


我通过根据所有输入位计算一个值并将其提供给自制的Blackhole 来给它a try on CR

【问题讨论】:

    标签: java security optimization timing


    【解决方案1】:

    你无法知道未来

    您从根本上无法预测未来的优化器在任何语言中可能会或可能不会做什么。

    展望未来,最好的机会是操作系统本身提供时序常数测试,这样它们就可以在所有环境中得到正确测试和使用。

    这已经持续了很长时间。例如。 libc 中的 timingsafe_bcmp() 函数最早出现在 OpenBSD 4.9 中。 (2011 年 5 月发布)。

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

    检查汇编代码

    有一些关于优化器here 的讨论。它具有 C(和 C++)的思想,但它实际上与语言无关,您只能查看当前的优化器可以做什么,而不是未来的优化器可能会做什么。 无论如何,他们正确地建议检查汇编代码以了解您的优化器的作用。

    鉴于 Java 的本质,它不一定像 C 或 C++ 那样“简单”,但特定的安全功能在当前环境中实际完成这项工作应该不是不可能的。

    可以避免

    你可以尽量避免定时攻击。

    例如:

    虽然直觉上添加随机时间似乎是可行的,但它不会起作用:攻击者已经在使用统计分析进行定时攻击,你只是增加了一些噪音。

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

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

    检测

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

    以太:

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

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

    • 理论上的选择是自己收集实际计时(也记录失败/成功)并自己进行统计分析。但是在实践中执行起来会很棘手,因为您的测量需要非常准确,因为您不能循环几百万次,您正在处理仅测量一次比较并且没有足够准确地测量它的分辨率。 .. .

    【讨论】:

      【解决方案2】:

      JIT 不仅允许进行此类优化,而且有时实际上也这样做

      这是我在 JMH 中发现的a sample bug,其中短路优化导致基准分数不稳定。 JIT 优化了(bool == bool1 &amp; bool == bool2) 的评估,尽管使用了&amp; 而不是&amp;&amp;,即使bool1bool2 被声明为volatile

      JIT 不保证它优化了什么和不优化什么。即使您验证它是否如您所愿,未来的 JVM 版本也可能会打破这些假设。理想情况下,核心 JDK 库中应该为这些重要的安全原语提供内置方法。

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

      • 涉及易变字段;
      • 应用增量累积;
      • 产生副作用,例如写入共享内存等

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

      【讨论】:

        【解决方案3】:

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

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

        我认为哈希函数不会被优化掉是一个相当安全的赌注。并且整数之间的异或无论结果如何都是相同的速度。

        【讨论】:

        • 返回int 会导致碰撞概率为2**-32,即2.3e-10,这对于任何安全敏感的东西来说都太过分了。你会减少猜测秘密(机会像2**-256,即不可能)来猜测碰撞。 +++ 使用long 会更好(在 64 位 JVM 上也是恒定时间),但仍然不够好。
        【解决方案4】:

        这样可以阻止优化:

        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 启动较晚,但你是对的,优化可能会发生:它需要在循环中进行额外的测试(这本身会减慢每个循环的速度)。

        所以你是对的。人们可能只希望在整个测量过程中这种影响可以忽略不计。如果只是失败时的随机延迟,其他一些安全措施会有所帮助,不平等,这总是一个很好的绊脚石。

        【讨论】:

        • 外部的日志语句如何防止短路?
        • @SleimanJneidi 由于记录了结果res,所以必须计算到最后,所以循环不能短路。
        • ... 除非你在某个时候得到res == -1 然后你可以退出循环。但这将是一个不同的测试,我无法想象编译器会这样做。
        • 我认为您不需要日志记录。 JIT 可以使循环短路,其中它可以在日志行和返回中使用短路值;或者不能,在这种情况下,回报就足够了。无论哪种方式,这都归结为试图通过向 JIT 提供我们知道等同于 & 但我们认为 JIT 无法识别的逻辑来智取 JIT。 (这不是一个坏方法!)
        • 如果结果不相等,res 将收敛到刚好超过 log2 的位数(大约 4)。因此,可能存在某种潜在的优化。 / 对于 32 位 ARM,我相信足够先进的优化器可以反转 res,使用 BICS(按位和设置标志的反转 rhs)并使用条件指令进行优化,而不增加内部循环指令数。
        猜你喜欢
        • 1970-01-01
        • 2010-09-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多