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

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

このコミットは、Go言語の crypto/openpgp/packet パッケージ内の private_key.go ファイルに対する修正です。このファイルは、OpenPGP (Open Pretty Good Privacy) のプライベートキーパケットの解析と処理に関連するロジックを扱っています。OpenPGPは、データの暗号化、復号、デジタル署名、署名検証を行うための標準であり、電子メールのセキュリティなどで広く利用されています。private_key.go は、特にプライベートキーの整合性を保証するためのチェックサム計算に焦点を当てています。

コミット

このコミットは、OpenPGPプライベートキーのチェックサム計算における誤りを修正するものです。具体的には、RFC 4880のセクション5.5.3で定義されているチェックサムの計算方法を誤って解釈していたために発生したバグを修正しています。元の実装では16ビット値の合計を計算していましたが、正しい実装は8ビット値の16ビット合計を計算するものでした。

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

https://github.com/golang/go/commit/8281f6bd1b02b52c8233fbcc6b54b697896d0155

元コミット内容

commit 8281f6bd1b02b52c8233fbcc6b54b697896d0155
Author: Adam Langley <agl@golang.org>
Date:   Wed Nov 23 09:44:29 2011 -0500

    crypto/openpgp/packet: fix private key checksum
    
    I misinterpreted http://tools.ietf.org/html/rfc4880#section-5.5.3
    and implemented the sum of 16-bit values, rather than the 16-bit sum
    of 8-bit values.
    
    Thanks to Szabolcs Nagy for pointing it out.
    
    R=bradfitz, r, rsc
    CC=golang-dev
    https://golang.org/cl/5372091

変更の背景

この変更の背景には、OpenPGPのプライベートキーパケットの整合性チェックに関する誤解がありました。OpenPGPでは、プライベートキーの機密部分が破損したり改ざんされたりしていないことを確認するために、チェックサム(またはハッシュ)が使用されます。RFC 4880はOpenPGPの仕様を定義しており、そのセクション5.5.3では「Modulo 65536 checksum」と呼ばれる特定のチェックサム計算方法が規定されています。

元の実装では、このチェックサムを計算する際に、入力データを16ビット単位で読み込み、それらの16ビット値の合計を計算していました。しかし、RFCの正しい解釈は、入力データを8ビット(バイト)単位で読み込み、それらの8ビット値(バイト)を16ビットの合計に加算していくというものでした。この誤った解釈により、生成されるチェックサムがRFCの仕様に準拠せず、他のOpenPGP実装との互換性の問題や、プライベートキーの整合性検証の失敗につながる可能性がありました。

Szabolcs Nagy氏がこの誤りを指摘したことで、Adam Langley氏によって修正が行われました。これは、セキュリティ関連のライブラリにおいて、仕様の厳密な遵守がいかに重要であるかを示す典型的な例です。

前提知識の解説

OpenPGP (Open Pretty Good Privacy)

OpenPGPは、データの暗号化、復号、デジタル署名、署名検証のための標準です。RFC 4880によって仕様が定義されており、電子メールのセキュリティ(例: GnuPG)やファイル暗号化など、幅広い用途で利用されています。OpenPGPは公開鍵暗号方式と共通鍵暗号方式を組み合わせて使用し、データの機密性、完全性、認証性、否認防止を提供します。

プライベートキーパケット

OpenPGPのキーは、公開鍵と秘密鍵のペアで構成されます。プライベートキーは、暗号化されたメッセージを復号したり、デジタル署名を作成したりするために使用される非常に機密性の高い情報です。OpenPGPのデータ構造では、キー情報は「パケット」と呼ばれる単位で格納されます。プライベートキーパケットには、キーのアルゴリズム、キーデータ、そしてキーの整合性を検証するためのチェックサムが含まれています。

チェックサム (Checksum)

チェックサムは、データの整合性を検証するために使用される小さなデータブロックです。データを送信または保存する際にチェックサムを計算し、受信または読み出し時に再度計算して比較することで、データが転送中や保存中に破損または改ざんされていないことを検出できます。

RFC 4880 セクション 5.5.3: Modulo 65536 Checksum

RFC 4880のセクション5.5.3では、OpenPGPのプライベートキーパケットの機密部分(S2K (String-to-Key) スペック、暗号化された秘密鍵データ、公開鍵の指紋など)の整合性を検証するために使用される「Modulo 65536 checksum」について記述されています。

このチェックサムは、以下の方法で計算されます。

  1. チェックサムの対象となるすべてのバイトを読み込みます。
  2. 各バイトの値を16ビットの合計に加算していきます。
  3. 合計が65536(2^16)を超えた場合、その合計を65536で割った余り(モジュロ演算)を取ります。これにより、合計は常に0から65535の範囲に収まります。
  4. 最終的な16ビットの合計がチェックサムとなります。

このチェックサムは、プライベートキーの機密部分が正しく復号されたか、または破損していないかを検証するために使用されます。復号された秘密鍵データの最後の2バイトがこのチェックサムと一致しない場合、復号が失敗したか、データが破損していると判断されます。

技術的詳細

このコミットで修正された mod64kHash 関数は、まさにこの「Modulo 65536 checksum」を計算するためのものでした。

修正前の mod64kHash 関数:

func mod64kHash(d []byte) uint16 {
	h := uint16(0)
	for i := 0; i < len(d); i += 2 {
		v := uint16(d[i]) << 8
		if i+1 < len(d) {
			v += uint16(d[i+1])
		}
		h += v
	}
	return h
}

このコードでは、入力バイトスライス d を2バイト(16ビット)ずつ処理しています。

  • uint16(d[i]) << 8 は、現在のバイト d[i] を上位8ビットにシフトし、16ビット値のMSB(最上位ビット)側に配置します。
  • v += uint16(d[i+1]) は、次のバイト d[i+1] を下位8ビットに加算し、2バイトで1つの16ビット値を構成します。
  • そして、この構成された16ビット値 v を合計 h に加算しています。

これは、RFC 4880の「16-bit sum of 8-bit values」ではなく、「sum of 16-bit values」を計算していることになります。つまり、バイト列を16ビットのワード列として扱い、そのワードの合計を計算していました。

修正後の mod64kHash 関数:

func mod64kHash(d []byte) uint16 {
	var h uint16
	for _, b := range d {
		h += uint16(b)
	}
	return h
}

修正後のコードは非常にシンプルです。

  • for _, b := range d ループで、入力バイトスライス d の各バイト b を個別に処理します。
  • h += uint16(b) は、各バイト buint16 型にキャストし、それを直接合計 h に加算します。

これにより、各8ビット値(バイト)が個別に16ビットの合計に加算されるようになり、RFC 4880のセクション5.5.3で定義されている「Modulo 65536 checksum」の正しい計算方法に合致するようになりました。Go言語の uint16 型は自動的にオーバーフロー時にモジュロ65536演算を行うため、明示的な % 65536 演算は不要です。

この修正は、OpenPGPのプライベートキーの復号と検証の互換性および信頼性を確保するために不可欠でした。

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

--- a/src/pkg/crypto/openpgp/packet/private_key.go
+++ b/src/pkg/crypto/openpgp/packet/private_key.go
@@ -99,13 +99,9 @@ func (pk *PrivateKey) parse(r io.Reader) (err error) {\n }\n \n func mod64kHash(d []byte) uint16 {\n-\th := uint16(0)\n-\tfor i := 0; i < len(d); i += 2 {\n-\t\tv := uint16(d[i]) << 8\n-\t\tif i+1 < len(d) {\n-\t\t\tv += uint16(d[i+1])\n-\t\t}\n-\t\th += v\n+\tvar h uint16\n+\tfor _, b := range d {\n+\t\th += uint16(b)\n \t}\n \treturn h
 }\n```

## コアとなるコードの解説

変更されたのは `mod64kHash` 関数のみです。

**変更前:**
この関数は、入力バイトスライス `d` を2バイトずつ処理し、それぞれの2バイトを16ビットの数値として解釈し、その16ビット数値の合計を計算していました。これは、バイト列を16ビットワードのシーケンスとして扱うことを意味します。例えば、バイト列 `[0x01, 0x02, 0x03, 0x04]` が与えられた場合、`0x0102` と `0x0304` という2つの16ビット値として解釈し、それらを合計していました。

**変更後:**
この関数は、入力バイトスライス `d` の各バイトを個別に `uint16` 型に変換し、それらを単純に合計していきます。例えば、バイト列 `[0x01, 0x02, 0x03, 0x04]` が与えられた場合、`0x01 + 0x02 + 0x03 + 0x04` というように、各バイトの値を合計します。Go言語の `uint16` 型の加算は、オーバーフロー時に自動的にモジュロ65536演算を行うため、最終的な合計は常に16ビットの範囲に収まります。

この修正により、`mod64kHash` 関数はRFC 4880で規定されている「Modulo 65536 checksum」の正しい計算ロジックに準拠するようになりました。これは、プライベートキーの整合性検証が正しく機能するために不可欠な変更です。

## 関連リンク

*   **RFC 4880 - OpenPGP Message Format**:
    *   セクション 5.5.3. Private-Key Packet Checksum: [https://tools.ietf.org/html/rfc4880#section-5.5.3](https://tools.ietf.org/html/rfc4880#section-5.5.3)
*   **Go CL (Change List)**:
    *   https://golang.org/cl/5372091

## 参考にした情報源リンク

*   RFC 4880 - OpenPGP Message Format (上記に記載)
*   Go言語の `uint16` 型の挙動に関する一般的な知識
*   OpenPGPのチェックサムに関する一般的な情報源 (例: GnuPGのドキュメントなど)
*   Go言語の `crypto/openpgp` パッケージのソースコード (変更前後の比較)
*   Szabolcs Nagy氏による指摘に関する情報 (コミットメッセージから推測)
*   Adam Langley氏のブログや関連するセキュリティ記事 (OpenPGPや暗号技術に関する背景知識の補強)
*   Go言語のコミット履歴と開発プロセスに関する情報