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

[インデックス 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バージョンにフォールバックする必要がある場合に、その機能が正しく動作することが保証されます。

前提知識の解説

  1. SHA-1 (Secure Hash Algorithm 1):

    • SHA-1は、アメリカ国家安全保障局 (NSA) によって設計された暗号学的ハッシュ関数です。入力データから固定長のハッシュ値(メッセージダイジェスト)を生成します。
    • SHA-1は160ビット(20バイト)のハッシュ値を生成します。
    • 暗号学的ハッシュ関数は、データの完全性検証(データが改ざんされていないことの確認)や、デジタル署名などに用いられます。
    • 現在では衝突攻撃(異なる入力から同じハッシュ値が生成されること)の脆弱性が発見されており、セキュリティが重要な場面ではSHA-256やSHA-3などのより強力なハッシュ関数への移行が推奨されています。しかし、既存のシステムやプロトコルでは依然として広く使用されています。
    • SHA-1の計算プロセスは、入力データを固定長のブロックに分割し、各ブロックに対して一連の複雑なビット演算(論理演算、ビットシフト、加算など)を繰り返し適用することでハッシュ値を更新していく「ブロック処理」が中心となります。
  2. Go言語のビルドタグ (// +build):

    • Go言語では、ファイルの先頭に // +build tag の形式でコメントを記述することで、そのファイルが特定のビルド条件(OS、アーキテクチャ、カスタムタグなど)が満たされた場合にのみコンパイルされるように制御できます。
    • 例えば、// +build amd64amd64 アーキテクチャでのみコンパイルされ、// +build !amd64amd64 以外のアーキテクチャでコンパイルされます。
    • この機能は、プラットフォーム固有の最適化(アセンブリコードの使用など)や、異なるOS向けのコードを一つのパッケージ内で管理する際に非常に有用です。
  3. アセンブリ最適化:

    • Go言語の標準ライブラリや高性能が求められる部分では、特定のCPUアーキテクチャ向けにアセンブリ言語で書かれたコードが使用されることがあります。
    • アセンブリコードは、Goコンパイラが生成する機械語よりもさらに低レベルで、CPUのレジスタや命令を直接操作できるため、Goコードでは実現できないような極限のパフォーマンス最適化が可能です。
    • SHA-1のような計算集約的なアルゴリズムでは、アセンブリ最適化によって大幅な速度向上が期待できます。
  4. ビットロット (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 関数がほとんどテストされず、その正確性が保証されにくい状態にあったことです。

このコミットでは、以下の変更によってこの問題を解決しています。

  1. blockGeneric 関数の導入と分離:

    • 既存の src/pkg/crypto/sha1/sha1block.go 内の純粋なGo実装の block 関数が blockGeneric にリネームされました。
    • この blockGeneric 関数は、sha1block.go からビルドタグ // +build !amd64,!386,!arm が削除され、すべてのアーキテクチャでコンパイルされるようになりました。
    • これにより、blockGeneric は常に利用可能な純粋なGo実装として存在します。
  2. sha1block_generic.go の新規作成:

    • 新しく src/pkg/crypto/sha1/sha1block_generic.go ファイルが作成されました。
    • このファイルには // +build !amd64,!386,!arm というビルドタグが付与されています。
    • このファイルの内容は非常にシンプルで、var block = blockGeneric という行のみです。
    • この設定により、amd64, 386, arm 以外のアーキテクチャでは、block という名前の関数が blockGeneric を指すようになります。つまり、これらのアーキテクチャでは引き続き純粋なGo実装が block として機能します。
  3. TestBlockGeneric テストの追加:

    • src/pkg/crypto/sha1/sha1_test.goTestBlockGeneric という新しいテスト関数が追加されました。
    • このテストは、crypto/rand パッケージを使用してランダムなバイト列を生成します。
    • 生成されたランダムデータに対して、blockGeneric 関数(純粋なGo実装)と、現在の環境で実際に使用される block 関数(アセンブリ最適化またはGo実装)の両方を適用します。
    • 両者のハッシュ計算結果(digest 構造体の状態)を比較し、一致しない場合はエラーを報告します。
    • このテストは、amd64 などのアセンブリ最適化が利用可能な環境でも実行されます。これにより、アセンブリ実装とGo実装が常に同じ結果を生成することが保証されます。

この変更により、Goのビルドシステムは以下のように動作します。

  • amd64, 386, arm アーキテクチャの場合:
    • sha1block.goblockGeneric はコンパイルされます。
    • アセンブリ実装の block 関数がコンパイルされ、これが block という名前で利用されます。
    • sha1block_generic.go はビルドタグによりコンパイルされません。
    • TestBlockGeneric は、アセンブリ実装の block と純粋なGo実装の blockGeneric を比較します。
  • その他のアーキテクチャ(例: ppc64, s390x)の場合:
    • sha1block.goblockGeneric はコンパイルされます。
    • アセンブリ実装の block 関数は存在しないため、コンパイルされません。
    • sha1block_generic.go がビルドタグによりコンパイルされ、var block = blockGeneric によって blockblockGeneric を指すようになります。
    • 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

コアとなるコードの解説

  1. src/pkg/crypto/sha1/sha1_test.go の変更:

    • import "crypto/rand" が追加され、ランダムなデータを生成するために使用されます。
    • TestBlockGeneric 関数が新しく追加されました。
      • このテストは、New().(*digest) を使って2つの digest オブジェクト (genasm) を作成します。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とアセンブリ)が同じ入力に対して同じ中間状態を生成することが保証されます。
  2. 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 とテストによって使用される」ことが明記されました。
  3. 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 ディレクトリ)
  • Go言語の公式ドキュメント
  • コミットメッセージと関連するコードレビュー (CL 62270043)
  • 一般的な暗号学的ハッシュ関数とソフトウェア開発における「ビットロット」の概念に関する知識