[インデックス 16390] ファイルの概要
このコミットは、Go言語の標準ライブラリ crypto/rsa パッケージにRSASSA-PSS (Probabilistic Signature Scheme) 署名アルゴリズムの実装を追加するものです。具体的には、以下のファイルが変更されています。
src/pkg/crypto/rsa/pss.go: RSASSA-PSSのエンコーディング (emsaPSSEncode) および検証 (emsaPSSVerify) ロジック、そして署名 (SignPSS) および検証 (VerifyPSS) の高レベルAPIが新規追加されました。src/pkg/crypto/rsa/pss_test.go:pss.goで実装されたRSASSA-PSS機能の単体テストが新規追加されました。これには、PKCS#1 v2.1のテストベクトル (pss-vect.txt.bz2) を用いたゴールデンテストや、OpenSSLとの互換性テスト、様々なソルト長での署名・検証テストが含まれます。src/pkg/crypto/rsa/rsa_test.go: 既存のRSAテストファイルに、big.Intの文字列変換に関する小さな修正が加えられています。これはPSSの実装とは直接関係ありませんが、テストユーティリティの一部として変更されています。src/pkg/crypto/rsa/testdata/pss-vect.txt.bz2: RSASSA-PSSのテストベクトルを含むバイナリファイルが追加されました。これはpss_test.goで利用されます。
コミット
commit 876455f3ba0e3ee66e177cf901ff5ea9c5aa9f07
Author: Nan Deng <monnand@gmail.com>
Date: Thu May 23 11:10:41 2013 -0400
crypto/rsa: implement PSS signatures.
This change contains an implementation of the RSASSA-PSS signature
algorithm described in RFC 3447.
R=agl, agl
CC=gobot, golang-dev, r
https://golang.org/cl/9438043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/876455f3ba0e3ee66e177cf901ff5ea9c5aa9f07
元コミット内容
crypto/rsa: implement PSS signatures.
This change contains an implementation of the RSASSA-PSS signature
algorithm described in RFC 3447.
R=agl, agl
CC=gobot, golang-dev, r
https://golang.org/cl/9438043
変更の背景
このコミットの主な背景は、Go言語の crypto/rsa パッケージに、よりセキュアで推奨されるRSA署名スキームであるRSASSA-PSSを追加することです。
従来のRSA署名スキームであるRSASSA-PKCS1-v1_5(PKCS#1 v1.5パディング)は、長年にわたり広く利用されてきましたが、その構造に起因するいくつかの脆弱性や攻撃手法が指摘されていました。特に、特定の条件下での選択的偽造攻撃(e.g., Bleichenbacher's attack on PKCS#1 v1.5 signatures)のリスクが懸念されていました。
RSASSA-PSSは、PKCS#1 v2.1(RFC 3447で定義)で導入された新しい署名スキームであり、以下の点でPKCS#1 v1.5よりも優れています。
- 確率的性質 (Probabilistic Nature): PSSは、署名生成プロセスにランダムなソルト(salt)値を導入します。これにより、同じメッセージと秘密鍵から生成される署名が毎回異なるものとなり、決定論的な署名スキームに存在する可能性のある攻撃(例えば、署名オラクル攻撃)に対する耐性が向上します。
- より厳密なセキュリティ証明 (Stronger Security Proofs): PSSは、ランダムオラクルモデル(Random Oracle Model, ROM)において、RSA問題の困難性に基づいたより厳密なセキュリティ証明が与えられています。これは、特定の仮定の下で、PSSが既存の攻撃に対して安全であることを数学的に保証するものです。
- パディングの堅牢性 (Robust Padding): PSSのパディングスキームは、PKCS#1 v1.5のそれよりも複雑で、メッセージのハッシュ値、ソルト、および特定の定数からマスク生成関数(MGF1)を用いてマスクを生成し、データブロックに適用します。これにより、パディングの構造を悪用した攻撃が困難になります。
これらの理由から、現代の暗号システムではRSASSA-PSSの使用が強く推奨されています。Go言語の標準ライブラリにこの機能を追加することで、開発者はよりセキュアな署名メカニズムを容易に利用できるようになり、Goアプリケーションの全体的なセキュリティレベルが向上します。
前提知識の解説
このコミットの理解には、以下の暗号技術に関する前提知識が必要です。
1. RSA暗号システム
RSAは、公開鍵暗号方式の一つで、データの暗号化、デジタル署名、鍵交換などに広く用いられます。
- 公開鍵 (Public Key):
(n, e)のペアで構成されます。nは2つの大きな素数pとqの積 (n = p * q)、eは公開指数です。 - 秘密鍵 (Private Key):
(n, d)のペアで構成されます。dは秘密指数で、e * d ≡ 1 (mod φ(n))を満たします(φ(n)はオイラーのトーシェント関数)。 - 暗号化: 平文
MをC = M^e mod nで暗号化します。 - 復号化: 暗号文
CをM = C^d mod nで復号化します。 - デジタル署名: メッセージ
Mのハッシュ値H(M)を秘密鍵で署名します (S = H(M)^d mod n)。検証者は公開鍵を使ってH(M) = S^e mod nを計算し、元のハッシュ値と比較します。
2. デジタル署名
デジタル署名は、メッセージの完全性(改ざんされていないこと)と送信者の認証(誰が送ったか)を保証する技術です。
- 署名生成: 送信者はメッセージのハッシュ値を計算し、それを自身の秘密鍵で暗号化(または署名アルゴリズムに従って変換)して署名を生成します。
- 署名検証: 受信者は、メッセージのハッシュ値を計算し、送信者の公開鍵を使って署名を復号化(または検証アルゴリズムに従って変換)します。両方のハッシュ値が一致すれば、署名は有効と判断されます。
3. パディングスキーム
RSAのようなブロック暗号では、メッセージのサイズが鍵のサイズと異なる場合や、特定の攻撃を防ぐために、暗号化や署名の前にメッセージに特定の構造(パディング)を追加します。
- PKCS#1 v1.5 (RSASSA-PKCS1-v1_5): 広く使われてきたRSA署名パディングスキーム。メッセージのハッシュ値の前に、特定のバイト列(
0x00 0x01 FF...FF 0x00)とハッシュアルゴリズムの識別子を追加します。シンプルですが、前述の通り脆弱性が指摘されています。 - RSASSA-PSS (Probabilistic Signature Scheme): PKCS#1 v2.1で導入された、よりセキュアなパディングスキーム。ランダムなソルト値とマスク生成関数 (MGF) を使用して、署名に確率的な性質を持たせ、セキュリティを強化します。
4. ハッシュ関数
任意の長さの入力データから、固定長の出力(ハッシュ値またはメッセージダイジェスト)を生成する一方向関数です。SHA-1, SHA-256, MD5などが代表的です。デジタル署名では、メッセージ全体ではなくそのハッシュ値に署名することで効率を高めます。
5. マスク生成関数 (MGF1)
MGF1 (Mask Generation Function 1) は、PKCS#1で定義されている関数で、任意の長さのシードから任意の長さのマスクを生成するために使用されます。これは、ハッシュ関数を繰り返し適用することで実現されます。PSSでは、このMGF1がデータブロックの生成に不可欠な役割を果たします。
技術的詳細
RSASSA-PSSは、RFC 3447 (PKCS #1 v2.1) のセクション 9.1 で定義されています。その主要な構成要素は、EMSA-PSSエンコーディング操作とEMSA-PSS検証操作です。
EMSA-PSS エンコーディング操作 (emsaPSSEncode)
この関数は、署名されるメッセージのハッシュ値 mHash と、RSAモジュラスのビット長 emBits、ランダムなソルト salt、および使用するハッシュ関数 hash を入力として受け取り、エンコードされたメッセージ EM を生成します。
- 入力チェック:
mHashの長さがハッシュ関数の出力長hLenと一致するか、emLen(エンコードされたメッセージのバイト長) がhLen + sLen + 2(ソルト長sLenと定数) より小さい場合にエラーを返します。 - M' の生成: 8バイトのゼロプレフィックス、
mHash、saltを連結してM'を生成します。M' = (0x)00 00 00 00 00 00 00 00 || mHash || salt - H の計算:
M'のハッシュ値H = Hash(M')を計算します。 - DB の生成:
emLen - sLen - hLen - 2個のゼロオクテットからなるパディング文字列PSと、0x01、saltを連結してDBを生成します。DB = PS || 0x01 || salt - dbMask の生成と maskedDB:
dbMask = MGF(H, emLen - hLen - 1)を計算し、DBとdbMaskのXORを取ってmaskedDB = DB XOR dbMaskを生成します。ここでMGFはマスク生成関数 (MGF1) です。 - 最上位ビットのクリア:
maskedDBの最上位オクテットの、8 * emLen - emBitsビットをゼロに設定します。これは、RSAモジュラスのビット長に合わせるための処理です。 - EM の生成:
maskedDB、H、およびトレーラーバイト0xBCを連結して、最終的なエンコードされたメッセージEMを生成します。EM = maskedDB || H || 0xBC
EMSA-PSS 検証操作 (emsaPSSVerify)
この関数は、署名されるメッセージのハッシュ値 mHash、エンコードされたメッセージ em、RSAモジュラスのビット長 emBits、ソルト長 sLen、および使用するハッシュ関数 hash を入力として受け取り、署名が有効かどうかを検証します。
- 入力チェック:
mHashの長さ、emLenの長さ、emの最後のバイトが0xBCであるかなどを検証します。 - maskedDB と H の抽出:
emからmaskedDBとHを抽出します。 - 最上位ビットのチェック:
maskedDBの最上位オクテットの、8 * emLen - emBitsビットがすべてゼロであることを確認します。 - dbMask の生成と DB:
dbMask = MGF(H, emLen - hLen - 1)を計算し、maskedDBとdbMaskのXORを取ってDB = maskedDB XOR dbMaskを復元します。 - ソルト長の自動検出 (PSSSaltLengthAuto):
sLenがPSSSaltLengthAutoの場合、DBの構造からソルト長を自動的に検出します。これは、0x01バイトの位置を探すことで行われます。 - DB の構造検証:
DBの先頭のゼロオクテットと0x01バイトが正しい位置にあることを確認します。 - salt の抽出:
DBからsaltを抽出します。 - M' の再生成と H' の計算: エンコーディング時と同様に、8バイトのゼロプレフィックス、
mHash、抽出したsaltを連結してM'を再生成し、そのハッシュ値H' = Hash(M')を計算します。 - H と H' の比較: 抽出した
Hと計算したH'が一致するかどうかを比較します。一致すれば署名は有効です。
SignPSS と VerifyPSS 関数
これらの関数は、上記のEMSA-PSS操作をRSA署名/検証プロセスに統合する高レベルAPIです。
SignPSS: 秘密鍵、ハッシュ関数、メッセージのハッシュ値、およびPSSOptions(ソルト長を指定) を受け取り、RSASSA-PSS署名を生成します。ソルト長はPSSSaltLengthAuto(最大長) またはPSSSaltLengthEqualsHash(ハッシュ長と同じ) に設定できます。VerifyPSS: 公開鍵、ハッシュ関数、メッセージのハッシュ値、署名、およびPSSOptionsを受け取り、RSASSA-PSS署名を検証します。
PSSOptions
PSSOptions 構造体は、RSASSA-PSS署名生成および検証時のソルト長を制御するために導入されました。
SaltLength: ソルトの長さをバイト単位で指定します。PSSSaltLengthAuto: 署名時には可能な限り最大のソルト長を使用し、検証時には自動検出します。PSSSaltLengthEqualsHash: ソルト長をハッシュ関数の出力長と同じにします。
コアとなるコードの変更箇所
このコミットで追加された主要なファイルと関数は以下の通りです。
-
src/pkg/crypto/rsa/pss.go:emsaPSSEncode(mHash []byte, emBits int, salt []byte, hash hash.Hash) ([]byte, error): PSSエンコーディングロジック。emsaPSSVerify(mHash, em []byte, emBits, sLen int, hash hash.Hash) error: PSS検証ロジック。signPSSWithSalt(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed, salt []byte) (s []byte, err error): 指定されたソルトでPSS署名を生成する内部関数。PSSSaltLengthAuto(定数): ソルト長を自動検出または最大長にするための定数。PSSSaltLengthEqualsHash(定数): ソルト長をハッシュ長と同じにするための定数。PSSOptions(構造体): PSS署名/検証オプションを保持。SignPSS(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte, opts *PSSOptions) (s []byte, err error): PSS署名を生成する公開API。VerifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, opts *PSSOptions) error: PSS署名を検証する公開API。verifyPSS(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte, saltLen int) error: 指定されたソルト長でPSS署名を検証する内部関数。
-
src/pkg/crypto/rsa/pss_test.go:TestEMSAPSS(t *testing.T): 基本的なEMSA-PSSエンコーディング/検証のテスト。TestPSSGolden(t *testing.T): PKCS#1 v2.1のテストベクトル (pss-vect.txt.bz2) を使用したゴールデンテスト。TestPSSOpenSSL(t *testing.T): OpenSSLで生成されたPSS署名の検証テスト。TestPSSSigning(t *testing.T): 様々なソルト長設定でのPSS署名と検証の組み合わせテスト。bigFromHex,intFromHex,fromHex(ヘルパー関数): テストデータ処理のためのユーティリティ。
コアとなるコードの解説
emsaPSSEncode と emsaPSSVerify
これらの関数は、RSASSA-PSSの核となるエンコーディングと検証のロジックを実装しています。RFC 3447のセクション 9.1.1 (EMSA-PSS-Encode) と 9.1.2 (EMSA-PSS-Verify) のステップに厳密に従っています。
-
emsaPSSEncode:hLen := hash.Size(): 使用するハッシュ関数の出力長を取得。emLen := (emBits + 7) / 8: RSAモジュラスのビット長emBitsから、エンコードされたメッセージEMのバイト長を計算。db := em[:emLen-sLen-hLen-2+1+sLen]:DB(Data Block) の領域を確保。h := em[emLen-sLen-hLen-2+1+sLen : emLen-1]:H(ハッシュ値) の領域を確保。hash.Write(prefix[:]),hash.Write(mHash),hash.Write(salt):M'を構成する要素をハッシュ関数に書き込み。h = hash.Sum(h[:0]):M'のハッシュ値Hを計算。db[emLen-sLen-hLen-2] = 0x01:DB内の0x01バイトを設定。copy(db[emLen-sLen-hLen-1:], salt):DB内にソルトをコピー。mgf1XOR(db, hash, h):DBにMGF1(H)をXORしてmaskedDBを生成。db[0] &= (0xFF >> uint(8*emLen-emBits)): 最上位オクテットの不要なビットをクリア。em[emLen-1] = 0xBC: トレーラーバイト0xBCを設定。
-
emsaPSSVerify:- エンコーディングの逆操作を行い、
DBを復元し、そこからソルトを抽出します。 - 抽出したソルトと元のメッセージハッシュから
M'を再構成し、そのハッシュ値H'を計算します。 - 最後に、元の署名から抽出した
Hと計算したH'を比較し、一致すれば検証成功とします。 if sLen == PSSSaltLengthAuto: ソルト長が自動検出の場合のロジック。DB内の0x01バイトの位置からソルト長を特定します。
- エンコーディングの逆操作を行い、
SignPSS と VerifyPSS
これらの関数は、emsaPSSEncode と emsaPSSVerify を利用して、RSA秘密鍵/公開鍵を用いた実際の署名と検証を行います。
-
SignPSS:saltLength := opts.saltLength():PSSOptionsからソルト長を取得。PSSSaltLengthAutoやPSSSaltLengthEqualsHashの場合は実際のバイト長に変換。salt := make([]byte, saltLength): 指定された長さのソルトを生成。io.ReadFull(rand, salt): 暗号論的に安全な乱数ジェネレータrandからソルトを生成。emsaPSSEncode(hashed, nBits-1, salt, hash.New()): エンコードされたメッセージEMを生成。decrypt(rand, priv, m): RSA秘密鍵でEMを復号化(RSA署名操作)。copyWithLeftPad(s, c.Bytes()): 署名結果を適切なバイト長にパディング。
-
VerifyPSS:encrypt(new(big.Int), pub, s): RSA公開鍵で署名sを暗号化(RSA検証操作)。emsaPSSVerify(hashed, em, emBits, saltLen, hash.New()): 復号化されたEMを用いてPSS検証を実行。
これらの関数は、Goの crypto/rsa パッケージの既存のRSA暗号プリミティブ (decrypt, encrypt, copyWithLeftPad) と組み合わせて、RSASSA-PSSの完全な実装を提供しています。
関連リンク
- RFC 3447 (PKCS #1 v2.1): RSA Cryptography Standard
- 特にセクション 9.1 "RSASSA-PSS Signature Scheme"
- https://datatracker.ietf.org/doc/html/rfc3447
- Go言語
crypto/rsaパッケージドキュメント:- https://pkg.go.dev/crypto/rsa (コミット当時のバージョンとは異なる可能性がありますが、現在のドキュメントでPSS関連のAPIを確認できます)
- PKCS #1: RSA Laboratoriesによる標準仕様
参考にした情報源リンク
- RFC 3447: https://datatracker.ietf.org/doc/html/rfc3447
- Go言語
crypto/rsaパッケージのソースコード (コミット当時のもの): - PKCS #1 v2.1 Test Vectors:
pss-vect.txt.bz2の元ファイルは、RSA SecurityのFTPサイトから提供されていたものですが、現在は直接アクセスできない可能性があります。しかし、その内容はRFC 3447に準拠しています。ftp://ftp.rsasecurity.com/pub/pkcs/pkcs-1/pkcs-1v2-1-vec.zip(参照元コミットメッセージより)
- RSASSA-PSSに関する一般的な情報源 (例: Wikipedia, Cryptography Stack Exchangeなど)