【テクニカル・上級編】 RSA暗号におけるパディング方式(PKCS#1 v1.5 vs OAEP)の選択基準 – 暗号理論・認証基盤 & エンドポイントセキュリティ防御ガイド

RSAパディングの死角:PKCS#1 v1.5からOAEPへ移行すべき本質的理由とBleichenbacherの亡霊

セキュリティアーキテクトやテックリードとしてシステムの暗号設計をレビューする際、いまだに古いコードベースやレガシーな金融・認証プロトコルの中で RSA/ECB/PKCS1Padding という文字列を見かけることがある。動けばいい、あるいは「昔からこれで動いているから」という理由で放置されているのだろうが、これは暗号学的な爆弾を抱えているに等しい。

結論から言おう。現代のシステムにおいて、RSA暗号のパディングに PKCS#1 v1.5 を選択することは、自ら攻撃者にオラクル(神託)を差し出す行為に他ならない。本稿では、なぜ私たちが速やかに RSA-OAEP へ移行しなければならないのか、その数学的背景、Bleichenbacher攻撃のメカニズム、そして現場で実装を誤らないための具体的な処方箋を、インシデントハンドリングの現場視点を交えて徹底的に解説する。

—

1. RSAの数学的素朴さと「パディング」という延命措置

RSA暗号のコアキネティクスは驚くほどシンプルだ。公開鍵 $(e, N)$ と平文 $m$ があるとき、暗号文 $c$ は以下のように計算される。

$$c = m^e \pmod N$$

この数式を見て、暗号に精通した人間なら誰しも違和感を覚えるはずだ。RSAは「準同態性(Homomorphism)」を持つ。つまり、$c_1 = m_1^e \pmod N$ と $c_2 = m_2^e \pmod N$ を掛け合わせると、$c_1 c_2 = (m_1 m_2)^e \pmod N$ となり、暗号文の掛け算がそのまま平文の掛け算に対応してしまう。

さらに、平文 $m$ が小さすぎる場合や、パディング構造を持たない場合、攻撃者は総当たりや代数的なアプローチ(教科書的RSA攻撃)によって容易に平文を復元できてしまう。

この「生(ロー)のRSA」が持つ脆弱性を隠蔽し、暗号学的な安全性を持たせるために考案されたのがパディング(Padding)である。しかし、パディングの設計思想を誤ると、アルゴリズム自体が強固であっても、実装のわずかな隙をついてシステム全体が崩壊する。それが歴史的に証明されてきた。

—

2. PKCS#1 v1.5の構造的欠陥とBleichenbacher攻撃

1998年、ダニエル・ブライヘンバッヘル(Daniel Bleichenbacher)が発表した論文は、TLS(当時SSL 3.0)の実装者たちを震撼させた。これが世に言う Bleichenbacherの攻撃(Adaptive Chosen-Ciphertext Attack) である。

PKCS#1 v1.5 のパディング構造

PKCS#1 v1.5のパディングは、以下のようなバイト列を構成する。

0x00 || 0x02 || PS (ランダムな非ゼロパディングバイト) || 0x00 || データの本体 (DERエンコードされたメッセージ等)

このフォーマットにおいて、復号側(サーバー)の処理手順は厳密でなければならない。
1. 先頭が 0x00 0x02 で始まっているか確認する。
2. 最初の 0x00 区切り文字を探す。
3. その後ろのデータをメッセージとして処理する。

もし攻撃者が巧妙に細工した暗号文 $c’$ を送りつけたとき、サーバーが「パディングエラー(先頭が0x00 0x02でない)」と「それ以外のエラー(メッセージ長や内部構造の不備)」を異なるエラーメッセージやレスポンスタイム、ログ出力として返してしまったとしたらどうなるか。

暗号オラクル(Cryptographic Oracle)の誕生

サーバーがエラーの有無(特にパディングの正当性)を外部に漏らす場合、そのサーバーは「パディングオラクル」として機能してしまう。

攻撃者は、元の暗号文 $c$ に対してランダムな値 $s$ を掛け合わせた $c’ = c \cdot s^e \pmod N$ を大量にサーバーへ送信する。サーバーが返す「パディングが正しいか否か」の二値(Yes/No)のフィードバックを手がかりに、攻撃者は数学的絞り込み(モンテカルロ法ベースの検索)を行い、たった数十万回のクエリで秘密鍵を持たずに平文 $m$ を完全に復元してしまうのだ。

この攻撃は、プロトコルの仕様そのものの欠陥というよりも、「エラーハンドリングの不備(サイドチャネル情報漏洩)」と「パディングの構造的曖昧さ」が組み合わさることで発生する。実際、2017年にはこの変種である ROBOT (Return of Bleichenbacher’s Oracle Threat) が数多くの大手WebサービスやTLS機器で発見され、業界を揺るがせたことは記憶に新しい。

—

3. 現代の標準:RSA-OAEPによる構造的防御

PKCS#1 v1.5の脆弱性を根本から解決するために設計されたのが、PKCS#1 v2.1で導入された OAEP (Optimal Asymmetric Encryption Padding) である。

OAEPの仕組み

OAEPは、Feistelネットワークの構造を応用し、暗号化のプロセスに暗号学的ハッシュ関数(通常SHA-256など)とMGF1(Mask Generation Function 1)を組み込んでいる。

+----------+---------+
       | 平文 M   | パ딩(0) |
       +----------+---------+
            |        |
            v        v
        [R] <---- XOR ---- [M || Pad]
         |                 |
         |                 v
         |              MGF1(R)
         |                 |
         v                 v
      [XOR] <------------+
         |
         v
       パディング済みデータ (EM)

OAEPの最大の強みは以下の点にある。
1. 非決定論的(ランダム化): 同じ平文を暗号化しても、内部のランダム値(Salt / Seed)により毎回まったく異なる暗号文が生成される。これにより、暗号文の比較による推測(Chosen-Ciphertext Attack)が不可能になる。
2. 完全なパディング検証: 復号時にパディング構造のどこか一箇所でも改ざんや不整合があれば、データ全体が完全にランダムなノイズへと崩壊する。これにより、攻撃者に有益な「どの部分が間違っているか」というエラー情報を一切与えない。

結果として、Bleichenbacher型のアダプティブ暗号文攻撃は数学的に完全に封じ込められる。

—

4. 実装の現場:コードで見る安全な使い分け

セキュリティアーキテクトとして監査を行う際、開発者がJavaやPython、Node.jsなどのモダンな言語でどのように暗号化を実装しているかをチェックするのは必須の作業だ。

以下に、不安全な実装(PKCS#1 v1.5)と、推奨される安全な実装(OAEP)の対比を示す。

Python (cryptographyライブラリ) による実装例

現代のPythonにおける暗号処理の事実上の標準である cryptography ライブラリを用いた例を確認する。

from cryptography.hazmat.primitives import hashes
from cryptography.hazmat.primitives.asymmetric import padding
from cryptography.hazmat.primitives.asymmetric import rsa

# 秘密鍵・公開鍵の生成(実運用では安全なストレージから読み込む)
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048
)
public_key = private_key.public_key()

message = b"TopSecretData: 202X-Incident-Report"

# ==========================================
# 【危険】レガシーなPKCS#1 v1.5 パディング(使用非推奨)
# ==========================================
# Bleichenbacher攻撃の標的になるリスクがあるため、新規開発では絶対に避けること
legacy_ciphertext = public_key.encrypt(
    message,
    padding.PKCS1v15()
)

# ==========================================
# 【推奨】現代の標準:RSA-OAEP パディング
# ==========================================
# SHA-256とMGF1を組み合わせた強固なパディング方式
secure_ciphertext = public_key.encrypt(
    message,
    padding.OAEP(
        mgf=padding.MGF1(algorithm=hashes.SHA256()),
        algorithm=hashes.SHA256(),
        label=None
    )
)

# 復号処理(OAEP)
# 復号失敗時は例外(UnsupportedAlgorithmやValueError等)を適切にキャッチし、
# 攻撃者にヒントを与えない汎用的なエラーメッセージを返すこと。
try:
    decrypted_message = private_key.decrypt(
        secure_ciphertext,
        padding.OAEP(
            mgf=padding.MGF1(algorithm=hashes.SHA256()),
            algorithm=hashes.SHA256(),
            label=None
        )
    )
    print(f"復号成功: {decrypted_message.decode('utf-8')}")
except Exception as e:
    # ログには詳細を記録するが、クライアントには詳細を返さない
    print("[セキュリティ警告] 復号処理に失敗しました。不正な暗号文の可能性があります。")

Node.js (crypto モジュール) による実装例

Node.jsのバックエンド環境でRSA暗号を扱う場合も同様である。crypto.publicEncrypt を用いる際の設定に注目してほしい。

const crypto = require('crypto');

// キーペアの生成
const { publicKey, privateKey } = crypto.generateKeyPairSync('rsa', {
  modulusLength: 2048,
});

const data = Buffer.from('API-Token-Secret-2024');

// ==========================================
# 【推奨】RSA-OAEP と SHA-256 の指定
// ==========================================
const encrypted = crypto.publicEncrypt(
  {
    key: publicKey,
    // パディング方式にOAEPを指定し、ハッシュアルゴリズムを明示する
    padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
    oaepHash: 'sha256', 
  },
  data
);

try {
  // 復号処理
  const decrypted = crypto.privateDecrypt(
    {
      key: privateKey,
      padding: crypto.constants.RSA_PKCS1_OAEP_PADDING,
      oaepHash: 'sha256',
    },
    encrypted
  );
  
  console.log('復号されたデータ:', decrypted.toString());
} catch (err) {
  // 復号エラー時のサイドチャネル対策(エラー内容をそのままクライアントに返さない)
  console.error('認証または復号プロセスで異常を検知しました');
}

—

5. セキュリティアーキテクトから現場への処方箋

レガシーシステムのコードベースから古いパディングを駆逐し、安全なアーキテクチャへ移行するための実践的なアプローチを最後にまとめる。

1. 暗号資産台帳(Crypto Inventory)の作成:
システム全体で使用されているすべての暗号アルゴリズム、鍵長、パディング方式を洗い出す。ソースコードの静的解析ツール(SemgrepやSonarQubeなど)に PKCS1Padding や RSA_PKCS1_PADDING を検知するカスタムルールを組み込むのが効果的だ。

2. エラーハンドリングの抽象化(Fail-Closed & Generic Error):
パディングエラーや復号エラーが発生した際、APIのレスポンスとして「パディングが不正です」や「秘密鍵の復号に失敗しました」といった情報を返していないか徹底的に監査する。すべてのエラーは一律で 400 Bad Request や 500 Internal Server Error に丸め込み、攻撃者にオラクルとしての情報を一切与えない設計を徹底する。

3. 耐量子暗号(PQC)時代への視野:
RSA-OAEPは現在のベストプラクティスであるが、Shorのアルゴリズムを持つ大規模な量子コンピュータの出現を見据えた場合、RSA暗号そのものが将来的に破棄される運命にある。新規の長寿命なアーキテクチャを設計する際は、ECC(楕円曲線暗号)ベースの鍵共有や、NISTが標準化した耐量子暗号(ML-KEMなど)への移行ロードマップを常に頭の片隅に置いておくべきだ。

セキュリティとは、過去の脆弱性の歴史から学び、細部に神を宿す泥臭いエンジニアリングの積み重ねである。「動いているから触らない」ではなく、「なぜ動いているのか、本当に安全なのか」を問い続けることこそが、私たちプロフェッショナルの責務である。

コメント

タイトルとURLをコピーしました