【问题标题】:Why compare two strings via calculating xor of their characters?为什么要通过计算字符的异或来比较两个字符串?
【发布时间】:2020-04-15 13:09:45
【问题描述】:

前段时间我发现了一个比较两个字符串并返回一个布尔值的函数(不幸的是,我不记得它来自哪里,很可能来自某个 Python 框架)。很容易理解这里发生了什么。 如果它们不匹配,则在 char 之间查找 xor 返回 1 (True)。

def  cmp_strings(str1, str2):
    return len(str1) == len(str2) and sum(ord(x)^ord(y) for x, y in zip(str1, str2)) == 0

但是为什么要使用这个函数呢?不是和str1==str2一样吗?

【问题讨论】:

    标签: python string algorithm string-comparison


    【解决方案1】:

    XOR 比较解决的安全问题称为定时攻击。 ...您可以在此处观察比较函数需要多长时间才能成功|失败,并使用该知识来获得优于系统的优势。

    有 95 个可打印的 ASCII 字符。如果你有一个 8 个字符的密码,则有 95^8 (6,634,204,312,890,625) 种可能的组合 ...如果正确的密码是你列表中的最后一个,并且你可以每秒尝试 10 亿个密码,大约需要 77 天 暴力破解密码...太长了 - 所以我们需要一个快捷方式!

    存储字符串的方法有无数种——可能有十几种流行使用{长度前缀、空终止、...}{Unicode、UTF-8、ASCII、...}。对于这个工作示例,我将使用普遍存在的“使用 ASCII 编码的以 NUL 结尾的字节数组”...IE。 "ABC" 将被存储为 "ABC"NUL{65, 66, 67, 0} ...但是无论您使用什么存储/编码标准,问题本质上都是一样的。

    从句法上讲,比较两个字符串的方法和语言一样多,例如。 if str1 == str2if (strcmp(str1, str2) == 0) 等...但是当您查看它们内部的工作方式时,它们几乎都相同。下面是一些简单(但真实)的伪代码,用于执行经典(非安全)字符串比较:

    index = 0
    LOOP FOREVER {
        IF ( (str1[index] == 0) AND (str2[index] == 0) )  THEN  return 'same'
        IF (str1[index] != str2[index])  THEN  return 'different'
        index = index + 1
    }   
    

    假设秘密密码是"BY3"NUL ...让我们尝试一些密码,并注意比较函数必须执行多少操作才能确定成功|失败。

    1. "A"NUL    ... returns 'different' when 1st char is checked (A)   [zero chars are correct]
    2. "B"NUL    ... returns 'different' when 2nd char is checked (NUL) [first char must be correct]
    3. "BX"NUL   ... returns 'different' when 2nd char is checked (X)   [first char must be correct]
    4. "BY"NUL   ... returns 'different' when 3rd char is checked (NUL) [first two chars must be correct]
    5. "BY1"NUL  ... returns 'different' when 3rd char is checked (1)   [first two chars must be correct]
    6. "BY2"NUL  ... returns 'different' when 3rd char is checked (2)   [first two chars must be correct]
    7. "BY3"NUL  ... returns 'same' when the 4th character is checked (NUL) [all three chars are correct]
    

    您可以看到猜测 1 在循环中第一次失败,猜测 2 和 3 在循环中第二次失败......猜测 4、5、6 在循环中第三次失败......并且猜测 7在循环中第四次成功。

    通过观察比较函数失败的时间,我们可以判断哪个字符是错误的!这意味着我们实际上可以一次猜出密码一个字符。

    再次假设由 95 个可打印字符组成的 8 个字符的密码,我们最后的猜测是正确的......因为我们现在可以一次猜测一个字符,所以需要 95*8 (760 ) 猜测。在每秒 10 亿次猜测中,找到密码大约需要 0.7 毫秒 [闪烁大约需要 100 毫秒] ...这在 77 天内是一个显着的优势 ... 20 个字符密码的优势(95^20 vs 95 * 20)。

    那么我们如何阻止攻击者使用定时攻击呢? [剧透:异或]

    我们需要做的第一件事是使两个字符串的长度相同;其次,在返回“相同”或“不同”之前,我们必须始终检查每个字符……如果不引入新的计时攻击,这很难做到。但是,与其向您展示许多错误的方法,不如让我们看看正确的方法。

    密码应该(在可能的情况下)存储为哈希 ...{DES, MD5, SHA-1, ...} 现在已被证明存在密码缺陷,{SHA-256, SHA-3, Whirlpool, . ..} 仍然很受欢迎 [Oct 2021] ...您可能知道所有哈希(由给定算法生成)的长度相同...因此,如果我们对猜测进行哈希处理并将猜测哈希与存储的哈希进行比较-哈希,我们已经解决了第一个问题 - 我们需要比较的“字符串”(字节数组)现在总是相同的长度。

    其次。如何确保我们的比较函数总是花费相同的时间来做出决定......可能有很多方法可以做到这一点,但最常见的解决方案是像这样使用 XOR:

    result = 0
    index  = 0
    LOOP WHILE (index < hashLength) {
        result = result OR ( secretHash[index] XOR guessHash[index] )
        index = index + 1
    }
    IF result == 0  THEN  return 'same'  ELSE  return 'different'
    

    这样,所有对比较函数的调用都需要相同的时间来运行......不再有计时攻击!

    脚注: 对于不熟悉布尔逻辑的读者 - 去阅读;但这里的本质是:

    If A and B are the same, (A XOR B) gives a result of 0
    If A and B are different, (A XOR B) gives a non-0 result
    
    If A and B are both 0, (A OR B) gives a result of 0
    If either A or B are non-0, (A OR B) gives a non-0 result
    

    所以(查看第二个代码块)第一次异或返回非 0(不同)时,结果变为非 0(不同)并且永远不能返回 0(相同)。

    搜索“cve 定时攻击”将为您提供现实生活中的示例列表。

    【讨论】:

      【解决方案2】:

      考虑到它们的长度相同,它似乎在字符串之间进行相关(XOR 和)字符。在您需要知道“相似性”而不是平等的情况下,可能需要它。也许这就是计划。作者可能想进一步扩展此功能。

      【讨论】:

        【解决方案3】:

        比较任何长度相同的字符串需要相似的时间。当字符串敏感时,它用于安全性。通常用于比较密码哈希。

        如果使用==,Python 会在找到第一个不匹配的字符时停止比较字符。这对散列不利,因为它可以揭示散列与匹配的接近程度。这将有助于攻击者暴力破解密码。

        这就是hmac.compare_digest 的工作原理。

        【讨论】:

        • 为什么更安全?你能举个例子吗?
        • 谢谢。它是有益的。我只是想知道如何完成“揭示”的过程。看起来 Python 一次用一个符号比较字符串。有可能找到比较两个符号所需的时间,然后检测哪里没有匹配的符号并尝试通过暴力等方式猜测它?
        • @funnydman This blog 提供了很多关于它的信息。
        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2020-03-20
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        相关资源
        最近更新 更多