[about] [index] [prev] [2021-11-07 20:16:49+03:00] [9dbbfb48af71d290a67a389117411ded7ecc11a6]
Topics: [crypto]

Потоковый формат шифрованных пакетов NNCP

http://lists.cypherpunks.ru/archive/nncp-devel/2111/0399.html
Ещё не зарелизил, ибо нужно всякие мелочи дописать и документацию
обновить, но локально уже использую новые наработки. Но это обратно
несовместимая мажорная версия.

В NNCP всегда была "проблема" с тем, что для создания шифрованного
пакета нужно знать длину полезной нагрузки. Не то чтобы это было
серьёзной проблемой, но потоково создавать на лету шифрованные пакеты
было нельзя, только через промежуточный временный файл.

В NNCP реальный размер полезных данных скрыт. У шифрованного пакета есть
заголовок, в чётко заданном известном формате, подпись к нему, после
которых шёл зашифрованный размер, а дальше последовательность 128KiB
блоков, внутри которых какая-то часть отведена под полезную нагрузку, а
какая-то под опциональное дополнение, фактически используемое только для
скрытия размера нагрузки.

Так как размер идёт перед нагрузкой, то его нужно заранее прописать. Так
как это AEAD шифрование разбитое на большие блоки, то, в принципе, можно
было бы просто зарезервировать место в файле и в конце вернутся на него
и дописать зашифрованный размер. Всё равно шифрованный пакет создаётся
на полноценной ФС, так что seek можно делать.

Но всё усложнял тот факт, что пакет при создании и шифровании сразу же и
параллельно хэшируется, поэтому изменить данные было уже нельзя. С
переходом на деревья Меркле (MTH, 26d0fad8f0c8e523ec77c70dec244afc2c0e86e3)
теперь возможно перехэшировать начало файла без проблем. Но всё
усложняется тем, что в NNCP, опять же, на лету создаются и транзитные
пакеты (всё шифруется в несколько потоков и хэшируется). В принципе,
если сохранить начало файла, то в конце уже можно довычислить всю
матрёшку шифротекстов и с MTH-ом поправить хэш.

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

Указывать вне шифротекста метаинформацию о границах payload-а и pad-а не
вариант -- иначе размер никак не скрывается. Очевидное решение, которое
в рассылку и вбросили: добавлять в каждый блок шифротекста (тот самый
128KiB блок) метаинформацию. Просто, но больно уж расточительно как-то,
ибо только ровно в одном блоке нужна информация о том, что в нём
заканчивается полезная нагрузка, а дальше начинается pad, такого то
размера.

Решил использовать трюк из CMAC-а, когда используются два разных ключа
шифрования для "сигнализации" о типе пакета. "Формат" очень простой: вся
полезная нагрузка шифруется на одном ключе, а когда мы понимаем всю её
окончательную длину, то создаём особый блок, в начале которого находится
XDR пакет с информацией о полной длине полезной нагрузки и длине
дополнения (чисто технически это просто два 64-бит big-endian uint-а).
Чтобы сигнализировать о том, что в блоке в начале находится этот XDR
пакет, используется другой ключ шифрования. После этого XDR пакета
продолжает идти остаток полезной нагрузки (если необходимо), после
которого опциональный pad, до конца блока заполняемый нулевыми байтами.

Если размер pad-а выходит за границы текущего блока, то он просто
генерируется на лету из BLAKE3 XOF-а, без всякого AEAD-шифрования.
Небольшое усложнение, но сильно повышающее производительность. Зная
полный размер payload-а и pad-а мы чётко понимаем сколько и чего
осталось прочитать. Явной криптографический аутентификации pad-а не
происходит, ибо мы детерминированно его можем сгенерировать на приёмной
стороне и просто побайтно сравнивать с тем что пришло. Для
злоумышленника же, без знания ключа, всё это неотличимо от шифротекста и
случайных данных.

В итоге для коротких пакетов такой формат становится даже компактнее чем
был прежде: ровно один AEAD-блок, внутри которого и зашитый размер и
payload. И всё это ценой одного лишнего падающего AEAD дешифрования,
которого может и не быть, если у нас на руках блок меньшего чем 128 KiB
размера (он в любом случае будет последним и зашифрованным на втором ключе).

Дифференциацию по ключу шифрования делаю впервые (самостоятельно, CMAC
не в счёт). И проверку дополнения просто через побайтное сравнение тоже
впервые.

[leave comment]