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

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

このコミットは、Go言語の標準ライブラリ crypto/rc4 パッケージにおける変更です。RC4暗号の純粋なGo言語実装(ポータブル版)が、アセンブリ最適化されたバージョンと並行して適切にテストされるように修正されました。これにより、ポータブル版のコードが時間とともに劣化(ビットロット)するのを防ぎ、その正確性と信頼性を維持することが目的です。

コミット

commit a7fb31833b6c2c39e3f3dc5ab0bc2e1abcaf8ef6
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Tue Feb 18 15:16:07 2014 -0800

    crypto/rc4: test the portable version too
    
    Prevent bitrot. (similar to the previous sha1 and md5 CLs)
    
    Fixes #6642
    
    LGTM=agl
    R=agl, dave
    CC=golang-codereviews
    https://golang.org/cl/65690043

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

https://github.com/golang/go/commit/a7fb31833b6c2c39e3f3dc5ab0bc2e1abcaf8ef6

元コミット内容

crypto/rc4: test the portable version too

このコミットは、crypto/rc4 パッケージにおいて、ポータブル(純粋なGo言語)バージョンのRC4実装もテスト対象に含めるように変更します。これは「ビットロット」を防ぐためであり、以前の sha1 および md5 パッケージに対する同様の変更に倣っています。

変更の背景

Go言語の標準ライブラリには、パフォーマンスが重要な暗号化アルゴリズムなどにおいて、特定アーキテクチャ向けに最適化されたアセンブリ実装が提供されることがあります。RC4暗号の XORKeyStream 関数もその一つで、amd64, 386, arm などのアーキテクチャ向けにアセンブリコードが用意されていました。

しかし、アセンブリ実装が存在する場合でも、純粋なGo言語で書かれた「ポータブル版」または「リファレンス版」の実装も通常は存在します。これは、アセンブリ実装が利用できない環境(例えば、サポートされていないCPUアーキテクチャ)でのフォールバックとして機能したり、コードの可読性や保守性の観点から重要であったりします。

問題は、アセンブリ実装が主に使われるようになると、ポータブル版のコードが実際に実行される機会が減り、テストも不十分になる可能性があることです。これにより、ポータブル版のコードが知らぬ間にバグを含んだり、Go言語のバージョンアップやコンパイラの変更によって動作しなくなったりする「ビットロット(bitrot)」と呼ばれる現象が発生するリスクがあります。ビットロットとは、ソフトウェアが時間の経過とともに劣化し、機能しなくなる現象を指します。

このコミットの背景には、sha1md5 といった他の暗号パッケージでも同様の問題が認識され、ポータブル版のテストを強化する変更が先行して行われていたことがあります。RC4についても同様の対策を講じることで、ライブラリ全体の堅牢性を高めることが目的です。

前提知識の解説

RC4 (Rivest Cipher 4)

RC4は、ストリーム暗号の一種で、Ron Rivestによって開発されました。ストリーム暗号は、データをビット単位またはバイト単位で暗号化・復号化する方式です。RC4は、鍵スケジュールアルゴリズム(KSA)と擬似乱数生成アルゴリズム(PRGA)の2つのフェーズで動作します。

  1. 鍵スケジュールアルゴリズム (KSA): 256バイトのS-box(状態配列)を初期化し、与えられた鍵に基づいてS-boxの要素をシャッフルします。
  2. 擬似乱数生成アルゴリズム (PRGA): S-boxを使用して、鍵ストリームと呼ばれる擬似乱数のバイト列を生成します。この鍵ストリームは、平文とXOR演算されることで暗号文が生成されます。復号化も同様に、暗号文と鍵ストリームをXORすることで平文に戻します。

RC4はかつてSSL/TLSやWEPなどで広く利用されましたが、その後の研究で複数の脆弱性が発見され、現在では安全なプロトコルでの使用は推奨されていません。しかし、Go言語の crypto/rc4 パッケージは、既存のシステムとの互換性や特定の用途のために提供されています。

Go言語におけるアセンブリ最適化とポータブル版

Go言語は、高いパフォーマンスを要求される処理のために、一部の標準ライブラリ関数でアセンブリ言語による最適化された実装を提供しています。これは、特定のCPUアーキテクチャの命令セット(例: SIMD命令)を直接利用することで、純粋なGo言語実装よりも高速な実行を可能にするためです。

通常、Goのビルドシステムは、実行環境のアーキテクチャに応じて最適な実装(アセンブリ版またはポータブル版)を自動的に選択します。例えば、crypto/rc4 の場合、rc4_amd64.s のようなファイルにアセンブリコードが記述され、rc4.gorc4_ref.go に純粋なGoコードが記述されます。

ビットロット (Bitrot)

ソフトウェア開発における「ビットロット」は、コードが時間の経過とともに劣化し、機能しなくなる現象を指す比喩的な表現です。これは、物理的なビットの劣化ではなく、以下のような要因によって引き起こされます。

  • 未使用コードの放置: 特定のコードパスがほとんど実行されない、またはテストされない場合、そのコードは新しい環境や依存関係の変更に適応できず、いつの間にか壊れてしまうことがあります。
  • 環境の変化: オペレーティングシステム、コンパイラ、ライブラリのバージョンアップなど、実行環境の変化によってコードが期待通りに動作しなくなることがあります。
  • テストの不足: 十分なテストカバレッジがない、またはテストが古くなっている場合、コードのバグが発見されずに放置され、問題が顕在化するまで気づかれないことがあります。

このコミットでは、アセンブリ最適化版が優先的に使用されることで、純粋なGo言語版が「未使用コード」に近い状態になり、ビットロットのリスクが高まることを懸念しています。

技術的詳細

このコミットの主要な技術的変更点は以下の通りです。

  1. xorKeyStreamGeneric 関数の導入: src/pkg/crypto/rc4/rc4.go に、RC4の鍵ストリームとデータをXORする処理の純粋なGo言語実装である xorKeyStreamGeneric メソッドが追加されました。この関数は、既存の Cipher 型のメソッドとして定義され、RC4のPRGA(擬似乱数生成アルゴリズム)のコアロジックを含んでいます。コメントには、この関数がテスト用であり、ビットロットを防ぐためのものであることが明記されています。

  2. XORKeyStream の委譲: src/pkg/crypto/rc4/rc4_ref.go にあった XORKeyStream メソッドの本体が削除され、新しく追加された xorKeyStreamGeneric を呼び出すように変更されました。これにより、rc4_ref.go が提供するリファレンス実装は、xorKeyStreamGeneric を介して提供されることになります。これは、Go言語のビルドシステムがアセンブリ実装を選択しない場合にフォールバックとして使用されるパスです。

  3. テストの強化: src/pkg/crypto/rc4/rc4_test.go に、TestBlockGeneric という新しいテスト関数が追加されました。この関数は、xorKeyStreamGeneric メソッドを明示的にテストします。 また、既存の TestBlock 関数は、testBlock というヘルパー関数を呼び出すように変更され、このヘルパー関数は XORKeyStreamxorKeyStreamGeneric の両方をテストできるように汎用化されました。これにより、アセンブリ最適化されたバージョンと純粋なGo言語バージョンの両方が、同じテストロジックで検証されるようになりました。

これらの変更により、Go言語の標準ライブラリが提供するRC4実装のポータブル版が、アセンブリ最適化版と同様に厳密にテストされることが保証されます。これにより、将来的なGo言語の変更やコンパイラの進化があっても、ポータブル版が正しく機能し続けることが期待されます。

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

src/pkg/crypto/rc4/rc4.go

 // xorKeyStreamGeneric sets dst to the result of XORing src with the
 // key stream.  Dst and src may be the same slice but otherwise should
 // not overlap.
 //
 // This is the pure Go version. rc4_{amd64,386,arm}* contain assembly
 // implementations. This is here for tests and to prevent bitrot.
 func (c *Cipher) xorKeyStreamGeneric(dst, src []byte) {
 	i, j := c.i, c.j
 	for k, v := range src {
 		i += 1
 		j += uint8(c.s[i])
 		c.s[i], c.s[j] = c.s[j], c.s[i]
 		dst[k] = v ^ uint8(c.s[uint8(c.s[i]+c.s[j])])
 	}
 	c.i, c.j = i, j
 }

src/pkg/crypto/rc4/rc4_ref.go

 // XORKeyStream sets dst to the result of XORing src with the key stream.
 // Dst and src may be the same slice but otherwise should not overlap.
 func (c *Cipher) XORKeyStream(dst, src []byte) {
-	i, j := c.i, c.j
-	for k, v := range src {
-		i += 1
-		j += uint8(c.s[i])
-		c.s[i], c.s[j] = c.s[j], c.s[i]
-		dst[k] = v ^ uint8(c.s[uint8(c.s[i]+c.s[j])])
-	}
-	c.i, c.j = i, j
+	c.xorKeyStreamGeneric(dst, src)
 }

src/pkg/crypto/rc4/rc4_test.go

 func TestBlock(t *testing.T) {
+	testBlock(t, (*Cipher).XORKeyStream)
+}
+
+// Test the pure Go version.
+// Because we have assembly for amd64, 386, and arm, this prevents
+// bitrot of the reference implementations.
+func TestBlockGeneric(t *testing.T) {
+	testBlock(t, (*Cipher).xorKeyStreamGeneric)
+}
+
+func testBlock(t *testing.T, xor func(c *Cipher, dst, src []byte)) {
 	c1a, _ := NewCipher(golden[0].key)
 	c1b, _ := NewCipher(golden[1].key)
 	data1 := make([]byte, 1<<20)
 	for i := range data1 {
-		c1a.XORKeyStream(data1[i:i+1], data1[i:i+1])
-		c1b.XORKeyStream(data1[i:i+1], data1[i:i+1])
+		xor(c1a, data1[i:i+1], data1[i:i+1])
+		xor(c1b, data1[i:i+1], data1[i:i+1])
 	}
 
 	c2a, _ := NewCipher(golden[0].key)
 	c2b, _ := NewCipher(golden[1].key)
 	data2 := make([]byte, 1<<20)
-	c2a.XORKeyStream(data2, data2)
-	c2b.XORKeyStream(data2, data2)
+	xor(c2a, data2, data2)
+	xor(c2b, data2, data2)
 
 	if !bytes.Equal(data1, data2) {
 		t.Fatalf("bad block")

コアとなるコードの解説

xorKeyStreamGeneric の追加 (rc4.go)

この関数は、RC4の鍵ストリーム生成とXOR演算のロジックをカプセル化したものです。ij はRC4の状態変数であり、c.s はS-box(状態配列)です。ループ内で、RC4のPRGAアルゴリズムに従って ij を更新し、S-boxの要素をスワップし、鍵ストリームバイトを生成して入力バイト v とXORしています。

この関数が rc4.go に追加されたのは、Cipher 型のメソッドとして、アセンブリ実装が存在しない場合のフォールバックや、テストのために純粋なGo実装を明示的に呼び出せるようにするためです。

XORKeyStream の変更 (rc4_ref.go)

rc4_ref.go は、Go言語のビルドシステムが特定アーキテクチャ向けのアセンブリ実装を選択しない場合にコンパイルされる、純粋なGo言語によるリファレンス実装を提供します。このコミットでは、XORKeyStream メソッドの内部実装が、新しく追加された xorKeyStreamGeneric を呼び出すように変更されました。

これにより、リファレンス実装のロジックが xorKeyStreamGeneric に集約され、コードの重複が避けられ、保守性が向上します。また、rc4_ref.goXORKeyStreamxorKeyStreamGeneric を呼び出すことで、この汎用実装が実際に使用されるパスが明確になります。

テストの変更 (rc4_test.go)

  1. testBlock ヘルパー関数の導入: TestBlockTestBlockGeneric の両方で共通のテストロジックを実行するために、testBlock というヘルパー関数が導入されました。この関数は、xor func(c *Cipher, dst, src []byte) という関数型の引数を受け取ります。これにより、テスト対象となるXORストリーム関数を柔軟に切り替えることができます。

  2. TestBlock の変更: 既存の TestBlock は、(*Cipher).XORKeyStream を引数として testBlock を呼び出すように変更されました。(*Cipher).XORKeyStream は、通常、アセンブリ最適化された実装(もし存在すれば)を呼び出すエントリポイントです。

  3. TestBlockGeneric の追加: 新しく追加された TestBlockGeneric は、(*Cipher).xorKeyStreamGeneric を引数として testBlock を呼び出します。これにより、純粋なGo言語で書かれた xorKeyStreamGeneric の実装が明示的にテストされることが保証されます。

これらのテストの変更は、アセンブリ最適化されたバージョンと純粋なGo言語バージョンの両方が、同じ厳密なテストケースで検証されることを意味します。これにより、Go言語のバージョンアップやコンパイラの変更によって、純粋なGo言語実装が意図せず壊れる「ビットロット」を防ぐことができます。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント (crypto/rc4パッケージ): https://pkg.go.dev/crypto/rc4
  • RC4暗号に関する一般的な情報 (Wikipediaなど): https://ja.wikipedia.org/wiki/RC4
  • ソフトウェアにおける「ビットロット」の概念に関する情報。
  • Go言語のアセンブリ実装に関する情報。
  • Go言語のテストフレームワークに関する情報。
  • Go言語のIssueトラッカー (go.dev/issue)
  • Go言語のコードレビューシステム (go.googlesource.com)
  • Go言語のソースコード (GitHubミラー): https://github.com/golang/go