[インデックス 18473] ファイルの概要
このコミットは、Go言語の標準ライブラリ crypto/sha1
パッケージにおけるSHA-1ハッシュ計算のブロック処理関数に関する変更です。特に、アセンブリ最適化されたバージョンと、純粋なGoで書かれたポータブルな(移植性の高い)バージョンの両方が常にテストされるように改善されています。これにより、ポータブルな実装が「ビットロット」(bitrot: 時間の経過とともに使われなくなり、テストもされずに劣化していくこと)するのを防ぎ、将来的な互換性と正確性を保証します。
コミット
commit 14c5c8a93a55593403f198c4df3c1e503840cf02
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Wed Feb 12 11:27:36 2014 -0800
crypto/sha1: always test the portable block function too
So it doesn't bitrot.
LGTM=agl
R=golang-codereviews, agl
CC=golang-codereviews
https://golang.org/cl/62270043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/14c5c8a93a55593403f198c4df3c1e503840cf02
元コミット内容
crypto/sha1: always test the portable block function too
So it doesn't bitrot.
変更の背景
Go言語の crypto/sha1
パッケージは、SHA-1ハッシュ計算のコア部分であるブロック処理において、パフォーマンス最適化のために特定アーキテクチャ(例: amd64
, 386
, arm
)向けにアセンブリ言語で実装された高速なバージョンと、あらゆる環境で動作する純粋なGo言語で書かれたポータブルなバージョンの両方を持っています。
これまでの実装では、アセンブリ最適化されたバージョンが存在する環境では、そちらが優先的に使用され、純粋なGoバージョンはほとんど実行されませんでした。この状況が続くと、Goバージョンのコードがテストされなくなり、Go言語やランタイムの変更、あるいはSHA-1アルゴリズムの微妙な解釈の違いなどによって、Goバージョンが誤った動作をするようになる「ビットロット」のリスクがありました。
このコミットの目的は、たとえアセンブリ最適化バージョンが使用される環境であっても、純粋なGoバージョンの block
関数が常にテストされるようにすることで、その正確性と保守性を保証することです。これにより、将来的にアセンブリバージョンが利用できない環境や、何らかの理由でGoバージョンにフォールバックする必要がある場合に、その機能が正しく動作することが保証されます。
前提知識の解説
-
SHA-1 (Secure Hash Algorithm 1):
- SHA-1は、アメリカ国家安全保障局 (NSA) によって設計された暗号学的ハッシュ関数です。入力データから固定長のハッシュ値(メッセージダイジェスト)を生成します。
- SHA-1は160ビット(20バイト)のハッシュ値を生成します。
- 暗号学的ハッシュ関数は、データの完全性検証(データが改ざんされていないことの確認)や、デジタル署名などに用いられます。
- 現在では衝突攻撃(異なる入力から同じハッシュ値が生成されること)の脆弱性が発見されており、セキュリティが重要な場面ではSHA-256やSHA-3などのより強力なハッシュ関数への移行が推奨されています。しかし、既存のシステムやプロトコルでは依然として広く使用されています。
- SHA-1の計算プロセスは、入力データを固定長のブロックに分割し、各ブロックに対して一連の複雑なビット演算(論理演算、ビットシフト、加算など)を繰り返し適用することでハッシュ値を更新していく「ブロック処理」が中心となります。
-
Go言語のビルドタグ (
// +build
):- Go言語では、ファイルの先頭に
// +build tag
の形式でコメントを記述することで、そのファイルが特定のビルド条件(OS、アーキテクチャ、カスタムタグなど)が満たされた場合にのみコンパイルされるように制御できます。 - 例えば、
// +build amd64
はamd64
アーキテクチャでのみコンパイルされ、// +build !amd64
はamd64
以外のアーキテクチャでコンパイルされます。 - この機能は、プラットフォーム固有の最適化(アセンブリコードの使用など)や、異なるOS向けのコードを一つのパッケージ内で管理する際に非常に有用です。
- Go言語では、ファイルの先頭に
-
アセンブリ最適化:
- Go言語の標準ライブラリや高性能が求められる部分では、特定のCPUアーキテクチャ向けにアセンブリ言語で書かれたコードが使用されることがあります。
- アセンブリコードは、Goコンパイラが生成する機械語よりもさらに低レベルで、CPUのレジスタや命令を直接操作できるため、Goコードでは実現できないような極限のパフォーマンス最適化が可能です。
- SHA-1のような計算集約的なアルゴリズムでは、アセンブリ最適化によって大幅な速度向上が期待できます。
-
ビットロット (Bitrot):
- ソフトウェア開発における「ビットロット」とは、コードが長期間使用されなかったり、テストされなかったりすることで、その機能が徐々に劣化したり、現在の環境や他のコードベースとの互換性が失われたりする現象を指します。
- 物理的なビットの劣化とは異なり、論理的な劣化であり、主に環境の変化(OSのバージョンアップ、ライブラリの更新、Go言語自体の変更など)や、他のコードの変更によって引き起こされます。
- ビットロットを防ぐためには、継続的なテスト、コードレビュー、そして定期的なメンテナンスが不可欠です。
技術的詳細
このコミットの核心は、SHA-1のブロック処理関数 block
の実装戦略とテストカバレッジの改善にあります。
Goの crypto/sha1
パッケージでは、SHA-1のブロック処理を行う関数が複数存在していました。
block
関数: これは通常、amd64
,386
,arm
などの主要なアーキテクチャ向けにアセンブリ言語で最適化された実装が提供されていました。これらのアセンブリ実装は、ビルドタグによって特定のアーキテクチャでのみコンパイルされるように制御されていました。- 純粋なGo実装: アセンブリ実装が存在しない、または利用できないアーキテクチャ(例:
ppc64
,s390x
など)向けに、純粋なGo言語で書かれたポータブルな実装も存在していました。
このコミット以前は、純粋なGo実装は sha1block.go
内の block
関数として定義されており、そのファイルには // +build !amd64,!386,!arm
というビルドタグが付与されていました。これは、amd64
, 386
, arm
以外のアーキテクチャでのみこのファイルがコンパイルされ、結果としてこのGo実装の block
関数が使用されることを意味します。
問題は、amd64
などの主要なアーキテクチャではアセンブリ実装が優先されるため、純粋なGo実装の block
関数がほとんどテストされず、その正確性が保証されにくい状態にあったことです。
このコミットでは、以下の変更によってこの問題を解決しています。
-
blockGeneric
関数の導入と分離:- 既存の
src/pkg/crypto/sha1/sha1block.go
内の純粋なGo実装のblock
関数がblockGeneric
にリネームされました。 - この
blockGeneric
関数は、sha1block.go
からビルドタグ// +build !amd64,!386,!arm
が削除され、すべてのアーキテクチャでコンパイルされるようになりました。 - これにより、
blockGeneric
は常に利用可能な純粋なGo実装として存在します。
- 既存の
-
sha1block_generic.go
の新規作成:- 新しく
src/pkg/crypto/sha1/sha1block_generic.go
ファイルが作成されました。 - このファイルには
// +build !amd64,!386,!arm
というビルドタグが付与されています。 - このファイルの内容は非常にシンプルで、
var block = blockGeneric
という行のみです。 - この設定により、
amd64
,386
,arm
以外のアーキテクチャでは、block
という名前の関数がblockGeneric
を指すようになります。つまり、これらのアーキテクチャでは引き続き純粋なGo実装がblock
として機能します。
- 新しく
-
TestBlockGeneric
テストの追加:src/pkg/crypto/sha1/sha1_test.go
にTestBlockGeneric
という新しいテスト関数が追加されました。- このテストは、
crypto/rand
パッケージを使用してランダムなバイト列を生成します。 - 生成されたランダムデータに対して、
blockGeneric
関数(純粋なGo実装)と、現在の環境で実際に使用されるblock
関数(アセンブリ最適化またはGo実装)の両方を適用します。 - 両者のハッシュ計算結果(
digest
構造体の状態)を比較し、一致しない場合はエラーを報告します。 - このテストは、
amd64
などのアセンブリ最適化が利用可能な環境でも実行されます。これにより、アセンブリ実装とGo実装が常に同じ結果を生成することが保証されます。
この変更により、Goのビルドシステムは以下のように動作します。
amd64
,386
,arm
アーキテクチャの場合:sha1block.go
のblockGeneric
はコンパイルされます。- アセンブリ実装の
block
関数がコンパイルされ、これがblock
という名前で利用されます。 sha1block_generic.go
はビルドタグによりコンパイルされません。TestBlockGeneric
は、アセンブリ実装のblock
と純粋なGo実装のblockGeneric
を比較します。
- その他のアーキテクチャ(例:
ppc64
,s390x
)の場合:sha1block.go
のblockGeneric
はコンパイルされます。- アセンブリ実装の
block
関数は存在しないため、コンパイルされません。 sha1block_generic.go
がビルドタグによりコンパイルされ、var block = blockGeneric
によってblock
がblockGeneric
を指すようになります。TestBlockGeneric
は、block
(実質的にはblockGeneric
)とblockGeneric
を比較します。これは常に一致するはずですが、テストのフレームワークとしては一貫しています。
結果として、どのアーキテクチャでビルド・テストを実行しても、純粋なGo実装である blockGeneric
は必ずテストされるようになり、ビットロットが効果的に防止されます。
コアとなるコードの変更箇所
src/pkg/crypto/sha1/sha1_test.go
--- a/src/pkg/crypto/sha1/sha1_test.go
+++ b/src/pkg/crypto/sha1/sha1_test.go
@@ -7,6 +7,7 @@
package sha1
import (
+ "crypto/rand"
"fmt"
"io"
"testing"
@@ -90,6 +91,18 @@ func TestBlockSize(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)
src/pkg/crypto/sha1/sha1block.go
--- a/src/pkg/crypto/sha1/sha1block.go
+++ b/src/pkg/crypto/sha1/sha1block.go
@@ -2,12 +2,6 @@
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.\n
-// +build !amd64,!386,!arm
-// SHA1 block step.
-// In its own file so that a faster assembly or C version
-// can be substituted easily.
-
package sha1
const (
@@ -17,7 +11,9 @@ const (
_K3 = 0xCA62C1D6
)
-func block(dig *digest, p []byte) {
+// blockGeneric is a portable, pure Go version of the SHA1 block step.
+// It's used by sha1block_generic.go and tests.
+func blockGeneric(dig *digest, p []byte) {
var w [16]uint32
h0, h1, h2, h3, h4 := dig.h[0], dig.h[1], dig.h[2], dig.h[3], dig.h[4]
src/pkg/crypto/sha1/sha1block_generic.go
(新規ファイル)
--- /dev/null
+++ b/src/pkg/crypto/sha1/sha1block_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 sha1
+
+var block = blockGeneric
コアとなるコードの解説
-
src/pkg/crypto/sha1/sha1_test.go
の変更:import "crypto/rand"
が追加され、ランダムなデータを生成するために使用されます。TestBlockGeneric
関数が新しく追加されました。- このテストは、
New().(*digest)
を使って2つのdigest
オブジェクト (gen
とasm
) を作成します。digest
はSHA-1ハッシュ計算の内部状態を保持する構造体です。 buf := make([]byte, BlockSize*20)
で、SHA-1のブロックサイズ(64バイト)の20倍の大きさのバッファを作成します。rand.Read(buf)
で、このバッファに暗号学的に安全なランダムなバイト列を書き込みます。blockGeneric(gen, buf)
を呼び出し、純粋なGo実装のブロック関数でgen
の状態を更新します。block(asm, buf)
を呼び出し、現在のビルド環境で利用可能なblock
関数(アセンブリ実装またはGo実装)でasm
の状態を更新します。if *gen != *asm
で、両者のdigest
オブジェクトが完全に一致するかを比較します。一致しない場合はt.Error
でテスト失敗を報告します。- このテストにより、異なる実装(Goとアセンブリ)が同じ入力に対して同じ中間状態を生成することが保証されます。
- このテストは、
-
src/pkg/crypto/sha1/sha1block.go
の変更:- ファイルの先頭にあったビルドタグ
// +build !amd64,!386,!arm
が削除されました。これにより、このファイル内のコード(特にblockGeneric
)は、すべてのアーキテクチャでコンパイルされるようになります。 func block(dig *digest, p []byte)
がfunc blockGeneric(dig *digest, p []byte)
にリネームされました。- 関数のコメントが更新され、
blockGeneric
が「ポータブルな、純粋なGoバージョンのSHA1ブロックステップ」であり、「sha1block_generic.go
とテストによって使用される」ことが明記されました。
- ファイルの先頭にあったビルドタグ
-
src/pkg/crypto/sha1/sha1block_generic.go
の新規作成:- このファイルは、
// +build !amd64,!386,!arm
というビルドタグを持ちます。これは、amd64
,386
,arm
以外のアーキテクチャでのみこのファイルがコンパイルされることを意味します。 var block = blockGeneric
という行が含まれています。これにより、アセンブリ実装が存在しないアーキテクチャでは、block
という名前がblockGeneric
関数への参照として定義されます。つまり、これらの環境ではblock
を呼び出すとblockGeneric
が実行されます。
- このファイルは、
これらの変更の組み合わせにより、Goのビルドシステムは、アセンブリ最適化された block
関数が存在する環境ではそちらを使用しつつ、純粋なGo実装の blockGeneric
も常にコンパイル・テスト対象とすることで、両者の整合性を保ち、Go実装のビットロットを防ぐことに成功しています。
関連リンク
- Go言語の
crypto/sha1
パッケージのドキュメント: https://pkg.go.dev/crypto/sha1 - Go言語のビルド制約(Build Constraints)に関する公式ドキュメント: https://pkg.go.dev/cmd/go#hdr-Build_constraints
- SHA-1に関するWikipedia記事: https://ja.wikipedia.org/wiki/SHA-1
参考にした情報源リンク
- Go言語のソースコード (特に
crypto/sha1
ディレクトリ) - Go言語の公式ドキュメント
- コミットメッセージと関連するコードレビュー (CL 62270043)
- 一般的な暗号学的ハッシュ関数とソフトウェア開発における「ビットロット」の概念に関する知識