Keyboard shortcuts

Press or to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

[インデックス 18178] ファイルの概要

このコミットは、Go言語のcrypto/rsaパッケージにおいて、パディングなしのRSA署名(unpadded signatures)をサポートするための変更を導入しています。通常、RSA署名を行う際には、メッセージをハッシュ化し、そのハッシュ値にパディングを施してから署名しますが、特定のプロトコルやレガシーシステムとの相互運用性のために、短いメッセージを直接(ハッシュ化やパディングなしで)署名するニーズが存在します。このコミットは、そのような特殊なケースに対応するための機能追加です。

コミット

commit 78c16c9b16dc9c64d1ddad6db5afaab12e87e8f2
Author: Adam Langley <agl@golang.org>
Date:   Mon Jan 6 16:11:58 2014 -0500

    crypto/rsa: support unpadded signatures.
    
    Usually when a message is signed it's first hashed because RSA has low
    limits on the size of messages that it can sign. However, some
    protocols sign short messages directly. This isn't a great idea because
    the messages that can be signed suddenly depend on the size of the RSA
    key, but several people on golang-nuts have requested support for
    this and it's very easy to do.
    
    R=golang-codereviews, rsc
    CC=golang-codereviews
    https://golang.org/cl/44400043

GitHub上でのコミットページへのリンク

https://github.com/golang/go/commit/78c16c9b16dc9c64d1ddad6db5afaab12e87e8f2

元コミット内容

crypto/rsa: パディングなし署名をサポート。

通常、メッセージはRSAが署名できるメッセージサイズに低い制限があるため、最初にハッシュ化されてから署名されます。しかし、一部のプロトコルでは短いメッセージを直接署名します。これは、署名できるメッセージがRSA鍵のサイズに突然依存するようになるため、あまり良いアイデアではありませんが、golang-nutsの何人かの人々がこのサポートを要求しており、実装は非常に簡単です。

変更の背景

RSA署名では、署名対象のメッセージが鍵のモジュラスサイズよりも小さい必要があります。また、セキュリティ上の理由から、メッセージを直接署名するのではなく、メッセージのハッシュ値を計算し、そのハッシュ値にPKCS#1 v1.5などのパディングスキームを適用してから署名するのが一般的です。これにより、メッセージの長さに関わらず固定長のデータ(ハッシュ値)を署名でき、また、選択平文攻撃(Chosen-plaintext attack)などに対する耐性を高めることができます。

しかし、コミットメッセージにもあるように、一部のレガシーなプロトコルや特定の相互運用性の要件により、メッセージをハッシュ化せずに直接署名する必要があるケースが存在します。これは、セキュリティ上のベストプラクティスとは言えませんが、現実世界のシステムではこのような要件に直面することがあります。

Go言語のコミュニティフォーラムであるgolang-nutsで、この「パディングなし署名」のサポートが複数回要望されたことが、この機能追加の直接的な動機となりました。開発者(Adam Langley)は、実装が比較的容易であると判断し、この機能を追加することにしました。これにより、Goのcrypto/rsaパッケージがより幅広いユースケースに対応できるようになります。

前提知識の解説

RSA署名

RSA署名は、公開鍵暗号方式の一つであるRSAアルゴリズムを応用したデジタル署名方式です。

  1. 鍵ペアの生成: 秘密鍵と公開鍵のペアを生成します。秘密鍵は署名者が秘密にし、公開鍵は署名を検証するすべての人が利用できるように公開します。
  2. 署名: 署名者は、メッセージのハッシュ値を秘密鍵で暗号化(実際には数学的な変換)して署名を生成します。
  3. 検証: 検証者は、署名者の公開鍵を使って署名を復号し、得られた値がメッセージのハッシュ値と一致するかを確認します。一致すれば、メッセージが改ざんされておらず、署名者によって署名されたことが保証されます。

ハッシュ関数とデジタル署名における役割

ハッシュ関数は、任意の長さの入力データ(メッセージ)から、固定長の短い出力(ハッシュ値、メッセージダイジェスト)を生成する一方向性の関数です。デジタル署名においてハッシュ関数が重要な役割を果たす理由は以下の通りです。

  • 効率性: 長いメッセージ全体を暗号化する代わりに、短いハッシュ値を暗号化するだけで済むため、署名と検証の処理が高速になります。
  • 完全性: メッセージが少しでも変更されると、ハッシュ値が大きく変化するため、メッセージの改ざんを容易に検出できます。
  • セキュリティ: ハッシュ関数は一方向性であるため、ハッシュ値から元のメッセージを復元することは困難です。

PKCS#1 v1.5 パディング

PKCS#1 v1.5は、RSA暗号および署名のための標準的なパディングスキームです。署名の場合、メッセージのハッシュ値に特定の構造を持つパディングデータを付加し、その結果をRSAアルゴリズムで署名します。このパディングは、署名のセキュリティを強化し、特定の攻撃(例: 選択平文攻撃)を防ぐために不可欠です。

PKCS#1 v1.5署名スキームでは、署名されるデータは以下の構造を持ちます。 00 || 01 || PS || 00 || T ここで、

  • 00 || 01 || PS || 00: パディング部分。PSは、鍵のモジュラス長に応じて可変長のFFバイトの列です。
  • T: ハッシュアルゴリズムの識別子(ASN.1構造)とメッセージのハッシュ値を含む構造体です。

パディングなし署名 (Unpadded Signatures)

通常、RSA署名ではメッセージのハッシュ値にパディングを施しますが、「パディングなし署名」とは、このパディング処理を行わずに、メッセージ(またはそのハッシュ値)を直接RSAアルゴリズムで署名することを指します。コミットメッセージにあるように、これはセキュリティ上の推奨事項ではありません。なぜなら、パディングがないと、署名されるメッセージのサイズがRSA鍵のサイズに厳密に依存し、また、特定の攻撃に対して脆弱になる可能性があるためです。しかし、一部のレガシーシステムや特殊なプロトコルでは、このような形式の署名が要求されることがあります。

技術的詳細

このコミットの主要な変更点は、crypto/rsaパッケージのSignPKCS1v15関数とVerifyPKCS1v15関数が、crypto.Hash(0)という特別なハッシュ値を受け入れるように拡張されたことです。通常、これらの関数はcrypto.SHA256crypto.SHA1のような有効なハッシュアルゴリズムの識別子を受け取りますが、crypto.Hash(0)が渡された場合、それは「ハッシュ化されていない(またはパディングされていない)メッセージを直接署名/検証する」という指示として解釈されます。

具体的には、以下の変更が行われました。

  1. SignPKCS1v15関数の変更:

    • 関数のコメントが更新され、hashがゼロの場合にhashedが直接署名されることが明記されました。
    • pkcs1v15HashInfo関数への呼び出しはそのままですが、pkcs1v15HashInfo内部でhash == 0のケースが特別に処理されます。
  2. VerifyPKCS1v15関数の変更:

    • 同様に、関数のコメントが更新され、hashがゼロの場合にhashedが直接使用されることが明記されました。
    • pkcs1v15HashInfo関数への呼び出しはそのままですが、pkcs1v15HashInfo内部でhash == 0のケースが特別に処理されます。
  3. pkcs1v15HashInfo関数の変更:

    • この関数は、与えられたハッシュアルゴリズムに基づいて、ハッシュ値の長さと、PKCS#1 v1.5パディングで使用されるハッシュアルゴリズムの識別子(プレフィックス)を返します。
    • このコミットで、hash == 0の場合の特別な処理が追加されました。この場合、hashLenは入力データの長さ(inLen)となり、prefixnil(プレフィックスなし)が返されます。これは、データがハッシュ化されず、パディングも適用されないことを意味します。
    • これにより、SignPKCS1v15VerifyPKCS1v15は、hash == 0の場合に、入力されたhashedデータをそのまま(パディングなしで)処理できるようになります。
  4. テストケースの追加:

    • pkcs1v15_test.goTestUnpaddedSignatureという新しいテスト関数が追加されました。
    • このテストは、OpenSSLで生成されたパディングなしのRSA署名とGoのSignPKCS1v15関数で生成された署名を比較し、Goの実装が正しく機能することを確認します。
    • SignPKCS1v15VerifyPKCS1v15crypto.Hash(0)とメッセージのバイト列を直接渡して呼び出し、期待される署名値と一致するか、および検証が成功するかを確認しています。

この変更により、Goのcrypto/rsaパッケージは、標準的なPKCS#1 v1.5署名だけでなく、特定の相互運用性要件を満たすためのパディングなし署名もサポートするようになりました。ただし、コミットメッセージにもあるように、パディングなし署名はセキュリティ上の推奨事項ではないため、使用には注意が必要です。

コアとなるコードの変更箇所

変更されたファイルは以下の2つです。

  1. src/pkg/crypto/rsa/pkcs1v15.go: RSA PKCS#1 v1.5署名および検証の主要なロジックが含まれるファイル。
    • SignPKCS1v15関数のコメントが変更されました。
    • VerifyPKCS1v15関数のコメントが変更されました。
    • pkcs1v15HashInfo関数にcrypto.Hash(0)を処理するロジックが追加されました。
  2. src/pkg/crypto/rsa/pkcs1v15_test.go: pkcs1v15.goのテストファイル。
    • TestUnpaddedSignatureという新しいテスト関数が追加されました。

コアとなるコードの解説

src/pkg/crypto/rsa/pkcs1v15.go

 // SignPKCS1v15 calculates the signature of hashed using RSASSA-PKCS1-V1_5-SIGN from RSA PKCS#1 v1.5.
 // Note that hashed must be the result of hashing the input message using the
-// given hash function.
+// given hash function. If hash is zero, hashed is signed directly. This isn't
+// advisable except for interoperability.
 func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []byte) (s []byte, err error) {
 	hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
 	if err != nil {
@@ -212,7 +213,8 @@ func SignPKCS1v15(rand io.Reader, priv *PrivateKey, hash crypto.Hash, hashed []b
 // VerifyPKCS1v15 verifies an RSA PKCS#1 v1.5 signature.
 // hashed is the result of hashing the input message using the given hash
 // function and sig is the signature. A valid signature is indicated by
-// returning a nil error.
+// returning a nil error. If hash is zero then hashed is used directly. This
+// isn't advisable except for interopability.
 func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte) (err error) {
 	hashLen, prefix, err := pkcs1v15HashInfo(hash, len(hashed))
 	if err != nil {
@@ -249,6 +251,12 @@ func VerifyPKCS1v15(pub *PublicKey, hash crypto.Hash, hashed []byte, sig []byte)
 }
 
 func pkcs1v15HashInfo(hash crypto.Hash, inLen int) (hashLen int, prefix []byte, err error) {
+	// Special case: crypto.Hash(0) is used to indicate that the data is
+	// signed directly.
+	if hash == 0 {
+		return inLen, nil, nil
+	}
+
 	hashLen = hash.Size()
 	if inLen != hashLen {
 		return 0, nil, errors.New("crypto/rsa: input must be hashed message")
  • SignPKCS1v15VerifyPKCS1v15のコメント変更:

    • これらの関数のドキュメンテーションが更新され、hash引数にcrypto.Hash(0)が渡された場合の挙動が明記されました。これは、hashed引数が直接署名/検証されることを意味します。また、この方法が相互運用性のため以外には推奨されない("This isn't advisable except for interoperability.")という重要な警告も追加されています。
  • pkcs1v15HashInfo関数の変更:

    • この関数は、署名/検証プロセスで必要となるハッシュ値の長さ(hashLen)と、PKCS#1 v1.5パディングで使用されるハッシュアルゴリズムの識別子(prefix)を決定します。
    • 追加されたif hash == 0ブロックがこのコミットの核心です。
      • hash0の場合、これは特別なケースとして扱われ、データが直接署名されることを示します。
      • hashLenは入力データの長さinLenに設定されます。これは、ハッシュ化されていないデータ全体が署名対象となるためです。
      • prefixnilに設定されます。これは、ハッシュアルゴリズムの識別子(ASN.1構造)がパディングに含まれないことを意味します。
      • エラーは返されません。
    • この変更により、SignPKCS1v15VerifyPKCS1v15は、hash0の場合に、パディングなしで署名/検証を実行するための正しい情報(ハッシュ長とプレフィックスなし)を受け取ることができます。

src/pkg/crypto/rsa/pkcs1v15_test.go

 func TestUnpaddedSignature(t *testing.T) {
 	msg := []byte("Thu Dec 19 18:06:16 EST 2013\n")
 	// This base64 value was generated with:
 	// % echo Thu Dec 19 18:06:16 EST 2013 > /tmp/msg
 	// % openssl rsautl -sign -inkey key -out /tmp/sig -in /tmp/msg
 	//
 	// Where "key" contains the RSA private key given at the bottom of this
 	// file.
 	expectedSig := decodeBase64("pX4DR8azytjdQ1rtUiC040FjkepuQut5q2ZFX1pTjBrOVKNjgsCDyiJDGZTCNoh9qpXYbhl7iEym30BWWwuiZg==")
 
 	sig, err := SignPKCS1v15(nil, rsaPrivateKey, crypto.Hash(0), msg)
 	if err != nil {
 		t.Fatalf("SignPKCS1v15 failed: %s", err)
 	}
 	if !bytes.Equal(sig, expectedSig) {
 		t.Fatalf("signature is not expected value: got %x, want %x", sig, expectedSig)
 	}
 	if err := VerifyPKCS1v15(&rsaPrivateKey.PublicKey, crypto.Hash(0), msg, sig); err != nil {
 		t.Fatalf("signature failed to verify: %s", err)
 	}
 }
  • TestUnpaddedSignature関数の追加:
    • このテストは、パディングなし署名機能の正確性を検証するために追加されました。
    • msg変数には、署名される短いメッセージ(バイト列)が定義されています。
    • expectedSig変数には、OpenSSLツールを使用して事前に生成された、このメッセージに対するパディングなしRSA署名のBase64エンコード値が格納されています。これは、Goの実装が外部ツールと相互運用できることを確認するための重要なステップです。
    • SignPKCS1v15関数がcrypto.Hash(0)msgを直接渡して呼び出され、生成された署名がexpectedSigと一致するかどうかが検証されます。
    • 最後に、VerifyPKCS1v15関数がcrypto.Hash(0)msg、そして生成された署名を渡して呼び出され、署名が正しく検証されるかどうかが確認されます。これにより、署名と検証の両方のパスが正しく機能することが保証されます。

これらの変更により、Goのcrypto/rsaパッケージは、パディングなしのRSA署名という特殊な要件に対応できるようになり、特定の相互運用性のシナリオでGoを使用する開発者にとって有用な機能が追加されました。

関連リンク

参考にした情報源リンク