Выбор между RSA-PSS и PKCS#1 v1.5 — это выбор того, как кодируется хеш перед операцией RSA с закрытым ключом. Обе схемы описаны в PKCS #1 (RFC 8017), обе одобрены в FIPS 186-5 и обе работают с одной и той же парой ключей RSA. Различаются они обоснованием стойкости, детерминированностью и параметрами, которые должны совпадать у подписанта и проверяющей стороны.
Коротко о главном
| RSASSA-PSS | RSASSA-PKCS1-v1_5 | |
|---|---|---|
| Спецификация | RFC 8017, § 8.1, кодирование EMSA-PSS, § 9.1 | RFC 8017, § 8.2, кодирование EMSA-PKCS1-v1_5, § 9.2 |
| Кодирование | Хеш с солью, маскированный с помощью MGF1 | 0x00 01 FF…FF 00 ‖ DigestInfo |
| Рандомизация | Да (если длина соли не равна 0) | Нет: один и тот же ключ и одно и то же сообщение дают одну и ту же подпись |
| Доказательство стойкости | Точная редукция к задаче RSA в модели случайного оракула | Сопоставимого доказательства нет; практических атак на корректную проверку нет |
| Параметры, которые нужно согласовать | Хеш, хеш для MGF, длина соли, поле trailer | Только хеш |
| Размер подписи | Размер модуля (256 байт для RSA-2048) | Такой же |
| Скорость | Та же операция RSA | Такая же |
| JOSE | PS256, PS384, PS512 (длина соли = длина хеша) | RS256, RS384, RS512 |
| TLS 1.3 | Требуется для CertificateVerify с RSA (rsa_pss_rsae_sha256 0x0804, rsa_pss_pss_sha256 0x0809) | Только для подписей внутри сертификатов (rsa_pkcs1_sha256 0x0401) |
| X.509 | id-RSASSA-PSS 1.2.840.113549.1.1.10 с параметрами | sha256WithRSAEncryption 1.2.840.113549.1.1.11 |
| Ограничение FIPS 186-5 | Длина соли от 0 до длины хеша | Проверки DigestInfo в § 5.4(h) |
Различия в стойкости
Дополнение подписей PKCS#1 v1.5 появилось в 1991 году. Оно детерминировано, и у него никогда не было такого же чистого доказательства стойкости, как у PSS, но и спустя три десятилетия нет практического способа подделать подпись так, чтобы её приняла проверка, сверяющая весь закодированный блок. Реальные взломы были ошибками реализации: атака Блейхенбахера 2006 года позволяла подделывать подписи для открытой экспоненты 3, если парсер игнорировал лишние байты после DigestInfo, а разновидности этой ошибки потом снова обнаруживались в библиотеках, например в NSS (BERserk, 2014), и в других. RFC 8017, § 8.2.2, исключает весь этот класс ошибок: ожидаемый блок кодируется заново и сравнивается побайтно, а FIPS 186-5, § 5.4(h), закрепляет эти проверки как требования.
RSASSA-PSS (Белларе и Рогуэй; стандартизирована в PKCS #1 v2.1 в 2002 году) хеширует случайную соль вместе с хешем сообщения и маскирует результат с помощью MGF1. Её стойкость точно сводится к задаче RSA в модели случайного оракула, а благодаря рандомизации сбой или утечка по побочному каналу при вычислении одной подписи раскрывает меньше. С солью нулевой длины PSS становится детерминированной, но сохраняет доказательство при несколько иных допущениях.
Не путайте ни одну из них с дополнением PKCS #1 v1.5 для шифрования: оно действительно взломано атаками на оракул дополнения (padding oracle: атака Блейхенбахера 1998 года, ROBOT 2017 года) и удалено из TLS 1.3. Дополнение для подписи и дополнение для шифрования — разные схемы с общим названием.
Совместимость: ловушка с длиной соли
Для PSS подписант и проверяющая сторона должны договориться о трёх параметрах: хеше, хеше для MGF1 и длине соли. JOSE фиксирует все три (PS256 означает SHA-256, MGF1-SHA-256 и соль длиной 32 байта), а TLS 1.3 требует, чтобы длина соли была равна длине хеша. В остальных случаях это нужно проверять: OpenSSL 1.1.1 и 3.0 по умолчанию подписывали с максимальной длиной соли (222 байта для RSA-2048 с SHA-256), а OpenSSL 3.1 и новее по умолчанию используют auto-digestmax, что даёт соль размером с дайджест. Поэтому проверяющая сторона, настроенная на фиксированную длину соли, отвергает подписи, созданные другим поколением OpenSSL.
Скрипт ниже, запущенный с OpenSSL 3.6.5, демонстрирует обе особенности.
#!/bin/sh
dir=$(mktemp -d)
cd "$dir" || exit 1
openssl genpkey -algorithm RSA -pkeyopt rsa_keygen_bits:2048 -out key.pem 2>/dev/null
openssl pkey -in key.pem -pubout -out pub.pem
printf 'hello' > msg
openssl dgst -sha256 -sign key.pem -out v15a.sig msg
openssl dgst -sha256 -sign key.pem -out v15b.sig msg
cmp -s v15a.sig v15b.sig && echo "PKCS#1 v1.5: identical signatures" || echo "PKCS#1 v1.5: different signatures"
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssa.sig msg
openssl dgst -sha256 -sign key.pem -sigopt rsa_padding_mode:pss -out pssb.sig msg
cmp -s pssa.sig pssb.sig && echo "PSS: identical signatures" || echo "PSS: different signatures"
for saltlen in digest max; do
printf 'verify PSS with rsa_pss_saltlen:%s -> ' "$saltlen"
openssl dgst -sha256 -verify pub.pem -sigopt rsa_padding_mode:pss \
-sigopt "rsa_pss_saltlen:$saltlen" -signature pssa.sig msg 2>&1 | grep -o -E "Verified OK|salt length check failed"
done
cd / && rm -rf "$dir"
PKCS#1 v1.5: identical signatures
PSS: different signatures
verify PSS with rsa_pss_saltlen:digest -> Verified OK
verify PSS with rsa_pss_saltlen:max -> salt length check failed
Проверяющая сторона с настройкой auto определяет длину соли по самой подписи — это самый совместимый вариант, если подписант вам неподконтролен.
Ключи и сертификаты
Один и тот же ключ RSA может создавать подписи обоих видов, а ключ rsaEncryption в сертификате годится для обоих (в TLS это называется rsa_pss_rsae). В X.509 есть также ключи id-RSASSA-PSS, параметры которых допускают только схему PSS (rsa_pss_pss в TLS); такие ключи нельзя использовать в другой схеме, но многие инструменты и некоторые браузеры плохо с ними работают, поэтому на практике выбирают ключи rsaEncryption, которые подписывают по PSS. Почти все подписи сертификатов в WebPKI — это PKCS#1 v1.5 или ECDSA, и уже поэтому проверяющие стороны обязаны поддерживать v1.5.
Что когда выбирать
- Рукопожатия TLS 1.3: PSS обязательна; ваша библиотека автоматически использует её с любым сертификатом RSA.
- JWT:
PS256, если его поддерживают все получатели токенов, иначеRS256; а ещё лучше рассмотритеES256(см. ES256 или RS256). - Сертификаты X.509 и CRL, которые вы выпускаете: PKCS#1 v1.5 (
sha256WithRSAEncryption) ради максимальной совместимости, если только в закрытой экосистеме стандартом не принята PSS. - Новые подписи документов, прошивок или API с ключами RSA: PSS с длиной соли, равной длине хеша.
- Совместимость с HSM и облачными KMS: поддерживаются обе схемы (например,
RSASSA_PSS_SHA_256иRSASSA_PKCS1_V1_5_SHA_256в AWS KMS); ориентируйтесь на то, что поддерживает проверяющая сторона. - Проверка подписей любого вида: используйте высокоуровневую функцию проверки из библиотеки и никогда не полагайтесь на ручное декодирование закодированного сообщения.
Заметки о миграции
Для перехода с v1.5 на PSS новые ключи не нужны, поэтому его легко провести поэтапно.
- Сначала научите проверяющие стороны работать с PSS: в JOSE разрешите для ключа
PS256(правило «один алгоритм на ключ» по-прежнему действует, поэтому опубликуйте тот же ключевой материал под новымkidсalg, равнымPS256, или проведите ротацию). - Задайте параметры явно: хеш SHA-256, MGF1 SHA-256, длина соли 32 байта; не полагайтесь на значения по умолчанию в библиотеках, которые меняются от версии к версии.
- Переключите подписантов и следите за ошибками проверки, в которых упоминается длина соли или дополнение.
- Перестаньте принимать v1.5 для этих ключей, когда истечёт срок действия старых подписей, и оставьте v1.5 только там, где этого требуют сертификаты или третьи стороны.
- Если вы всё равно проводите ротацию ключей, рассмотрите вместо RSA ECDSA P-256 или Ed25519: они компактнее и подписывают быстрее, чем RSA с любым дополнением.