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 のハッシュ、ソルト長、トレーラー | ハッシュのみ |
| 署名サイズ | モジュラスのサイズ(RSA-2048 では 256 バイト) | 同じ |
| 速度 | RSA の演算は同じ | 同じ |
| JOSE | PS256、PS384、PS512(ソルト = ハッシュ長) | RS256、RS384、RS512 |
| TLS 1.3 | RSA での CertificateVerify に必須(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 からハッシュ長まで | §5.4(h) の DigestInfo の検査 |
安全性の違い
PKCS#1 v1.5 の署名パディングは 1991年にさかのぼります。決定的な方式で、PSS ほど明快な安全性証明を得たことは一度もありませんが、30 年を経た今も、エンコードされたブロック全体を検査する検証者に対する実用的な偽造は存在しません。現実に起きた破綻は、いずれも実装のバグでした。Bleichenbacher が 2006年に示した攻撃は、DigestInfo の後ろに続くバイトを無視するパーサーに対して、公開指数 3 の鍵の署名を偽造するものでした。同じ誤りの変種は、NSS(BERserk、2014年)をはじめとする複数のライブラリで再び見つかっています。RFC 8017 §8.2.2 は、期待されるブロックを再エンコードしてバイト単位で比較することで、この種の問題をまとめて回避しており、FIPS 186-5 §5.4(h) はこれらの検査を明文化しています。
RSASSA-PSS(Bellare と Rogaway が考案し、2002年に PKCS #1 v2.1 で標準化)は、ランダムなソルトをメッセージのハッシュ値とともにハッシュし、その結果を MGF1 でマスクします。安全性はランダムオラクルモデルで RSA 問題にタイトに帰着し、ランダム化されているため、1 つの署名でフォールトやサイドチャネルが生じても漏れる情報が少なくなります。ソルト長を 0 にすると PSS は決定的になりますが、少し異なる仮定のもとで証明は保たれます。
どちらも、PKCS #1 v1.5 の暗号化パディングと混同しないでください。こちらはパディングオラクル攻撃(1998年の Bleichenbacher の攻撃、2017年の ROBOT)によって実際に破られており、TLS 1.3 から削除されました。署名パディングと暗号化パディングは、名前が同じだけの別々の方式です。
相互運用性:ソルト長の落とし穴
PSS では、署名者と検証者がハッシュ、MGF1 のハッシュ、ソルト長という 3 つのパラメーターについて合意している必要があります。JOSE は 3 つすべてを固定しており(PS256 は SHA-256、MGF1-SHA-256、32 バイトのソルトを意味します)、TLS 1.3 はソルト長がハッシュ長と等しいことを要求します。それ以外の場面では確認が必要です。OpenSSL 1.1.1 と 3.0 はデフォルトで最大長のソルトを使って署名していました(SHA-256 を使う RSA-2048 では 222 バイト)。一方、OpenSSL 3.1 以降のデフォルトは auto-digestmax で、ダイジェストと同じサイズのソルトになります。そのため、固定のソルト長を設定した検証者は、もう一方の世代が生成した署名を拒否します。
次のスクリプトを 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 では、パラメーターによって用途を PSS に限定する id-RSASSA-PSS 鍵も定義されています(TLS では rsa_pss_pss)。この鍵は方式をまたいだ使用を防ぎますが、多くのツールや一部のブラウザーではうまく扱えないため、PSS で署名する rsaEncryption 鍵が現実的な選択肢です。WebPKI の証明書の署名はほぼすべてが PKCS#1 v1.5 か ECDSA であり、その理由だけでも検証者は v1.5 に対応しなければなりません。
どちらを選ぶべきか
- TLS 1.3 のハンドシェイク:PSS が必須です。どの RSA 証明書を使っても、ライブラリが自動的に PSS で署名します。
- JWT:すべての利用側が対応していれば
PS256、そうでなければRS256を使います。さらによいのはES256を検討することです(ES256 と RS256 の比較を参照)。 - 自分で発行する X.509 証明書と CRL:閉じたエコシステムで PSS に統一している場合を除き、互換性を最大限に保つため PKCS#1 v1.5(
sha256WithRSAEncryption)を使います。 - RSA 鍵による新しい文書、ファームウェア、API の署名:ソルト長をハッシュ長と等しくした PSS を使います。
- HSM やクラウド KMS との相互運用:どちらの方式にも対応しています(たとえば AWS KMS の
RSASSA_PSS_SHA_256とRSASSA_PKCS1_V1_5_SHA_256)。検証者が想定する方式に合わせてください。 - どちらの方式の検証でも:ライブラリの高水準な検証関数を使います。エンコードされたメッセージを手作業でデコードしてはいけません。
移行時の注意点
v1.5 から PSS への移行には新しい鍵が不要なため、段階的に進めやすくなっています。
- まず検証者を PSS に対応させる:JOSE では、その鍵に
PS256を許可します(1 つの鍵には 1 つのアルゴリズムという原則は変わらないため、同じ鍵素材を新しいkidとalgPS256で公開するか、鍵をローテーションします)。 - パラメーターを明示的に固定する:ハッシュは SHA-256、MGF1 は SHA-256、ソルト長は 32 バイトにします。バージョンによって異なるライブラリのデフォルトに頼るのは避けてください。
- 署名側を切り替え、ソルト長やパディングに言及する検証の失敗がないかを監視します。
- 古い署名の有効期限が切れたら、それらの鍵について v1.5 の受け入れを停止し、v1.5 は証明書や第三者が必要とする場面だけに残します。
- どのみち鍵をローテーションするのであれば、代わりに ECDSA P-256 や Ed25519 を検討してください。どちらも、どの RSA パディングよりもサイズが小さく、署名も高速です。