Что: 4eed9f47294d277e84f8ba1451b1b4ced04a09de
Когда: 2025-01-18 17:53:48+03:00
Темы: crypto keks
Начинаю делать enveloped-data аналог http://www.keks.cypherpunks.su/enveloped_002ddata.html http://www.keks.cypherpunks.su/signed_002ddata.html Не смотря на то, что KEKS ещё так себе покрыт тестами, особенно его "PKI" часть, но приспичило меня всё же продумать формат для шифрования на его основе. Аналог подписанных данных (SignedData CMS, PKCS#7) у меня уже имеется. Аналог X.509 сертификатов тоже. Главным отличием является то, что сертификаты сами по себе являются signed-data документом. Нет разделения на совершенно отдельный формат сертификата и совершенно отдельный формат произвольно подписанных данных. 0 000 { 2 1 006 load: { 2 2 009 t: "cer" 2 015 v: { 3 3 019 ku: { 2 4 023 ca: NIL 4 028 sig: NIL } 3 034 pub: [ 1 4 035 0: { 3 5 038 a: "ed25519-blake2b" 5 056 v: 32:81A531CAA9342C482901274F5287C8D88918F8C0... 5 092 id: 94c109bc-d2d5-4a0c-a962-7d7de7a3eadc } ] 3 115 sub: { 2 4 118 C: "RU" 4 124 CN: "SubCA" } } } 1 138 sigs: [ 1 2 139 0: { 2 3 144 tbs: { 3 4 149 cid: 019479ea-fdc9-745f-91f0-ab248fcaef4d 4 170 exp: [ 2 5 171 0: TAI64(2025-01-18 14:57:48 TAI, 2025-01-18 14:57:11 UTC) 5 180 1: TAI64(2026-01-18 14:57:48 TAI, 2026-01-18 14:57:11 UTC) ] 4 194 sid: e451d616-8a97-41b6-a37b-7bb245f97fc2 } 3 217 sign: { 2 4 220 a: "ed25519-blake2b" 4 238 v: 64:A0B62600DA9A49BDF051598B09627F14BE995987... } } ] } Это позволяет иметь несколько подписей над TBS-ом сертификата. Например "выпустить" его как-бы несколькими CA, несколько якорей доверия иметь. Совершенно нет уверенности что это может пригодиться и вообще здравая идея, но возможность есть (никто же не мешает именно для сертификатов сделать ограничение на кол-во подписей?). Имя сертификата, его subject, это просто map со строчками. Map убирает автоматом необходимость проверки дубляжа в записях. С ним удобнее работать. Плюс позволяет всё же чуть более сложные чем ровно-одна-строчка значения задавать, хоть какая-то простая структура. Validity аналог вынесен за пределы TBS-а. Всё что CA-specific выносится в аналог signerInfo подписи конкретной. Поэтому у меня в sigs, в tbs, exp(iration) поле является тем самым validity. Это также позволяет обновлять сертификат без смены его TBS-а, без затрагивания тела, в котором, как минимум, могут идти только ключи и имя. Просто добавить/обновить подпись от CA. KeyUsage это ku поле, являющееся map-ом с NIL-значениями, что делает его фактически set-ом строчек (ключей). Сертификат может переносить несколько ключей. Но предполагается, что это только и исключительно для случаев, когда надо их всегда для какой-то задачи переносить вместе. Например в NNCP всегда вместе идут ключи DH, ключи подписи, ключи для Noise-а. ku должен при этом содержать "nncp" какой-нибудь в качестве контекста применения. У каждого ключа есть идентификатор в виде UUIDv4. Рекомендуется его генерировать детерминировано из хэша от структуры публичного ключа. Это как-бы аналог subjectPublicKeyIdentifier. Никаких серийных номеров. Никакой идентификации сертификата по issuerAndSerialNumber. Даже в X.509 PKI это почти никогда не требовалось, раз имелся SKID/AKID. Но всё же сам сертификат как-то надо идентифицировать, а также иметь возможность переподписать, но чтобы подпись была другой. Для этого в в подписе есть "cid" поле (certificate identifier), тоже являющееся UUID-ом и контролируемое CA. Рекомендуется использоваться UUIDv7 для него. Вот и идентификатор, и возможность его обновить чтобы подпись поменялась, и ещё и дружелюбный к сортировке. sid (signer identifier) в подписе является id публичного ключа подписавшего сертификат. Аналог AKID. Формат сертификатов у меня давно не менялся и им вроде все мы удовлетворены. Используются сертификаты с ГОСТ Р 34.10 ключами (я "стандартизовал" только 256A и 512C варианты, которые на скрученных кривых Эдвардса). Но для себя, в качестве proof-of-concept сделал и Ed25519-BLAKE2b вариант. Обнаруживал (1be5aab3dae457709202ac4f0288b7953fe2fa93), что правила валидации Ed25519 разнятся. Я просто сослался на zCach-овский ZIP-0215. Долго не мог решиться: всё же использовать EdDSA/Ed25519 как он описан в RFC всяких или же выпилить SHA2 из него и использовать что-то более минималистичное и быстрое. SHA3, SHAKE? Skein мне так нравящийся? BLAKE2b? Остановился на BLAKE2b. SHA3/SHAKE сами по себе очень медленные в софте -- неприятно, но я считаю их лучше использовать чем SHA2, особенно когда нужна NIST-approved совместимость. Skein всем хорош. Как и BLAKE2. Я просто не смог найти хороших доводов/аргументов в пользу того или иного. BLAKE2 всё же побыстрее будет. BLAKE3 например уже, как по мне, слишком небольшой запас прочности имеет, из-за чего я не хотел бы его для очень ответственных (для хэша) задач применять. В итоге остановился на BLAKE2 и убедился что его заслуженно любят другие криптографы. Но это добавляет немного геморроя: если в Ed25519 реализации сильно зашит SHA2 хэш, то придётся помучиться. Правила формирования подписи в signed-data по сути идентичны SignedData CMS. Не потому что они мне нравились, а просто потому что это вполне себе адекватный и разумный несложный подход. Так же как и возможность не переносить подписываемые данные внутри signed-data контейнера. Структура подписи позволяет добавлять как и произвольные данные не защищаемые подписью (например cer-loc поле в котором можно список URL-ов перенести, которые чисто для информации), так и защищаемые. А также есть prehashing режим, когда мы заранее указываем алгоритмы хэширования и подписываем уже значение хэша в структуре. Но для сертификатов этот режим не разрешён, что удовлетворяет EdDSA и его предупреждениям о недостатках prehash режима подписи. Хотел надолго отложить вопрос с продумыванием аналога для enveloped-data контейнера. Ибо это всё сильно сложнее уже. Но что-то вот напрягает меня (эстетически, так сказать) тот факт, что age не поддерживает пост-квантовую криптографию, хотя его автор достаточно хорошо в ней шарит и вообще реализовал crypto/mlkem и поддержку Kyber в Go TLS. А вот GnuPG поддерживает всю эту тему. И решил форсировать процесс продумывания enveloped-data и написать для себя готовый инструмент на замену age. Меня тоже достаёт что это, в очередной раз, как будто проявление NIH-синдрома. Но мне в age ещё и не нравился его "128-бит ключ" (4adf9b82dc1c86d787ca0a56f0d37b924877277c). Ну и то, что он не чешется касательно PQ. К формату файлов age у меня нет претензий никаких, но раз уж я занялся KEKS-ом и его реализации довольно компактны, то это будет конечно же KEKS структуры. Я смотрел на OpenPGP, многое помню про EnvelopedData CMS, ещё раз посмотрел на age, HPKE. И кроме того, что вполне себе пока удовлетворён предложенным enveloped-data форматом, но и начал писать Go реализацию утилиты для замены age. И сегодня я начал использовать KEM/DEM терминологию. DEM -- data encapsulation mechanism, мне действительно больше нравится чем "encrypted", ибо encrypted ничего не говорит об аутентификации или проверки целостности данных. Типа слишком низкоуровневое слово. А DEM и короткое и уже известное в криптографических кругах. Так же как и KEM (key encapsulation mechanism), который не намекает на детали "инкапсуляции" ключа, ни на какие DH или прочее. Данные шифруются CEK (content encryption/encapsulation key) -- термин из EnvelopedData CMS. А дальше CEK "передаётся" одному или более получателям. В age они названы получателями. Я же просто обозвал KEM-ами. Ибо при использовании шифрования на пароле -- как бы никакого получателя нет. Но при этом scrypt/bcrypt/Argon2/whatever может сформировать KEM. Алгоритм шифрования данных (DEM) независим от используемых KEM. Пока я реализовал только ChaCha20-Poly1305 шифрование. Бьём на кусочки, каждый шифруем/аутентифицируем, в nonce не забываем сигнализировать о последнем кусочке шифротекста, чтобы его нельзя было обрезать. Но не забывал про возню (6529c5c19cb52f69f65fec3d17e718cc491d2c53) с key commitment-ом. Для ряда KEM-ов, которые явно согласовывают/инкапсулируют ключи, как и в age, отсутствие key commitment не несёт проблем. age не даёт возможности использовать не ChaCha20-Poly1305, в отличии от моего формата. И в теории может появится какой-нибудь KEM, которому бы key commitment не помешал бы. Почитав всяких статей, решил добавить простейший key commitment пригодный для ChaCha20-Poly1305: добавляю 128-бит нулей перед шифруемыми данными, которые я должен проверить после расшифрования. Это overhead, но я считаю терпимым, плюс тривиальная реализация. Для шифрования на пароле я добавил balloon-blake2b KEM. scrypt из age я считаю удовлетворительным для хороших парольных фраз, но не спроста же начали Argon2 конкурс, чтобы иметь что-то более серьёзное. Argon2 меня более чем удовлетворял бы, но мне он не нравится тем фактом, что это не алгоритм используемый поверх произвольного хэша. Например у нас доверяют Стрибогу, а Argon2 это Argon2. Я поклонник Balloon алгоритма, который просто описывает какие действия надо над произвольным хэшом сделать, дабы получить усиление пароля. Он появился позже Argon2, но его научные статьи говорят о том, что всё равно в Argon2 нашлось что-то там не ладное, а Balloon закрывает все проблемы. Нашей страны это не касается, но в США (0ddca657ed629fa8458a471d7655d3bd63c6facc) Balloon вообще NIST-ом рекомендован. В качестве хэша предлагается BLAKE2b, чтобы уж везде по минимуму зоопарк хэшей был в проекте. Для шифрования по публичному ключу добавил sntrup4591761-x25519-blake2b. Собственно, чистый/голый Curve25519 не буду использовать, а только гибридную версию с пост-квантовым алгоритмом. Почему не ML-KEM или Kyber? Потому что, следя за рассылками по криптографии, читая DJB, у меня мало доверия к NIST-у. Я думаю что ML-KEM достаточно безопасен, да, но, как и в случае с SHA3 и AES, они выбрали алгоритм далеко не с самым хорошим запасом прочности. А вот к алгоритмам от DJB (+компания) у меня ни йоты претензий. К тому же, по факту, Streamlined NTRU Prime 761 у меня используется уже не один год по много раз в ведь из-за OpenSSH. В нём нет RSA, ECDSA, AES, но есть SNTRUP. Почему sntrup4591761, а не sntrup761? Только по причине того, что я нашёл одну реализацию (sntrup4591761) на Go. Разница между ними только в чуть более компактном кодировании данных (экономия в десятки байт, на фоне более чем килобайта), последний чуть компактнее. Во всём остальном они полностью идентичны. Как объединить результат работы SNTRUP и X25519? Вообще в PDF-ке SNTRUP намекается на допустимость просто хэширования их секретов. OpenSSH так и делает, как и TLS. Но даже мне очевидно, что не помешало бы, на всякий пожарный, захэшировать бы и публичные ключи участников хотя бы. DJB в рассылке это тоже рекомендует, дабы не заниматься доказыванием что и без этого всё нормально. ML-KEM, кстати, в отличии от Kyber, отличается отсутствием хэширования дополнительных данных, чем он мне тоже не понравился. X-Wing (135afdcb923cd9463751d5766279c8426ee6ab00) тоже делает хэширование ключей. Учитывая производительность современных Kyber/X25519, а также килобайтные размеры PQ-ключей, стоимость хэширования становится отнюдь не нулевой, но я бы не экономил на этом. CEK ключ шифруется выработанным из KEM-а KEK-ом (key encryption key). В моих случаях это, опять же, ChaCha20-Poly1305, с добавленным padding, просто чтобы не думалось. Принципиально это всё не отличается от того, что происходит в age. Но он из file-key отдельно вырабатывает CEK и ключ для расчёта MAC-а над всем заголовком. Пока я не увидел хороших аргументов зачем это надо. Да и я бы и не хотел делал список KEM-ов immutable. Откладывал я тему с enveloped-data по причине того, что его же грамотно нужно бы ещё и совмещать с signed-data. То бишь иметь возможность делать не просто encrypt и sign, а (55c0fea0eaa0b13b63e85c1a21afee4ed02f8ad9), так называемый, signcrypt. Чего ни age, ни CMS, ни S/MIME, ни куча других форматов не делают вовсе. Пока достаточно хорошим решением вижу создание, так называемого, binding-а в структуре enveloped-data, являющегося UUID-ом. И при создании вложенного signed-data предполагается указывание envelope-binding внутри подписываемой структуры. Таким образом мы явно связываем подписываемый документ с конкретным будущим envelope. Плюс binding участвует в вычислениях всех KEM-ов, явно привязывая их к конкретной посылке. Сам шифротекст можно вложить и в enveloped-data. И я решил сделать это поле чисто BLOB-ом, размер кусочка которого будет специфичен для каждого DEM-а. Для chacha20poly1305 с 64KiB кусочками это будет 64KiB+32 байта. Однако мои текущие реализации библиотек не вернут мне управление, пока полностью от и до не прочитают весь KEKS файл. А я хочу у себя перешифровать age-файлы на много гигабайт. Никто же не мешает эти данные предоставить отдельно (detached), независимо от структуры, как это можно в EnvelopedData CMS и age? Поэтому ciphertext поле у меня опционально. И мои текущие реализации KEKS библиотек просто декодируют KEKS структуру, а дальше можно будет продолжать обрабатывать данные после неё из io.Reader/файла. На ГОСТовых алгоритмах ещё не делал спецификации, но с CMS на их основе я много работал и не помню чтобы там были не вписывающиеся, во всё мной придуманное, особенности.
Сгенерирован: SGBlog 0.34.0
text/gemini
This content has been proxied by September (ba2dc).