[インデックス 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が正しく機能するようになります。
前提知識の解説
-
MD5 (Message-Digest Algorithm 5): MD5は、128ビット(16バイト)のハッシュ値を生成する広く使用されている暗号学的ハッシュ関数です。入力データが少しでも変更されると、出力されるハッシュ値が大きく変化するという特性(雪崩効果)を持ちます。主にデータの完全性チェックに使用されますが、衝突耐性の脆弱性が指摘されており、セキュリティが重要な場面ではSHA-256などのより強力なハッシュ関数が推奨されます。MD5の計算プロセスは、入力データを512ビット(64バイト)のブロックに分割し、各ブロックに対して一連の複雑なビット演算と論理演算を適用することでハッシュ値を更新していきます。このブロック処理がMD5計算のコアとなります。
-
Go言語のビルドタグ (
+build
): Go言語では、ソースファイルに特殊なコメント行// +build tag
を記述することで、そのファイルが特定のビルド条件(OS、アーキテクチャ、カスタムタグなど)が満たされた場合にのみコンパイルされるように制御できます。例えば、// +build amd64
はamd64
アーキテクチャでのみコンパイルされ、// +build !amd64
はamd64
以外のアーキテクチャでコンパイルされます。これにより、異なる環境向けに最適化されたコードや、プラットフォーム固有のコードを適切に分離して管理できます。 -
アセンブリ最適化: Go言語の標準ライブラリや高性能が求められる部分では、C言語やGo言語で書かれたコードよりもさらに高速な実行を可能にするために、特定のCPUアーキテクチャ向けにアセンブリ言語で実装された関数が提供されることがあります。これらのアセンブリ実装は、CPUの特定の命令セット(例: SIMD命令)を直接利用することで、Goコンパイラが生成するコードよりも効率的な処理を実現します。
-
ビット腐敗 (Bitrot): ソフトウェア開発における「ビット腐敗」とは、コードが時間の経過とともに、使用されなくなったり、テストされなくなったりすることで、徐々に機能しなくなったり、バグが発生したりする現象を指します。これは、コードベースの他の部分の変更、コンパイラの更新、ランタイム環境の変更など、さまざまな要因によって引き起こされる可能性があります。特に、あまり使われないコードパスや、特定の環境でしか実行されないコードは、ビット腐敗のリスクが高まります。
技術的詳細
このコミットは、crypto/md5
パッケージ内のMD5ブロック処理関数の実装とテスト方法を改善しています。
主な変更点は以下の通りです。
-
block
関数のリネームと分離:src/pkg/crypto/md5/gen.go
およびsrc/pkg/crypto/md5/md5block.go
内で定義されていたblock
関数がblockGeneric
にリネームされました。これらのファイルは、Goで書かれたMD5ブロック処理の汎用実装を含んでいます。- これらのファイルから、ビルドタグ
// +build !amd64,!386,!arm
が削除されました。これは、これらのファイルが常にコンパイルされることを意味します。
-
md5block_generic.go
の新規作成:src/pkg/crypto/md5/md5block_generic.go
という新しいファイルが作成されました。- このファイルには、
// +build !amd64,!386,!arm
というビルドタグが付与されています。 - このファイル内で、
var block = blockGeneric
という行が追加されました。 - この変更により、
amd64
,386
,arm
以外のアーキテクチャでは、block
という名前でblockGeneric
関数が使用されるようになります。つまり、純粋なGo実装がデフォルトのブロック処理関数として機能します。
-
テストの追加 (
TestBlockGeneric
):src/pkg/crypto/md5/md5_test.go
にTestBlockGeneric
という新しいテスト関数が追加されました。- このテストは、
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 block
がfunc 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
という新しいテスト関数が追加されました。この関数は、blockGeneric
とblock
の両方を使ってランダムなデータでハッシュ計算を行い、結果が一致するかを検証します。
コアとなるコードの解説
このコミットの核心は、GoのMD5実装における「汎用(ポータブル)なGoコード」と「アセンブリ最適化されたコード」の共存と、そのテスト戦略の強化にあります。
-
block
からblockGeneric
へのリネーム:gen.go
とmd5block.go
にあったblock
関数がblockGeneric
にリネームされたのは、これが純粋なGoで書かれた汎用的な実装であることを明確にするためです。これにより、アセンブリで書かれた最適化されたblock
関数(もし存在すれば)と名前の衝突を避け、コードの意図をより明確にしています。 -
ビルドタグの変更と
md5block_generic.go
の導入:gen.go
とmd5block.go
から// +build !amd64,!386,!arm
タグが削除されたことで、これらのファイル(およびその中のblockGeneric
関数)は、どのアーキテクチャでも常にコンパイルされるようになりました。- 新しく作成された
md5block_generic.go
には、削除されたビルドタグが再び付与されています。このファイルは、var block = blockGeneric
という一行を含んでいます。 - この構成により、
amd64
,386
,arm
などの最適化されたアセンブリ実装が存在するアーキテクチャでは、コンパイラはアセンブリ版のblock
関数を選択します。 - 一方、これらのアーキテクチャ以外(またはアセンブリ実装が存在しない場合)では、
md5block_generic.go
がコンパイルされ、block
という名前がblockGeneric
(純粋なGo実装)を指すようになります。これにより、すべての環境で適切なblock
実装が利用されることが保証されます。
-
TestBlockGeneric
によるテストカバレッジの強化: このテストは、このコミットの最も重要な部分です。gen, asm := New().(*digest), New().(*digest)
: 2つのMD5ダイジェストインスタンスを初期化します。gen
はblockGeneric
の結果を、asm
はblock
の結果を保持するために使われます。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
:blockGeneric
とblock
の両方で処理された後のダイジェストの状態を比較します。- このテストは、アセンブリ最適化された
block
関数が存在する環境であっても、純粋なGo実装であるblockGeneric
が常に実行され、その結果がアセンブリ版と一致するかどうかを検証します。これにより、純粋なGo実装が「ビット腐敗」することなく、常に正しく機能していることが保証されます。
この一連の変更により、GoのMD5パッケージは、異なるアーキテクチャやビルド環境において、パフォーマンスと正確性の両方を維持できるようになりました。
関連リンク
- Go言語のビルド制約(Build Constraints)に関する公式ドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- MD5 (Wikipedia): https://ja.wikipedia.org/wiki/MD5
- SHA-1の同様の変更に関するGoの変更リスト (CL): https://golang.org/cl/62270043
参考にした情報源リンク
- Go言語のコミットメッセージと差分情報 (
./commit_data/18478.txt
) - Go言語の公式ドキュメント (Go Build Constraints)
- MD5アルゴリズムに関する一般的な知識
- ソフトウェア開発における「ビット腐敗」の概念
- Go言語の標準ライブラリのソースコード構造に関する一般的な知識