是否有任何有效的概念验证字符串比较定时攻击?

信息安全 定时攻击
2021-08-31 05:12:41

我尝试用两种语言(Java 和 Python)重现字符串比较计时预言,但我没有看到基于比较输入的计时有任何相关性。是否有任何示例,或者您是否发现我的代码有问题?请注意,我想要一些简单的东西,就像我给出的例子一样。“在野外”对大型程序的攻击不是一个很好的例子。

我也找不到任何简单且可立即运行的定时攻击示例。我相信我已经使用 -Djava.compiler=NONE 禁用了 Java 的优化。运行代码需要花费大量时间,因此显然没有完全优化代码。

经常谈论这似乎很奇怪,但是那里没有实际的容易找到的例子。

这是我今天的 Java 代码。输出有点随机变化,我已经确定没有明显的相关性。我在输出中添加了一些注释,这样你就可以看到哪个比较慢。

public class TimingAttackJavaTest {
    public static void main(String[] args) {
        TimeCompare("a0", "b0", "a, b      ");
        TimeCompare("aaaaaaaaaa1", "bbbbbbbbbbb1", "a*10, b*10");
        TimeCompare("aaaaaaaaaa10", "bbbbbbbbbbb10", "a*10, b*10");
        TimeCompare("aaaaaaaaaa2", "b2", "a*10, b  ");
        TimeCompare("a3", "bbbbbbbbbbb3", "a, b*10  ");
        TimeCompare("aaaaaaaaaa4", "bbbbbbbbbbb4", "a*10, b*10  ");
        TimeCompare("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1", "a*100, a*100");
        TimeCompare("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa", "aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa1", "a*100, a*100");
        TimeCompare("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "a*100, b*100");
        TimeCompare("aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa2", "bbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbbb", "a*100, b*100");

        TimeCompare("a", "a", "a, a      ");
        TimeCompare("aaaaaaaaaaa", "aaaaaaaaaaa", "a*1, a*10 ");
        TimeCompare("1aaaaaaaaaa10", "2bbbbbbbbbbb10", "a*10, b*10");
    }


    static void TimeCompare(String a, String b, String outputLabel)
    {
        boolean temp = false;
        long startTime;
        long endTime;
        long duration;

        startTime = System.nanoTime();
        for(int i = 0; i < 10000000; i++)
        {
            temp = a.equals(b);
        }
        endTime = System.nanoTime();
        duration = endTime - startTime;
        System.out.println(outputLabel + temp + "\t\t" + duration);

    }
}

输出(请注意,随着程序的启动,第一次比较总是很慢):

a, b      false     930418800
a*10, b*10false     513034800
a*10, b*10false     510905300
a*10, b  false      534267200
a, b*10  false      524720700
a*10, b*10  false       509250100
a*100, a*100false       516159000    **This should return slowly**
a*100, a*100false       508714700    **This should return slowly**
a*100, b*100false       511160700    **This should return quickly**
a*100, b*100false       522029800    **This should return quickly**
a, a      true      278492700
a*1, a*10 true      284238900
a*10, b*10false     506245000
1个回答

您的示例无法正常工作有几个原因,其中主要原因是您使用了最好的案例数据(即最不可能显示差异)。如果字符串的差异在开始时,提前比较应该提醒您。但是,由于您使用的是 JIT 语言,因此存在各种可能阻碍此类检查的奇怪可能性。

演示此类攻击的最佳方法不是使用循环,而是使用低级语言(例如 C)进行一次测量。

这是一个运行良好的快速示例:

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <time.h>
using namespace std;

// this is just strcmp() wrapped in timer calls
bool CheckEqual(char* a, char* b, bool show)
{
    int i, r, trv;
    long time_pre, time_post;
    struct timespec ts;

    if((trv = clock_gettime(CLOCK_MONOTONIC,&ts)) != 0) {
        printf("Timing error, pre. Code = %d\n", trv);
    }
    time_pre = ts.tv_nsec + (ts.tv_sec * 1000000000L);

    r = strcmp(a, b);

    if((trv = clock_gettime(CLOCK_MONOTONIC,&ts)) != 0) {
        printf("Timing error, post. Code = %d\n", trv);
    }
    time_post = ts.tv_nsec + (ts.tv_sec * 1000000000L);

    if (show)
        printf("Time: %ld\n", time_post - time_pre);
    else
        printf("First compare done.\n");

    return r == 0;
}

// this function helps us avoid the problem of the compiler being too smart.
// if we were to use string literals that were equal, it'd optimise compile-time
// constant strings into a single instance, so strcmp() would just early-out on
// the fact that the two pointers were equal, which is not what we want to
// demonstrate here - in reality we'd be comparing buffers that aren't static.
char* MakeStr(char c, int n)
{
    int i;
    char* s = (char*)calloc(n+1, sizeof(c));
    for(i=0; i<n; i++)
        s[i] = c;
    return s;
}

int main()
{
    // equal apart from last
    char* aa = MakeStr('0', 64);
    char* ab = "0000000000000000000000000000000000000000000000000000000000000001";

    // equal apart from first
    char* ba = MakeStr('0', 64);
    char* bb = "1000000000000000000000000000000000000000000000000000000000000000";

    // inequality in the middle
    char* ca = "0000000000000000000000000000010000000000000000000000000000000000";
    char* cb = "0000000000000000000000000000001000000000000000000000000000000000";

    // equal
    char* da = MakeStr('0', 64);
    char* db = MakeStr('0', 64);

    // first call to strcmp() and time functions will result in cache misses
    // so we call it once and don't display output to account for that.
    CheckEqual(aa, ab, false);

    printf("Equal but last, ");
    CheckEqual(aa, ab, true);
    printf("Equal but first, ");
    CheckEqual(ba, bb, true);
    printf("Equal but mid, ");
    CheckEqual(ca, cb, true);
    printf("Completely equal, ");
    CheckEqual(da, db, true);

    free(aa); free(ba); free(da); free(db);
    return 0;
}

您的输出应如下所示:

First compare done.
Equal but last, Time: 345
Equal but first, Time: 229
Equal but mid, Time: 234
Completely equal, Time: 270

注意到前两者之间的巨大差异了吗?这是由于早期优化,即一旦发现差异,比较就会退出。在第二次比较中,由于第一个字符比较显示它不相等,所以它花费的时间更少,所以它可以提前返回。在第三个比较中,我们看到这在字符串中走得更远,导致运行时间比“equal but first”稍长,但比“equal but last”短。

完全平等的是一只有趣的野兽。我猜想,当最后一个字符不相等时,需要一些额外的逻辑。相比之下,我认为完全相等的时间更少,因为它只需要返回 0。