如何用JVM语言(Java,Scala,Clojure ...)编写定时安全代码?
是否有可能使BouncyCastle之类的库免受定时攻击的影响?

我知道即使在C语言中也很难正确地解决这些问题–在C语言中,您可以查看生成的汇编代码,至少可以预测目标CPU将如何执行它。

在JVM语言中,您不知道JVM如何将您的代码转换为机器代码,或者将执行哪些运行时优化。

编辑:我不是真的担心本地定时攻击,而是担心可远程利用的定时攻击。

评论

请记住,在密码学中,“恒定时间”实际上意味着“秘密独立时间”;在相同的时间内完成算法的每个应用程序并不重要;重要的是要确保算法花费的时间不取决于秘密信息。

@Nat:在这种情况下,攻击者将发出要由目标服务器处理的请求,并使用响应时间来处理这些额外的请求,以确定加密操作是否仍在运行,或者是否已经到达“等待循环”;它还可以尝试查看是否通过加密操作从缓存中清除了某些特定的存储元素。

没有“ JVM语言”,“编译语言”或“解释语言”之类的东西。语言是规则和限制(一种规范)的抽象数学集合。解释,编译,托管在JVM上,所有这些都是实现的特征,而不是语言。例如,有针对C和C ++的解释器和JIT编译器,还有Scala和Java的本地编译实现。特别是TruffleC是JVM上的JIT编译的C实现,它的后继者Sulong是JVM上的LLVM IR的JIT编译实现,您…

@ K.Biermann:是的,emscripten例如是从LLVM到ECMAScript的编译器,因此它可以用于在ECMAScript上运行几乎所有内容(通过编译程序(如果使用LLVM编写的语言编写)基于-的编译器存在,或通过将解释器/编译器编译为ECMAScript,然后使用该编译器来运行程序)。我相信,这种罕见的用例将来会越来越少见。例如,TruffleRuby可以使用Sulong来与Ruby C扩展一起解释Ruby程序,而早期的结果表明,这比本机C快,因为两者都可以。
…语言被一起编译到单个Truffle AST中,从而可以进行跨语言优化,例如跨语言障碍进行内联。因此,鉴于这些令人鼓舞的结果,看到Sulong和类似解决方案使用更多的内容,我不会感到惊讶。这意味着“ C提前本地编译”这一假设在将来将变得不那么安全。至少,这是我的预测。

#1 楼

理论是:不要尝试用JVM语言或其他本质上解释但有时可能是编译的语言编写时序安全的代码;宁可


使用从JVM语言方便的角度调用的定时安全库。典型示例:在通过AVA_VAN.5扩充将通用标准评估传递到EAL5 +的JavaCard中,可以肯定地认为AES(如果可用)是定时安全的。
尽可能避免在第一时间需要定时安全。 。典型示例:不要尝试在恒定时间内将一个所谓的16字节值与一个参考16字节值进行比较,而是绘制一个新的AES密钥,然后使用此AES密钥(使用恒定时间AES实现)对所谓的和参考值进行加密。 ,然后使用手边的任何方法比较结果。
作为上述方法的一种变体,请使用对策,这些方法可降低计时攻击的效率,例如造成盲目性。

以上内容也可能对一方不利定时攻击以外的渠道攻击,例如力量分析。


当市场压力另有指示时,选择方案是尽量减少相对安全的选择;并在知道实际目标的情况下仔细检查它们。例如,在编写
// compare two byte arrays so that timing does not leak where a difference lies
public static boolean myEqualByteArrays[(byte[] a, byte[] b) {
    int j = a.length;
    if (j != b.length)
        return false; // arrays are not the same length
    int r = 0; // will stay zero until a difference is found
    while (--j>=0)
        r |= a[j] ^ b[j];
    return r==0; // true if and only if the arrays match
}



二进制操作^|是恒定时间的(&和一元~也是,尽管在此示例中我们不需要这样做);
即使对以下内容进行了深入分析也可以退出循环。

到目前为止,如果我遇到了什么打破这些假设,我没有意识到!




使用任何不能安全地假设为恒定时间的示例:


任何使用的ifswitch ..

短路运算符&&||

逻辑非!

数组访问(例如,由于高速缓存)。 br />整数乘法(包括一个常数):某些CPU的乘法时间取决于一个参数;例子包括该32位CPU和32位CPU(对制造商来说,是对它们的证明)。当然,除法(包括一个常数)。
对大于数据类型组合所支持的较大数据类型的操作编译器,运行时和硬件:可以想象,即使在+-中,某些内部进位也可能导致与数据相关的时序依赖性,例如内部使用32位变量的某些64位类型。对于变量加法,担心的要比增量少,可以在运行时实现或自动优化为:递增低一半,如果不是零,则递增高一半。
移位:定时通常取决于移位计数。
按恒定移位右移:可能取决于移位参数的符号或高阶位(对于无符号类型,这种可能性较小;在Java中,无符号>>>运算符比带符号移位运算符>>的可能性较小。但是,在Java中,仅限于内置的带符号2的补码32位类型?:int的替代形式可能是
// compute the OR of all bits in  a  into the low-order bit of  d
d = (a >>> 1) | a;
d = (d >>> 2) | d;
d = (d >>> 4) | d;
d = (d >>> 8) | d;
d = (d >>>16) | d;
d = -(d&1); // duplicate the low-order bit of  d  over the whole  d
d = ((b^c)&d)^b; // select  b  iff a==0,  c  otherwise

,并且该技术可以扩展为例如椭圆形上的恒定时间点乘法。如果效率确实是次要的,则在某个二进制字段上进行曲线。


评论


$ \ begingroup $
另一个问题:检查a.length == b.length是否不好?您可以给攻击者很大的帮助,因为攻击者可以快速确定字符串的长度。它不是j = min(a.length,b.length)吗?另一个侧面的问题(对不起):任何加密哈希都可以用来拒绝错误的输入(如果(hash(a)!= hash(b))返回false;),据我所知,它将为定时攻击以及何时提供帮助哈希匹配,那么即使是某种天真的比较也可以完成工作。所有这些都表示我同意...使用已经完成并证明是安全的现有功能。
$ \ endgroup $
–阿德里亚诺·雷佩蒂(Adriano Repetti)
17年7月6日在8:33



$ \ begingroup $
@AdrianoRepetti即使您避免显式检查a.length == b.length,您仍然必须确保所有数组访问都在范围之内。这可能意味着运行for(i = 0; i $ \ endgroup $
– Nayuki
17年7月6日在8:39

$ \ begingroup $
@Adriano Repetti:密钥/挑战长度通常被认为是密码系统定义的一部分,因此不是秘密的(至少在一定程度上与密钥/挑战的价值相同);并且,如另一条评论中所述,很难以时序安全的方式泄漏期望的长度,我想要一个简单的示例。再考虑一下,如果(hash(a)!= hash(b))返回false;如果与memcmp进行比较并且密钥具有足够低的熵(例如7字节的随机值,如果真正的定时安全测试需要毫秒,这将很安全)会受到定时的攻击。
$ \ endgroup $
–fgrieu♦
17年7月6日在8:59

$ \ begingroup $
@AdrianoRepetti:攻击进行了:尝试增加a的值。最初,将它们提交给真正的设备进行验证,直到发现a1的故障速度比其他设备慢。然后计算h1 = hash(a1),然后从该值开始跳过a哈希值不与h1相同的第一个字节开头的值。找到较慢失败的新值a2后,跳过其哈希值与h2 = hash(a2)相同的前两个字节不开头的a值,依此类推。攻击必须测试和散列大约$ 2 ^ {55} $的值,但仅需要大约900个在线测试。
$ \ endgroup $
–fgrieu♦
17年7月6日在9:09

$ \ begingroup $
谢谢!没错,那么memcmp会成为薄弱点(4字节的朴素哈希是否有太多的冲突无法使用?CMP应该不会遇到这个问题)。关于数组长度:好吧,您始终可以使用a [min(i,a.length-1)]访问数组,并重复与最后一个字符的比较,但是需要检查长度。很好的解释!
$ \ endgroup $
–阿德里亚诺·雷佩蒂(Adriano Repetti)
17年7月6日在9:21



#2 楼

当然可以用Java或类似语言(例如C#)编写恒定时间的密码代码。但是,您必须正确地进行操作。

此处的“恒定时间”表示可观察到的与时间有关的行为并不取决于秘密数据。这并不意味着执行时间总是相同的,而仅意味着变化与秘密数据元素不相关。

某种恒定时间执行策略,通常称为“微体系结构防御”。 ,往往无法与Java完美配合。这些策略主要与命中所有相关的缓存行有关。 Java JIT编译器和GC很难(即使不是不可能)确保相对于高速缓存行,任何数据都位于明确的位置。当用低级语言(如C)实现微体系结构防御时,它已经非常脆弱(当硬件供应商决定更改其实现时,它们往往会失去其属性)。在Java中,它们几乎是不可用的。

另一方面,“真”恒定时间是完全可能的,其方式与在C语言中几乎相同。通过“真”恒定时间,我的意思是:


没有条件跳转,其条件取决于机密数据。
没有内存访问,其地址取决于机密数据。
操作码其执行时间取决于秘密数据。

您可能想看看BearSSL中如何完成这些事情。 BearSSL是用C语言编写的(大多数情况下),但是该页面中解释的所有内容在Java中的工作方式都相似。我的C代码使用uint32_t来保存布尔标志,约定为“ true”为1,“ false”为0。在这里重要的是不要使用布尔值,以防止编译器(C或Java JIT)使用条件跳。在Java中,您将再次使用int,同时使用1和0。或者,您可能希望对-1使用“ true”,因为它是对位掩码很方便的全模式。 Java的优点之一是它保证了模块化算术,并且其>>运算符执行符号扩展。

评论


$ \ begingroup $
“不使用执行时间取决于机密数据的操作码”部分比较棘手,并且可能与平台有关。谁知道32位JVM上long(64位)的增量是否具有与数据相关的时序?通过扩展,谁知道JavaCard上的short(16位,也是保证的最大宽度)增量?那么在32位ARM上的乘法呢?等等..
$ \ endgroup $
–fgrieu♦
17年7月5日在19:52



$ \ begingroup $
@fgrieu:我已经写了一些有关乘法的文档。无论如何,这里的要点是,尽管“恒定时间代码”的概念特定于所使用的硬件,但是Java并没有实质性地改变。您可以像使用C在相同硬件上一样轻松(或不安)地用Java编写恒定时间代码。另外,如果它运行BouncyCastle,那么我怀疑它是JavaCard ...
$ \ endgroup $
–托马斯·波宁(Thomas Pornin)
17年7月5日在20:01

$ \ begingroup $
我同意您的推理以及消除条件跳转,消除数据相关地址以及使用uint32 0/1作为标志的方法。几年前,我在Stack Overflow上问了类似的问题,最终基于恒定时间原理实现了比特币ECDSA库。
$ \ endgroup $
– Nayuki
17年7月6日在8:49

$ \ begingroup $
“在这里重要的是不要使用布尔值来劝阻编译器”-我为您提供了消息,21世纪的编译器将不在乎。虽然C编译器现在具有_Booltype,但是有很多遗留代码使用整数作为布尔值,优化器会寻找该模式。为了安全起见,请使用volatile int crypto_true = 1和#define CRYPTO_TRUE crypto_true && 1(后者可防止意外修改)。易失性块优化。
$ \ endgroup $
–MSalters
17年7月7日在10:24

$ \ begingroup $
@Will“ volatile”与与计时相关的泄漏问题无关。泄漏与值何时到达高速缓存(而不是保存在寄存器中)有关,而是该时刻(和目标地址)是否取决于值的内容。在此处添加一些“ volatile”,然后仅会将不安全的代码转换为较慢的不安全的代码。一种看待它的方法是,易失性会影响静态数据路由,而恒定时间就是动态数据路由的泄漏。
$ \ endgroup $
–托马斯·波宁(Thomas Pornin)
17年7月31日在12:05

#3 楼

至少不是。算了。

这是根据我在Java方面的经验编写的,但是所有JVM语言都会遇到类似的无法解决的问题。编译时间和运行时间优化存在一些问题,这些问题使字节码几乎无法预测。而且,随着每个主要/次要版本的优化,主要和细微的变化。您必须将代码锁定到特定的次要Java版本,如果更改了代码,则所有时间计算都将被排除在外。

不幸的是,优化管理是实现这一目标的容易部分。使之成为不可能的真正问题是动态线程和垃圾回收的结合。 C天真的没有这些,因此您可以对其进行管理。 Java是自动完成的。

至少有四个不同的垃圾收集器,它们具有适合不同应用程序的功能。他们所有人都始料未及。这是硬件抽象的结果,它是基于JVM的语言的主要目的。这就是Java应用程序突然被JVM淘汰掉的原因。
线程也是动态分配的。这意味着您无法确定性地预测CPU内核中当前正在执行哪个线程。当您简单地启动JVM以执行“ -version”命令时,可能有六个并行运行的线程。确实,Java的SecureRandom加密RNG的种子利用了这种非常不可预测的特性。可以肯定地说,它在JVM内部是混乱的。

并记住JVM是什么。具有硬中断和软中断的全多线程操作系统。这些只是混淆了实际上一次执行的线程数。所有这些混乱行为的结果是,您无法以任何确定性水平来预测Bouncy Castle中for / next循环实际上将花费多长时间。

好的测试是喷气发动机。当劳斯莱斯开始向带有运行Java的ECU的涡轮风扇交付时,您就会知道用基于JVM的语言编写对时间要求严格的代码是安全的。您将等待一段时间,因为在硬件级别上可能没有与C竞争的市场需求。

有Javalution和JRockit Real Time等项目,但这些项目确实具有实时编程的雄心。它们实际上是为人类用户简化应用程序的执行。您不会将JRockit放在小型Cortex处理器上来运行Bouncy Castle。
这些仍然会屈服于您需要底层实时操作系统(例如此处列出的实时操作系统)的事实。

发布OP编辑:

编辑使我的回答更加相关。攻击者现在将面临对服务器响应的分析,这些响应完全被微小的(或没有)但不确定的时间波动所淹没。不仅将有不确定的JVM和OS时序,而且还将是所有网络时序/延迟问题的重中之重。要查看此效果,请尝试:-

ping baidu.com


并观察延迟。在中国。

评论


$ \ begingroup $
精确的GC不应对加密代码引入任何定时侧信道攻击(保守的GC可能),多线程也不应。
$ \ endgroup $
– CodesInChaos
17年7月5日在15:21



$ \ begingroup $
@CodesInChaos是的,进一步说明您的观点,即使您无法编写JVM,JVM也能不受定时攻击的影响吗?如果那里很混乱,那应该掩盖了加密算法的操作,并且时间将随机有效地改变。嗯...
$ \ endgroup $
–Paul Uszak
17年7月5日在15:24

$ \ begingroup $
为了安全,我不想依赖像“分析起来很复杂”这样难以量化的东西。
$ \ endgroup $
– CodesInChaos
17年7月5日在15:26

$ \ begingroup $
这个答案提出了充分的事实,但没有回答问题。主题是关于执行时序,该时序会根据要处理的数据值而变化。
$ \ endgroup $
– Nayuki
17年7月6日在8:42

$ \ begingroup $
@PaulUszak不幸的是你错了。文献中充斥着各种旁道攻击,这些攻击可以克服网络延迟带来的盲目性-我认为垃圾收集延迟不会更加严重(事实上,它们可能更容易-攻击者可以忽略“高”值)。当然,这的确涉及进行更多尝试以求平均延迟,但是密码学家通常并不在乎2 ** 20的另一个因素。
$ \ endgroup $
–马丁·邦纳(Martin Bonner)支持莫妮卡(Monica)
17年7月7日在11:45

#4 楼

如果您担心的只是秘密的独立执行时间,我想您也可以在JVM中合理地执行。

您可以添加带有计时器的内置延迟以实现恒定时间执行。如果您需要严格的实时限制,则可能需要使用非开放式JVM,例如,您需要不间断的GC。尽管您可能会选择G1,但在许多工作负载下,G1不会暂停。这将允许您设置执行时间的合理上限。使用标准的JVM,您可能不想设置100%的保证上限,但这可能就足够了。如果攻击者无法触发这些极端情况。作为附加保护,如果尚未按指定的上限时间准备好结果,则可以乘以分配的时间。因此,即使攻击者可以触发极端负载情况,所测得的加密时间也会被严格舍入,因此他只能测量其生成的负载。

这个复杂系统的混乱性质可能会增加许多潜在的漏洞,但是

即使在JVM上,我们也可以在很大程度上分析性能,尽管更难,我们可以查看字节码,JIT代码,当然还可以凭经验测量各种微步。我们需要记住,即使在组装时,也可能会发生处理器级别的操作,乱序执行,分支预测超线程,内存缓存等等,这些都会影响性能。通常,我们通常不会将它们全部考虑在内,但至少在合理的条件下,我们可以对其进行测试。

实时JVM将使您在运行时做出良好的承诺,并且可能会有用。 br />
很显然,我们一直在寻找新的辅助渠道,定时攻击只是一种相对容易利用的渠道。对于相同的物理机器攻击者,会有更多的场所,例如L3缓存。

评论


$ \ begingroup $
如何在Java上执行确定性的持续时间计时器任务?否则,您将无法进入实时操作系统的一半,而且我不认为在DELL上运行任何基于通用服务器的RTOS。
$ \ endgroup $
–Paul Uszak
17年7月6日在15:31

$ \ begingroup $
定时器可能只会在语义之前而不保证。但是任何延迟都将归因于其他不依赖于秘密的负载,因为任何依赖秘密的计算都已经完成了很高的可能性。
$ \ endgroup $
–梅尔·莫尔(Meir Maor)
17年7月6日在15:42

$ \ begingroup $
@PaulUszak如果您感到惊讶,您可能想检查一下恰好在Dell PowerEdge服务器上运行的“戴尔实时操作系统软件”和RTOS,例如iHawk平台解决方案。 ;)
$ \ endgroup $
– e-sushi
17年7月22日在6:41



#5 楼

当然是。只需使用设置了延迟的回调即可。

例如,假设您的加密转换可能需要1ns至500ns的时间;然后,只需等待授权使用转换指令,然后调用它,并将转换后的结果设置为某个参考即可。 1000ns之后,使用预定参考的结果进行回调触发。

由于您需要将所有内容减慢到最慢的速度才能启用恒定时间评估,因此这会减慢速度。但是从好的方面来说,CPU可以在回调触发之前执行其他工作,因此它不会占用所有CPU时间。

由于CPU时间仍然受转换影响,因此攻击者谁会淹没您的系统并观察由此产生的CPU使用率可能仍然会利用它,即使这很烦人。因此,如果这是一个问题,那么您可能想要对程序中的所有加密评估添加速率限制,或者在回调之前在CPU上忙于工作以确保CPU使用率没有差异。 >
由于这是一个异步编程方案,因此您需要确保使用内存屏障等,否则攻击者可以利用由不正确的多线程编码引起的编程错误。 br />
请注意,某些“预定”值可能应在操作过程中检查。例如,如果某个转换花费的时间比预期的长,那么将来所有评估的等待时间应相应地调高-并可能触发密钥更改,以防万一攻击者从那一长段时间内收集到当前密钥的某些知识评估。

评论


$ \ begingroup $
执行时间算法非常随机。可以通过使高速缓存未命中,增加系统负载等来进一步增加这种情况。这是一种可怕的方法,尤其是从长远来看,“触发键更改”有时有时并不像调用一个函数那样简单。
$ \ endgroup $
–axapaxa
17年7月5日在19:11

$ \ begingroup $
@axapaxa为什么这么重要?这些变化与加密转换无关;攻击者无法使用它们发起计时攻击。
$ \ endgroup $
–纳特
17年7月5日在19:19



$ \ begingroup $
它们很重要,因为这可能导致在给定的时间限制内执行,在这种情况下您什么也不能做,并且将显示原始执行时间。该方法不能解决问题,而是试图通过降低执行速度来隐藏它,但无论如何都需要更改密钥,因此效果不佳。如果您可以快速更改密钥,为什么还要打扰您的修复程序?或者我们可以使用固定时间实现...
$ \ endgroup $
–axapaxa
17年7月5日在23:47