Зачем нам нужна функция сравнения по времени * по одному байту? - программирование
Подтвердить что ты не робот

Зачем нам нужна функция сравнения по времени * по одному байту?

В стандартной библиотеке Go Go есть функция ConstantTimeByteEq, которая выглядит следующим образом:

func ConstantTimeByteEq(x, y uint8) int {
    z := ^(x ^ y)
    z &= z >> 4
    z &= z >> 2
    z &= z >> 1

    return int(z)
}

Теперь я понимаю необходимость постоянного сравнения string (массив и т.д.), так как регулярный алгоритм может замыкаться после первого неравного элемента. Но в этом случае не является регулярным сравнением двух целых чисел фиксированного размера с постоянной операцией времени на уровне ЦП?

4b9b3361

Ответ 1

Не обязательно. И трудно сказать, что компилятор будет испускать после выполнения своих оптимизаций. В конечном итоге вы можете использовать разные машинные коды для высокого уровня "сравнить один байт". Утечка всего лишь крошечного бита в боковом канале может изменить ваше шифрование от "в основном нерушимого" до "надеюсь, что не стоит денег, необходимых для трещины".

Ответ 2

Точка, вероятно, избежит неверных предсказаний ветвления, в дополнение к тому, что результат будет равен 1 или 0 вместо истинного или ложного (позволяя следить за побитовыми операциями).

Сравните, как это компилируется:

var a, b, c, d byte
_ =  a == b && c == d

= >

0017 (foo.go:15) MOVQ    $0,BX
0018 (foo.go:15) MOVQ    $0,DX
0019 (foo.go:15) MOVQ    $0,CX
0020 (foo.go:15) MOVQ    $0,AX
0021 (foo.go:16) JMP     ,24
0022 (foo.go:16) MOVQ    $1,AX
0023 (foo.go:16) JMP     ,30
0024 (foo.go:16) CMPB    BX,DX
0025 (foo.go:16) JNE     ,29
0026 (foo.go:16) CMPB    CX,AX
0027 (foo.go:16) JNE     ,29
0028 (foo.go:16) JMP     ,22
0029 (foo.go:16) MOVQ    $0,AX

При этом:

var a, b, c, d byte
_ =  subtle.ConstantTimeByteEq(a, b) & subtle.ConstantTimeByteEq(c, d)

= >

0018 (foo.go:15) MOVQ    $0,DX
0019 (foo.go:15) MOVQ    $0,AX
0020 (foo.go:15) MOVQ    $0,DI
0021 (foo.go:15) MOVQ    $0,SI
0022 (foo.go:16) XORQ    AX,DX
0023 (foo.go:16) XORQ    $-1,DX
0024 (foo.go:16) MOVQ    DX,BX
0025 (foo.go:16) SHRB    $4,BX
0026 (foo.go:16) ANDQ    BX,DX
0027 (foo.go:16) MOVQ    DX,BX
0028 (foo.go:16) SHRB    $2,BX
0029 (foo.go:16) ANDQ    BX,DX
0030 (foo.go:16) MOVQ    DX,AX
0031 (foo.go:16) SHRB    $1,DX
0032 (foo.go:16) ANDQ    DX,AX
0033 (foo.go:16) MOVBQZX AX,DX
0034 (foo.go:16) MOVQ    DI,BX
0035 (foo.go:16) XORQ    SI,BX
0036 (foo.go:16) XORQ    $-1,BX
0037 (foo.go:16) MOVQ    BX,AX
0038 (foo.go:16) SHRB    $4,BX
0039 (foo.go:16) ANDQ    BX,AX
0040 (foo.go:16) MOVQ    AX,BX
0041 (foo.go:16) SHRB    $2,BX
0042 (foo.go:16) ANDQ    BX,AX
0043 (foo.go:16) MOVQ    AX,BX
0044 (foo.go:16) SHRB    $1,BX
0045 (foo.go:16) ANDQ    BX,AX
0046 (foo.go:16) MOVBQZX AX,BX

Хотя последняя версия длиннее, она также линейна - нет ветвей.

Ответ 3

Если код, который вызвал функцию, должен был немедленно развернуть на основе результата, использование метода постоянной продолжительности не обеспечило бы значительной дополнительной безопасности. С другой стороны, если бы кто-то вызывал функцию на кучу разных пар байтов, сохраняя общее количество результатов, и только ветвь основывалась на конечном результате, то внешний snooper мог бы определить, будет ли это последнее ветвь была взята, но не знала, какой из предыдущих сравнений байтов несет ответственность за нее.

Что было сказано, я не уверен, что вижу много преимуществ в большинстве случаев использования для того, чтобы преодолеть проблему перенаправления вывода метода на нуль или один; просто сохранение текущего показателя notEqual = (A0 ^ B0); notEqual |= (A1 ^ B1); notEqual |= (A2 ^ B2); ... обеспечит тот же эффект и будет намного быстрее.