本节内容要从一个问题说起:

给出下列判断字符串相等的两段代码,在不查询资料的情况下说明两段代码有什么区别,分别在什么情况下适用。语言:Python

第一段:

def equals(str1, str2) -> bool:
    if len(str1) != len(str2):
        return False
    equal = True
    for i in range(len(str1)):
        equal &= str1[i] == str2[i]
    return equal

第二段:

def equals(str1, str2) -> bool:
    if len(str1) != len(str2):
        return False
    for i in range(len(str1)):
        if str1[i] != str2[i]:
            return False
    return True

均为7行

这是我作为本次算法文章的引子,要引出的内容是Timing Attack(时序攻击)

好像还没有人发现代码写的有点小问题(功能没写错,但是速度稍微慢了亿点):

第一段应该是:

def equals(str1, str2) -> bool:
    if len(str1) != len(str2):
        return False
    equal = True
    for i in range(len(str1)):
        equal |= str1[i] ^ str2[i]
    return equal == 0

第二段应该是:

def equals(str1, str2) -> bool:
    if len(str1) != len(str2):
        return False
    for i in range(len(str1)):
        if str1[i] ^ str2[i] != 0:
            return False
    return True

改进后的两段代码功能与原版没有区别,但考虑到加进了位运算替换掉了==,速度上会有一个不小的提升

如果觉得改进后的版本不好理解,看改进前的即可,里面的思想相同。


以上代码其实是我转译过来的,原文为https://blog.csdn.net/ahyz9638/article/details/101561620,原文使用的语言是Java,但是还是不影响思想。

 

第一段代码,不像@rice0208说的毫无用处,它的特点在于:

比如比较password和pastword两个密码

第一段代码比较完了8个字母后才返回结果,也就是说,时间恒定为8个“单位”

第二段代码比较了4个字母后返回结果,时间随两个字符串变化而变化

这样,第一段代码的耗时是一个相对的“常量”,第二段代码的耗时是一个相对的“变量”,不知大家能否理解。

 

那么你可能会问为什么用“password”这个词作为例子?问的好。答案也很简单,这种思想正广泛运用于密码安全、密钥安全领域。

设想这样一个场景:

某个函数负责比较用户输入的密码和存放在系统内密码是否相同,如果该函数是从第一位开始比较,发现不同就立即返回,那么通过计算返回的速度就知道了大概是哪一位开始不同的,这样就实现了在某些科幻电影中经常出现的按位破解密码的场景。密码破解复杂度成千上万倍甚至百万千万倍的下降。

当然现实生活中很少会将密码明文存储在数据库中的例子,因为这样本身就很不安全。但是对于Web服务器的私钥来说,无法通过哈希存储检查,必须通过明文比较,在这种情况下,第一段代码就起到了防御Timing Attack的效果。

我们来看一个真实运用第一段代码的例子,选自Java语言的java.security.MessageDigest(注释经过翻译,原文请参考下面的图片)

public static boolean isEqual(byte[] digesta, byte[] digestb) {
   if (digesta == digestb) return true;
   if (digesta == null || digestb == null) {
       return false;
   }
   if (digesta.length != digestb.length) {
       return false;
   }
 
   int result = 0;
   // 常量时间的比较
   for (int i = 0; i < digesta.length; i++) {
       result |= digesta[i] ^ digestb[i];
   }
   return result == 0;
}

而且这段代码不是随意编出来的,有diff图为证(截图选自提到的原文),这是特意改出来的

引用原文结尾作为本文结尾:

论文Remote Timing Attacks are Practical指出:

计时攻击往往用于攻击一些性能较弱的计算设备,例如一些智能卡。我们通过实验发现,也能用于攻击普通的软件系统。本文通过实验证明,通过这种计时攻击方式能够攻破一个基于 OpenSSL 的 web 服务器的私钥。结果证明计时攻击用于进行网络攻击在实践中是可行的,因此各大安全系统需要抵御这种风险。

This post belongs to Column 「算法专栏」 .

0 comments
latest

No comments yet.