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

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

このコミットは、Go言語の標準ライブラリ crypto/md5 パッケージにおけるMD5ハッシュ計算のブロック処理関数に関する変更です。具体的には、アセンブリ最適化されたblock関数と、純粋なGoで実装されたポータブルなblockGeneric関数の両方が常にテストされるように、テストカバレッジを強化し、コードの構造を整理しています。これにより、特定のアーキテクチャに依存しない汎用実装が「ビット腐敗」(bitrot)するのを防ぎ、将来にわたって正しく機能することを保証します。

コミット

commit 72f0ed42fa89f1c02c6ff547762d4d1757d4fc75
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date:   Wed Feb 12 13:31:05 2014 -0800

    crypto/md5: always test the portable block function too
    
    So it doesn't bitrot.
    
    Like the sha1 version (https://golang.org/cl/62270043)
    
    LGTM=agl
    R=agl
    CC=golang-codereviews
    https://golang.org/cl/62420043

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

https://github.com/golang/go/commit/72f0ed42fa89f1c02c6ff547762d4d1757d4fc75

元コミット内容

このコミットの目的は、「ポータブルなブロック関数も常にテストする」ことです。これにより、その関数が「ビット腐敗」しないようにします。これは、以前のSHA-1の実装における同様の変更(https://golang.org/cl/62270043)に倣ったものです。

変更の背景

Go言語の暗号化ライブラリでは、パフォーマンス最適化のために、特定のCPUアーキテクチャ(例: amd64, 386, arm)向けにアセンブリ言語で最適化された実装が提供されることがあります。MD5ハッシュ計算のコア部分であるブロック処理関数もその一つです。しかし、これらの最適化された実装が存在する一方で、それらが利用できない環境(他のアーキテクチャや、アセンブリコードが利用できない場合)のために、純粋なGoで書かれた汎用(ポータブル)な実装も存在します。

問題は、通常の使用では最適化されたアセンブリ実装が優先的に使用されるため、純粋なGo実装が十分にテストされない可能性があることでした。テストカバレッジが低いと、Go実装にバグが潜んでいても発見されにくく、時間の経過とともにコードが陳腐化し、意図しない動作を引き起こす「ビット腐敗」(bitrot)が発生するリスクがあります。

このコミットは、crypto/sha1パッケージで以前に行われた同様の修正(https://golang.org/cl/62270043)に触発されており、crypto/md5パッケージでも同様に、純粋なGoで書かれたブロック処理関数が常にテストされるようにすることで、この問題を解決しようとしています。これにより、Go実装の正確性と堅牢性が保証され、将来にわたってすべてのサポートされる環境でMD5が正しく機能するようになります。

前提知識の解説

  1. MD5 (Message-Digest Algorithm 5): MD5は、128ビット(16バイト)のハッシュ値を生成する広く使用されている暗号学的ハッシュ関数です。入力データが少しでも変更されると、出力されるハッシュ値が大きく変化するという特性(雪崩効果)を持ちます。主にデータの完全性チェックに使用されますが、衝突耐性の脆弱性が指摘されており、セキュリティが重要な場面ではSHA-256などのより強力なハッシュ関数が推奨されます。MD5の計算プロセスは、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して一連の複雑なビット演算と論理演算を適用することでハッシュ値を更新していきます。このブロック処理がMD5計算のコアとなります。

  2. Go言語のビルドタグ (+build): Go言語では、ソースファイルに特殊なコメント行 // +build tag を記述することで、そのファイルが特定のビルド条件(OS、アーキテクチャ、カスタムタグなど)が満たされた場合にのみコンパイルされるように制御できます。例えば、// +build amd64amd64アーキテクチャでのみコンパイルされ、// +build !amd64amd64以外のアーキテクチャでコンパイルされます。これにより、異なる環境向けに最適化されたコードや、プラットフォーム固有のコードを適切に分離して管理できます。

  3. アセンブリ最適化: Go言語の標準ライブラリや高性能が求められる部分では、C言語やGo言語で書かれたコードよりもさらに高速な実行を可能にするために、特定のCPUアーキテクチャ向けにアセンブリ言語で実装された関数が提供されることがあります。これらのアセンブリ実装は、CPUの特定の命令セット(例: SIMD命令)を直接利用することで、Goコンパイラが生成するコードよりも効率的な処理を実現します。

  4. ビット腐敗 (Bitrot): ソフトウェア開発における「ビット腐敗」とは、コードが時間の経過とともに、使用されなくなったり、テストされなくなったりすることで、徐々に機能しなくなったり、バグが発生したりする現象を指します。これは、コードベースの他の部分の変更、コンパイラの更新、ランタイム環境の変更など、さまざまな要因によって引き起こされる可能性があります。特に、あまり使われないコードパスや、特定の環境でしか実行されないコードは、ビット腐敗のリスクが高まります。

技術的詳細

このコミットは、crypto/md5パッケージ内のMD5ブロック処理関数の実装とテスト方法を改善しています。

主な変更点は以下の通りです。

  1. block関数のリネームと分離:

    • src/pkg/crypto/md5/gen.go および src/pkg/crypto/md5/md5block.go 内で定義されていた block 関数が blockGeneric にリネームされました。これらのファイルは、Goで書かれたMD5ブロック処理の汎用実装を含んでいます。
    • これらのファイルから、ビルドタグ // +build !amd64,!386,!arm が削除されました。これは、これらのファイルが常にコンパイルされることを意味します。
  2. md5block_generic.go の新規作成:

    • src/pkg/crypto/md5/md5block_generic.go という新しいファイルが作成されました。
    • このファイルには、// +build !amd64,!386,!arm というビルドタグが付与されています。
    • このファイル内で、var block = blockGeneric という行が追加されました。
    • この変更により、amd64, 386, arm 以外のアーキテクチャでは、block という名前で blockGeneric 関数が使用されるようになります。つまり、純粋なGo実装がデフォルトのブロック処理関数として機能します。
  3. テストの追加 (TestBlockGeneric):

    • src/pkg/crypto/md5/md5_test.goTestBlockGeneric という新しいテスト関数が追加されました。
    • このテストは、blockGeneric(純粋なGo実装)と block(アセンブリ最適化された実装、またはmd5block_generic.goによってエイリアスされたblockGeneric)の両方を使用して、同じランダムな入力データに対してMD5ハッシュ計算のブロック処理を実行します。
    • 両者の結果(digest構造体の状態)が一致するかどうかを比較することで、異なる実装間での互換性と正確性を検証します。これにより、アセンブリ実装が存在する環境でも、純粋なGo実装が正しく機能していることを確認できます。

これらの変更により、GoのMD5実装は、アセンブリ最適化されたバージョンと純粋なGoバージョンの両方が常にテストされるようになり、特に純粋なGoバージョンがビット腐敗するリスクが大幅に低減されました。

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

変更されたファイルと行数は以下の通りです。

  • src/pkg/crypto/md5/gen.go: 4行変更 (1追加, 3削除)
  • src/pkg/crypto/md5/md5_test.go: 13行変更 (13追加, 0削除)
  • src/pkg/crypto/md5/md5block.go: 4行変更 (1追加, 3削除)
  • src/pkg/crypto/md5/md5block_generic.go: 9行変更 (9追加, 0削除)

具体的な変更内容:

src/pkg/crypto/md5/gen.go および src/pkg/crypto/md5/md5block.go:

--- a/src/pkg/crypto/md5/gen.go
+++ b/src/pkg/crypto/md5/gen.go
@@ -167,8 +167,6 @@ var program = `// Copyright 2013 The Go Authors. All rights reserved.
 // DO NOT EDIT.
 // Generate with: go run gen.go{{if .Full}} -full{{end}} | gofmt >md5block.go
 
-// +build !amd64,!386,!arm`
-
 package md5
 
 import (
@@ -204,7 +202,7 @@ func init() {
 	littleEndian = *(*[4]byte)(unsafe.Pointer(&x)) == y
 }
 
-func block(dig *digest, p []byte) {
+func blockGeneric(dig *digest, p []byte) {
 	a := dig.s[0]
 	b := dig.s[1]
 	c := dig.s[2]
  • // +build !amd64,!386,!arm の行が削除されました。これにより、これらのファイルは常にコンパイルされるようになります。
  • func blockfunc blockGeneric にリネームされました。

src/pkg/crypto/md5/md5block_generic.go (新規ファイル):

--- /dev/null
+++ b/src/pkg/crypto/md5/md5block_generic.go
@@ -0,0 +1,9 @@
+// Copyright 2014 The Go Authors. All rights reserved.
+// Use of this source code is governed by a BSD-style
+// license that can be found in the LICENSE file.
+
+// +build !amd64,!386,!arm
+
+package md5
+
+var block = blockGeneric
  • 新しいファイルが作成され、// +build !amd64,!386,!arm ビルドタグが付与されています。
  • var block = blockGeneric という行が追加され、block という名前が blockGeneric 関数を指すようにエイリアスされています。

src/pkg/crypto/md5/md5_test.go:

--- a/src/pkg/crypto/md5/md5_test.go
+++ b/src/pkg/crypto/md5/md5_test.go
@@ -5,6 +5,7 @@
 package md5
 
 import (
+	"crypto/rand"
 	"fmt"
 	"io"
 	"testing"
@@ -105,6 +106,18 @@ func TestLarge(t *testing.T) {
 	}
 }
 
+// Tests that blockGeneric (pure Go) and block (in assembly for amd64, 386, arm) match.
+func TestBlockGeneric(t *testing.T) {
+	gen, asm := New().(*digest), New().(*digest)
+	buf := make([]byte, BlockSize*20) // arbitrary factor
+	rand.Read(buf)
+	blockGeneric(gen, buf)
+	block(asm, buf)
+	if *gen != *asm {
+		t.Error("block and blockGeneric resulted in different states")
+	}
+}
+
 var bench = New()
 var buf = make([]byte, 8192+1)
 var sum = make([]byte, bench.Size())
  • crypto/rand パッケージがインポートされました。
  • TestBlockGeneric という新しいテスト関数が追加されました。この関数は、blockGenericblock の両方を使ってランダムなデータでハッシュ計算を行い、結果が一致するかを検証します。

コアとなるコードの解説

このコミットの核心は、GoのMD5実装における「汎用(ポータブル)なGoコード」と「アセンブリ最適化されたコード」の共存と、そのテスト戦略の強化にあります。

  1. block から blockGeneric へのリネーム: gen.gomd5block.go にあった block 関数が blockGeneric にリネームされたのは、これが純粋なGoで書かれた汎用的な実装であることを明確にするためです。これにより、アセンブリで書かれた最適化された block 関数(もし存在すれば)と名前の衝突を避け、コードの意図をより明確にしています。

  2. ビルドタグの変更と md5block_generic.go の導入:

    • gen.gomd5block.go から // +build !amd64,!386,!arm タグが削除されたことで、これらのファイル(およびその中の blockGeneric 関数)は、どのアーキテクチャでも常にコンパイルされるようになりました。
    • 新しく作成された md5block_generic.go には、削除されたビルドタグが再び付与されています。このファイルは、var block = blockGeneric という一行を含んでいます。
    • この構成により、amd64, 386, arm などの最適化されたアセンブリ実装が存在するアーキテクチャでは、コンパイラはアセンブリ版の block 関数を選択します。
    • 一方、これらのアーキテクチャ以外(またはアセンブリ実装が存在しない場合)では、md5block_generic.go がコンパイルされ、block という名前が blockGeneric(純粋なGo実装)を指すようになります。これにより、すべての環境で適切な block 実装が利用されることが保証されます。
  3. TestBlockGeneric によるテストカバレッジの強化: このテストは、このコミットの最も重要な部分です。

    • gen, asm := New().(*digest), New().(*digest): 2つのMD5ダイジェストインスタンスを初期化します。genblockGeneric の結果を、asmblock の結果を保持するために使われます。
    • buf := make([]byte, BlockSize*20): MD5のブロックサイズ(64バイト)の20倍のランダムなバイト列を生成します。これにより、複数のブロックにわたる処理をテストできます。
    • rand.Read(buf): 生成したバッファをランダムなデータで埋めます。これにより、予測不能な入力に対するテストが可能になります。
    • blockGeneric(gen, buf): 純粋なGo実装である blockGeneric 関数を呼び出し、gen ダイジェストの状態を更新します。
    • block(asm, buf): 現在の環境で利用可能な block 関数(アセンブリ最適化版、または md5block_generic.go によってエイリアスされた blockGeneric)を呼び出し、asm ダイジェストの状態を更新します。
    • if *gen != *asm: blockGenericblock の両方で処理された後のダイジェストの状態を比較します。
    • このテストは、アセンブリ最適化された block 関数が存在する環境であっても、純粋なGo実装である blockGeneric が常に実行され、その結果がアセンブリ版と一致するかどうかを検証します。これにより、純粋なGo実装が「ビット腐敗」することなく、常に正しく機能していることが保証されます。

この一連の変更により、GoのMD5パッケージは、異なるアーキテクチャやビルド環境において、パフォーマンスと正確性の両方を維持できるようになりました。

関連リンク

参考にした情報源リンク

  • Go言語のコミットメッセージと差分情報 (./commit_data/18478.txt)
  • Go言語の公式ドキュメント (Go Build Constraints)
  • MD5アルゴリズムに関する一般的な知識
  • ソフトウェア開発における「ビット腐敗」の概念
  • Go言語の標準ライブラリのソースコード構造に関する一般的な知識