Круглые константы в Кеччаке
Недавно, просто ради этого, я пытался реализовать Keccak, криптографический примитив SHA-3. Однако я столкнулся с некоторыми проблемами, в частности с вычислением округлых констант, используемых на шаге "Йота" перестановки.
Просто чтобы убрать это с дороги: Да. Я знаю, что они круглые константы. Я знаю, что могу жестко закодировать их как константы. Но где в этом веселье?
Я специально ссылался на документ спецификации FIPS 202 на SHA-3, а также на собственный справочник Keccak команды Keccak. Однако, несмотря на мои усилия, я не могу получить правильные константы. Я никогда раньше не сталкивался с битовыми манипуляциями, поэтому, если я делаю что-то совершенно неправильно, не стесняйтесь, дайте мне знать.
rc является функцией, определенной в стандарте FIPS 202 Keccak, который является регистром сдвига с линейной обратной связью с полиномом обратной связи x^8 + x^6 + x^5 + x^4 + 1
,
Значения t
(специфичные для SHA-3) определяются как набор целых чисел, который включает j + 7 * i_r
где i_r = {0, 1, ..., 22, 23} и j = {0, 1, ..., 4, 5}.
Ожидаемые результаты (круглые константы), определяются следующим образом: 0x0000000000000001, 0x0000000000008082, 0x800000000000808a, 0x8000000080008000, 0x000000000000808b, 0x0000000080000001, 0x8000000080008081, 0x8000000000008009, 0x000000000000008a, 0x0000000000000088, 0x0000000080008009, 0x000000008000000a, 0x000000008000808b, 0x800000000000008b, 0x8000000000008089, 0x8000000000008003, 0x8000000000008002, 0x8000000000000080, 0x000000000000800a, 0x800000008000000a, 0x8000000080008081, 0x8000000000008080, 0x0000000080000001 и 0x8000000080008008.
Реализация функции rc
uint64_t rc(int t)
{
if(t % 255 == 0)
{
return 0x1;
}
uint64_t R = 0x1;
for(int i = 1; i <= t % 255; i++)
{
R = R << 0x1;
R |= (((R >> 0x0) & 0x1) ^ ((R >> 0x8) & 0x1)) << 0x0;
R |= (((R >> 0x4) & 0x1) ^ ((R >> 0x8) & 0x1)) << 0x4;
R |= (((R >> 0x5) & 0x1) ^ ((R >> 0x8) & 0x1)) << 0x5;
R |= (((R >> 0x6) & 0x1) ^ ((R >> 0x8) & 0x1)) << 0x6;
R &= 0xFF;
}
return R & 0x1;
}
вызов функции rc
for(int i_r = 0; i_r < 24; i_r++)
{
uint64_t RC = 0x0;
// TODO: Fix so the limit is not constant
for(int j = 0; j < 6; j++)
{
RC ^= (rc(j + 7 * i_r) << ((int) pow(2, j) - 1));
}
printf("%llu\n", RC);
}
Любая помощь по этому вопросу высоко ценится.
1 ответ
Я сделал несколько случайных изменений в коде, и теперь он работает. Вот основные моменты:
j
цикл должен считать от 0 до 6. Это потому, что 2^6-1 = 63. Так что еслиj
никогда не равен 6, то на выходе никогда не может быть установлен MSB, т. е. на выходе 0x8... невозможно.С использованием
pow
Функция, как правило, плохая идея для этого типа приложения.double
значения имеют неприятную привычку быть немного ниже, чем хотелось бы, например, 4 на самом деле 3.99999999999, который усекается до 3, когда вы конвертируете его вint
, Сомнительно, что происходило в этом случае, но зачем рисковать, так как легко просто умножить переменнуюshift
по 2 на каждый проход через петлю.Максимальное значение для
t
7*23+6 = 167, поэтому% 255
ничего не делает (по крайней мере, со значениемi
а такжеt
в этом коде). Кроме того, нет необходимости лечитьt == 0
как особый случай. Цикл не будет работать, когдаt
0, поэтому по умолчанию результат 0x1.Реализация регистра сдвига с линейной обратной связью довольно проста в C. Каждый член полинома соответствует одному биту.
x^8
просто 2^8, что0x100
а такжеx^6 + x^5 + x^4 + 1
является0x71
, Так что всякий раз, когда немного0x100
установлен, вы XOR результат0x71
,
Вот обновленный код:
#include <stdio.h>
#include <stdint.h>
#include <inttypes.h>
uint64_t rc(int t)
{
uint64_t result = 0x1;
for (int i = 1; i <= t; i++)
{
result <<= 1;
if (result & 0x100)
result ^= 0x71;
}
return result & 0x1;
}
int main(void)
{
for (int i = 0; i < 24; i++)
{
uint64_t result = 0x0;
uint64_t shift = 1;
for (int j = 0; j < 7; j++)
{
uint64_t value = rc(7*i + j);
result |= value << (shift - 1);
shift *= 2;
}
printf("0x%016" PRIx64 "\n", result);
}
}