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

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

このコミットは、Go言語の標準ライブラリ crypto/sha1 パッケージにトップレベルの Sum 関数を追加するものです。これにより、与えられたデータのSHA-1ハッシュ値を簡単に計算できるようになります。

コミット

commit 4cf73890a2cc4a75cd8cd2ad726690a2ef60cf1d
Author: Rob Pike <r@golang.org>
Date:   Tue Jun 25 17:04:18 2013 -0700

    crypto/sha1: provide a top-level Sum function
    Makes it easy to ask the simple question, what is the hash of this data?
    
    R=golang-dev, rsc, bradfitz
    CC=golang-dev
    https://golang.org/cl/10571043

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

https://github.com/golang/go/commit/4cf73890a2cc4a75cd8cd2ad726690a2ef60cf1d

元コミット内容

crypto/sha1: provide a top-level Sum function Makes it easy to ask the simple question, what is the hash of this data?

このコミットは、crypto/sha1 パッケージにトップレベルの Sum 関数を提供します。これにより、「このデータのハッシュは何ですか?」という単純な問いに簡単に答えることができるようになります。

変更の背景

Go言語の hash インターフェース(hash.Hash)は、ストリーム形式でデータを処理し、ハッシュ値を計算するための柔軟な方法を提供します。通常、ハッシュ値を計算するには、New() 関数でハッシュアルゴリズムのインスタンスを作成し、Write() メソッドでデータを渡し、最後に Sum() メソッドを呼び出して最終的なハッシュ値を取得します。

しかし、多くのユースケースでは、単一のバイトスライス全体のハッシュ値を一度に計算したいというシンプルな要求があります。既存の hash.Hash インターフェースのパターンでは、この単純な操作のためにも複数のステップ(インスタンス作成、書き込み、Sum呼び出し)が必要でした。

このコミットの背景には、このような一般的なユースケースに対して、より簡潔で直感的なAPIを提供したいという意図があります。トップレベルの Sum 関数を導入することで、ユーザーはハッシュオブジェクトのライフサイクルを意識することなく、直接データのハッシュ値を計算できるようになります。これは、特にスクリプトやワンショットのハッシュ計算において、コードの可読性と記述性を向上させます。

前提知識の解説

1. SHA-1 (Secure Hash Algorithm 1)

SHA-1は、アメリカ国家安全保障局(NSA)によって設計された暗号学的ハッシュ関数です。任意の長さの入力データ(メッセージ)を受け取り、160ビット(20バイト)の固定長ハッシュ値(メッセージダイジェスト)を出力します。このハッシュ値は、データの整合性を検証するために使用されます。

SHA-1の主な特性は以下の通りです。

  • 一方向性: ハッシュ値から元のデータを復元することは計算上困難です。
  • 衝突耐性: 異なる入力データから同じハッシュ値が生成されること(衝突)は計算上困難であるべきです。ただし、SHA-1は現在、理論的および実践的な衝突攻撃に対して脆弱であることが知られており、新しいアプリケーションでの使用は推奨されていません。より強力なSHA-2(SHA-256, SHA-512など)やSHA-3が推奨されます。
  • 雪崩効果: 入力データのごくわずかな変更でも、ハッシュ値が大きく変化します。

2. Go言語の crypto/sha1 パッケージ

Go言語の標準ライブラリ crypto/sha1 パッケージは、SHA-1ハッシュアルゴリズムの実装を提供します。このパッケージは、hash.Hash インターフェースを実装しており、以下のような典型的な使用パターンがあります。

import (
	"crypto/sha1"
	"fmt"
	"io"
)

func main() {
	data := []byte("Hello, Go!")

	// 1. ハッシュオブジェクトの作成
	h := sha1.New()

	// 2. データの書き込み
	io.WriteString(h, string(data)) // または h.Write(data)

	// 3. ハッシュ値の取得
	hashValue := h.Sum(nil) // nil を渡すと、ハッシュ値のみが返される

	fmt.Printf("%x\n", hashValue) // 16進数文字列で出力
}

3. hash.Hash インターフェースと Sum メソッド

hash.Hash インターフェースは、Go言語におけるハッシュ関数の共通インターフェースを定義しています。主要なメソッドは以下の通りです。

  • New() hash.Hash: ハッシュアルゴリズムの新しいインスタンスを返します。
  • Write(p []byte) (n int, err error): データをハッシュ計算器に書き込みます。
  • Sum(b []byte) []byte: 現在のハッシュ計算器の状態に基づいてハッシュ値を計算し、b に追加して返します。bnil の場合、ハッシュ値のみが返されます。このメソッドはハッシュ計算器の状態をリセットしません。
  • Reset(): ハッシュ計算器を初期状態にリセットします。
  • Size() int: ハッシュ値のバイト長を返します。
  • BlockSize() int: ハッシュアルゴリズムのブロックサイズを返します。

Sum(b []byte) []byte メソッドは、既存のバイトスライス b にハッシュ値を追加するという設計になっています。これは、例えば複数のハッシュ値を連結して一つのバイトスライスに格納したい場合などに便利ですが、単にハッシュ値だけが欲しい場合には nil を渡す必要があります。

4. トップレベルの Sum 関数と Sum メソッドの違い

このコミットで追加されるトップレベルの Sum 関数は、crypto/sha1.Sum(data []byte) [Size]byte のようなシグネチャを持ちます。これは、sha1.New().Write(data).Sum(nil) の一連の操作を内部でカプセル化し、直接ハッシュ値を [Size]byte 型(固定長配列)で返します。

一方、既存の (d *digest) Sum(in []byte) []byte メソッドは、digest 型のレシーバを持つメソッドであり、hash.Hash インターフェースの一部として機能します。このメソッドは、ハッシュ計算器の現在の状態に基づいてハッシュ値を計算し、引数 in にそのハッシュ値を追加して新しいスライスとして返します。

新しいトップレベルの Sum 関数は、より高レベルで使いやすいAPIを提供し、ハッシュオブジェクトの管理をユーザーから隠蔽します。

技術的詳細

このコミットの技術的な核心は、crypto/sha1 パッケージにおけるハッシュ計算の内部構造の整理と、それを利用した簡潔なAPIの提供です。

1. checkSum() メソッドの導入

コミット前は、*digest 型の Sum メソッドが、パディング処理、ハッシュ計算、そして結果のバイトスライスへの追加という複数の役割を担っていました。このコミットでは、ハッシュ計算の最終段階(パディングとハッシュ値の生成)を checkSum() という新しいプライベートメソッドに切り出しています。

  • func (d *digest) checkSum() [Size]byte:
    • このメソッドは、digest 構造体の現在の状態(内部のハッシュ値 h と処理されたデータの長さ len)に基づいて、SHA-1の最終的なパディング処理とハッシュ値の計算を行います。
    • 計算されたハッシュ値は、[Size]byte 型(Size はSHA-1のハッシュ値のバイト長、20バイト)の固定長配列として返されます。これにより、ハッシュ値が常に固定長であることが型レベルで保証されます。
    • このメソッドは、ハッシュ計算器の状態を変更しないため、Sum メソッドが呼び出された後も digest オブジェクトを再利用して追加のデータを処理し、再度 Sum を呼び出すことが可能です(ただし、Sum メソッド自体は d0 のコピーに対して操作を行うため、元の d0 は変更されません)。

2. (d *digest) Sum(in []byte) []byte メソッドの変更

既存の Sum メソッドは、新しく導入された checkSum() メソッドを利用するように変更されました。

  • 変更前: Sum メソッド内で直接パディングとハッシュ値の計算を行っていました。
  • 変更後: Sum メソッドは d.checkSum() を呼び出してハッシュ値を取得し、その結果を引数 in に追加して返します。これにより、Sum メソッドのロジックが簡素化され、ハッシュ計算のコアロジックが checkSum() に集約されました。

3. トップレベルの Sum(data []byte) [Size]byte 関数の追加

これがこのコミットの主要な変更点です。

  • func Sum(data []byte) [Size]byte:
    • この関数は、data という単一のバイトスライスを入力として受け取ります。
    • 内部では、新しい digest オブジェクトを作成し(var d digest)、d.Reset() で初期化します。
    • 次に、d.Write(data) を呼び出して入力データ全体をハッシュ計算器に書き込みます。
    • 最後に、d.checkSum() を呼び出して最終的なハッシュ値を計算し、それを [Size]byte 型で直接返します。
    • この関数は、ユーザーが New(), Write(), Sum() といった一連の操作を明示的に行う必要なく、データのハッシュ値を簡単に取得できるようにします。

4. テストの追加

新しいトップレベルの Sum 関数が正しく機能することを検証するために、sha1_test.go にテストケースが追加されています。既存の TestGolden 関数内で、Sum([]byte(g.in)) を呼び出し、その結果が期待されるゴールデン値と一致するかどうかを確認しています。これにより、新しいAPIの正確性が保証されます。

これらの変更により、crypto/sha1 パッケージは、ストリーム処理とワンショット処理の両方に対応できる、より柔軟で使いやすいAPIセットを提供することになりました。

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

src/pkg/crypto/sha1/sha1.go

--- a/src/pkg/crypto/sha1/sha1.go
+++ b/src/pkg/crypto/sha1/sha1.go
@@ -90,9 +90,13 @@ func (d *digest) Write(p []byte) (nn int, err error) {
 func (d0 *digest) Sum(in []byte) []byte {
 	// Make a copy of d0 so that caller can keep writing and summing.
 	d := *d0
+	hash := d.checkSum()
+	return append(in, hash[:]...)\
+}
 
-\t// Padding.  Add a 1 bit and 0 bits until 56 bytes mod 64.\
+func (d *digest) checkSum() [Size]byte {
 	len := d.len
+\t// Padding.  Add a 1 bit and 0 bits until 56 bytes mod 64.
 	var tmp [64]byte
 	tmp[0] = 0x80
 	if len%64 < 56 {
@@ -120,5 +124,13 @@ func (d0 *digest) Sum(in []byte) []byte {
 	\tdigest[i*4+3] = byte(s)
 	}
 
-\treturn append(in, digest[:]...)\
+	return digest
+}\
+
+// Sum returns the SHA1 checksum of the data.
+func Sum(data []byte) [Size]byte {
+	var d digest
+	d.Reset()
+	d.Write(data)
+	return d.checkSum()
 }

src/pkg/crypto/sha1/sha1_test.go

--- a/src/pkg/crypto/sha1/sha1_test.go
+++ b/src/pkg/crypto/sha1/sha1_test.go
@@ -54,6 +54,10 @@ var golden = []sha1Test{
 func TestGolden(t *testing.T) {
 	for i := 0; i < len(golden); i++ {
 		g := golden[i]
+		s := fmt.Sprintf("%x", Sum([]byte(g.in)))
+		if s != g.out {
+			t.Fatalf("Sum function: sha1(%s) = %s want %s", g.in, s, g.out)
+		}
 		c := New()
 		for j := 0; j < 3; j++ {
 			if j < 2 {

コアとなるコードの解説

src/pkg/crypto/sha1/sha1.go の変更点

  1. checkSum() メソッドの追加:

    • func (d *digest) checkSum() [Size]byte という新しいメソッドが追加されました。
    • このメソッドは、SHA-1ハッシュ計算の最終段階であるパディング処理と、内部状態 h から最終的な20バイトのハッシュ値を生成するロジックをカプセル化しています。
    • 戻り値の型が [Size]byte (固定長配列) であるため、ハッシュ値が常に20バイトであることが明確になります。
  2. Sum メソッドの変更:

    • 既存の func (d0 *digest) Sum(in []byte) []byte メソッドは、checkSum() を呼び出すように変更されました。
    • 変更前は、このメソッド内でパディングとハッシュ値の計算を行っていましたが、変更後は hash := d.checkSum() でハッシュ値を取得し、append(in, hash[:]...) でそのハッシュ値を in スライスに追加して返します。これにより、Sum メソッドの役割が「ハッシュ値を計算して既存のスライスに追加する」ことに特化され、コードの責務が明確になりました。
  3. トップレベルの Sum 関数の追加:

    • func Sum(data []byte) [Size]byte という新しいパッケージレベルの関数が追加されました。
    • この関数は、ユーザーが最もシンプルにSHA-1ハッシュを計算したい場合に利用されます。
    • 内部では、var d digest で新しいハッシュ計算器のインスタンスを作成し、d.Reset() で初期化します。
    • 次に、d.Write(data) で入力データ全体を一度に書き込みます。
    • 最後に、d.checkSum() を呼び出して最終的なハッシュ値を計算し、それを直接 [Size]byte 型で返します。
    • この関数により、sha1.New().Write(data).Sum(nil) といった一連の操作を記述することなく、sha1.Sum(data) だけでハッシュ値を取得できるようになり、APIの使いやすさが大幅に向上しました。

src/pkg/crypto/sha1/sha1_test.go の変更点

  1. TestGolden 関数へのテストケース追加:
    • 既存の TestGolden 関数内に、新しいトップレベルの Sum 関数をテストするためのコードが追加されました。
    • s := fmt.Sprintf("%x", Sum([]byte(g.in))) の行で、golden テストデータ (g.in) を新しい Sum 関数に渡し、その結果を16進数文字列にフォーマットしています。
    • if s != g.out の条件で、計算されたハッシュ値が期待されるゴールデン値 (g.out) と一致するかどうかを検証しています。
    • これにより、新しい Sum 関数が既存のSHA-1実装と互換性があり、正しくハッシュ値を計算できることが保証されます。

これらの変更は、Go言語の標準ライブラリにおけるAPI設計のベストプラクティスを示しています。すなわち、低レベルで柔軟なインターフェース(hash.Hash)を提供しつつ、一般的なユースケースに対してはより簡潔で使いやすい高レベルな関数(トップレベルの Sum)も提供するというアプローチです。

関連リンク

参考にした情報源リンク

  • Go言語の公式ドキュメント
  • SHA-1に関する一般的な情報 (例: Wikipedia)
  • Go言語のハッシュ関数の一般的な使用パターンに関する知識