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

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

このコミットは、Go言語の標準ライブラリ crypto/hmac パッケージにおける変更を扱っています。具体的には、hmac.NewMD5hmac.NewSHA1hmac.NewSHA256 といった特定のハッシュ関数に特化したHMAC生成関数を非推奨とし、より汎用的な hmac.New 関数を使用するように変更を促すものです。この変更は、不要なハッシュ関数のリンクを防ぎ、コードのモジュール性を高めることを目的としています。また、既存のコードベースを新しいAPIに自動的に移行するための gofix ツール用のルールも追加されています。

コミット

commit 8d66a416cb4b84abeaeccaa69dda3783dda1b76a
Author: Luit van Drongelen <luitvd@gmail.com>
Date:   Thu Jan 19 17:28:38 2012 -0500

    crypto/hmac: Deprecate hmac.NewMD5, hmac.NewSHA1 and hmac.NewSHA256
    
    Remove NewMD5, NewSHA1 and NewSHA256 in favor of using New and
    explicitly importing the used hash-function. This way when using, for
    example, HMAC with RIPEMD there's no md5, sha1 and sha256 linked in
    through the hmac package.
    
    A gofix rule is included, and applied to the standard library (3 files
    altered).
    
    This change is the result of a discussion at
    https://golang.org/cl/5550043/ to pull the discussion about
    deprecating these functions out of that issue.
    
    R=golang-dev, agl
    CC=golang-dev, r, rsc
    https://golang.org/cl/5556058

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

https://github.com/golang/go/commit/8d66a416cb4b84abeaeccaa69dda3783dda1b76a

元コミット内容

crypto/hmac パッケージにおいて、hmac.NewMD5hmac.NewSHA1hmac.NewSHA256 関数を非推奨とし、削除します。代わりに、より汎用的な hmac.New 関数を使用し、必要なハッシュ関数(例: md5.Newsha1.Newsha256.New)を明示的にインポートして渡す形式に移行します。これにより、hmac パッケージを介して不要なハッシュ関数(例えば、RIPEMDでHMACを使用する場合にMD5、SHA1、SHA256がリンクされること)がリンクされるのを防ぎます。

この変更に伴い、gofix ツールに新しいルールが追加され、標準ライブラリ内の既存のコード(3ファイル)に適用されました。この変更は、https://golang.org/cl/5550043/ で行われた議論から、これらの関数の非推奨化に関する議論を分離して行われたものです。

変更の背景

この変更の主な背景は、Go言語の標準ライブラリにおける依存関係の最適化とモジュール性の向上です。

  1. 不要な依存関係の排除: 以前の hmac.NewMD5hmac.NewSHA1hmac.NewSHA256 のような関数は、crypto/hmac パッケージ自体が特定のハッシュ関数(MD5, SHA1, SHA256)への依存性を持っていました。これは、例えばHMACをRIPEMDなどの他のハッシュ関数と組み合わせて使用する場合でも、MD5やSHA1、SHA256のコードが不必要に最終バイナリにリンクされてしまうことを意味しました。特に組み込みシステムやリソースが限られた環境では、このような不要なコードの増加は問題となります。
  2. APIの汎用化と柔軟性: hmac.New 関数は、hash.Hash インターフェースを実装する任意のハッシュ関数を受け入れるように設計されています。これにより、ユーザーはHMACで使用するハッシュ関数を自由に選択でき、将来的に新しいハッシュ関数が追加された場合でも、hmac パッケージのAPIを変更することなく対応できるようになります。これは、API設計における「開かれた/閉じた原則 (Open/Closed Principle)」に沿った改善と言えます。
  3. gofix ツールの活用: Go言語には、APIの変更に伴う既存コードの自動修正を支援する gofix ツールがあります。このコミットでは、非推奨化された関数から新しいAPIへの移行を容易にするために、専用の gofix ルールが作成され、標準ライブラリ内の関連ファイルに適用されました。これにより、開発者が手動でコードを修正する手間を省き、スムーズな移行を促進しています。
  4. コミュニティの議論: コミットメッセージに記載されているように、この変更はGoコミュニティ内での議論(https://golang.org/cl/5550043/)の結果として行われました。これは、Go言語の開発がオープンな議論と合意形成に基づいて進められていることを示しています。

これらの背景から、このコミットはGo言語のライブラリ設計におけるベストプラクティスを追求し、より効率的で柔軟な暗号化機能の提供を目指したものであると理解できます。

前提知識の解説

このコミットを理解するためには、以下の技術的な概念について知っておく必要があります。

  1. HMAC (Keyed-Hash Message Authentication Code):

    • HMACは、メッセージ認証コード (MAC) の一種で、秘密鍵とハッシュ関数を組み合わせてメッセージの完全性と認証性を保証するメカニズムです。
    • メッセージが改ざんされていないこと(完全性)と、メッセージが正当な送信者から送られたこと(認証性)を確認するために使用されます。
    • HMACは、任意の暗号学的ハッシュ関数(MD5, SHA-1, SHA-256など)と組み合わせて使用できます。
    • Go言語の crypto/hmac パッケージは、このHMACの機能を提供します。
  2. ハッシュ関数 (Cryptographic Hash Function):

    • 任意の長さの入力データ(メッセージ)を受け取り、固定長の短い出力(ハッシュ値、メッセージダイジェスト、フィンガープリントなどと呼ばれる)を生成する一方向関数です。
    • 暗号学的ハッシュ関数には、以下の特性が求められます。
      • 原像計算困難性 (Preimage Resistance): ハッシュ値から元のメッセージを計算するのが計算上困難であること。
      • 第二原像計算困難性 (Second Preimage Resistance): あるメッセージと同じハッシュ値を持つ別のメッセージを見つけるのが計算上困難であること。
      • 衝突困難性 (Collision Resistance): 異なる2つのメッセージが同じハッシュ値を持つペアを見つけるのが計算上困難であること。
    • 代表的なハッシュ関数には、MD5 (Message-Digest Algorithm 5)、SHA-1 (Secure Hash Algorithm 1)、SHA-256 (Secure Hash Algorithm 256) などがあります。Go言語では crypto/md5crypto/sha1crypto/sha256 などのパッケージで提供されます。
  3. Go言語の hash.Hash インターフェース:

    • Go言語の hash パッケージで定義されているインターフェースで、すべてのハッシュ関数が実装すべきメソッド(WriteSumResetSizeBlockSize)を定めています。
    • これにより、異なるハッシュ関数(MD5, SHA-1, SHA-256など)を統一的な方法で扱うことが可能になります。hmac.New 関数が func() hash.Hash 型の引数を受け取るのは、このインターフェースのおかげです。
  4. Go言語の gofix ツール:

    • gofix は、Go言語のツールチェーンの一部であり、GoのAPIが変更された際に、古いAPIを使用しているコードを新しいAPIに自動的に書き換えるためのユーティリティです。
    • Go言語の進化に伴い、APIの改善や変更が行われることがありますが、gofix を使用することで、開発者は手動での大規模なコード修正を避けることができます。
    • gofix は、AST (Abstract Syntax Tree) を操作してコードを変換します。
  5. Go言語のパッケージとインポート:

    • Go言語では、コードはパッケージに分割され、他のパッケージの機能を利用するには import キーワードを使って明示的にインポートする必要があります。
    • このコミットの変更は、hmac パッケージが特定のハッシュ関数パッケージ(crypto/md5 など)を直接インポートするのではなく、ユーザーが必要なハッシュ関数パッケージを明示的にインポートするように促すものです。これにより、最終的なバイナリに含まれるコードを最小限に抑えることができます。

これらの知識があれば、コミットがなぜ行われたのか、そしてその技術的な影響について深く理解することができます。

技術的詳細

このコミットの技術的詳細は、主に crypto/hmac パッケージのAPI変更と、それに伴う gofix ツールの実装に集約されます。

1. crypto/hmac パッケージのAPI変更

  • 変更前: crypto/hmac パッケージは、特定のハッシュ関数(MD5, SHA1, SHA256)に特化したHMAC生成関数を提供していました。

    func NewMD5(key []byte) hash.Hash
    func NewSHA1(key []byte) hash.Hash
    func NewSHA256(key []byte) hash.Hash
    

    これらの関数は、内部で対応するハッシュ関数パッケージ(crypto/md5crypto/sha1crypto/sha256)をインポートしていました。これにより、たとえユーザーがMD5やSHA1を使用しない場合でも、crypto/hmac をインポートするだけでこれらのハッシュ関数がリンクされてしまう可能性がありました。

  • 変更後: 上記の特定のハッシュ関数に特化した関数は削除されました。代わりに、汎用的な New 関数のみが残されました。

    func New(h func() hash.Hash, key []byte) hash.Hash
    

    この New 関数は、func() hash.Hash 型の引数 h を受け取ります。これは、hash.Hash インターフェースを実装する新しいハッシュインスタンスを生成する関数を意味します。例えば、MD5を使用したい場合は md5.New を、SHA1を使用したい場合は sha1.New を渡します。

    :

    • 変更前: hmac.NewMD5(key)
    • 変更後: hmac.New(md5.New, key)

    この変更により、crypto/hmac パッケージ自体は特定のハッシュ関数への直接的な依存性を持たなくなり、ユーザーが必要なハッシュ関数を明示的にインポートする責任を負うことになります。これにより、最終的なバイナリサイズを最適化し、不要なコードのリンクを防ぐことができます。

2. gofix ツールの実装 (src/cmd/gofix/hmacnew.go)

このコミットの重要な側面は、API変更に伴う既存コードの移行を自動化するために gofix ルールが追加されたことです。

  • hmacnew.go の役割: このファイルは、gofix ツールが実行される際に、古い hmac.NewMD5hmac.NewSHA1hmac.NewSHA256 の呼び出しパターンを検出し、新しい hmac.New 形式に自動的に書き換えるためのロジックを実装しています。

  • AST (Abstract Syntax Tree) の操作: gofix ツールは、Goのソースコードをパースして抽象構文木 (AST) を構築し、そのASTを操作することでコードの変換を行います。 hmacnew.go 内の hmacnew 関数は、以下の手順で変換を行います。

    1. crypto/hmac のインポートチェック: 変換対象のファイルが crypto/hmac パッケージをインポートしているかを確認します。インポートしていない場合は、変換の必要がないため処理を終了します。
    2. hmac.NewMD5, hmac.NewSHA1, hmac.NewSHA256 の検出: ASTを走査し、hmac.NewMD5(...)hmac.NewSHA1(...)hmac.NewSHA256(...) のような関数呼び出し (ast.CallExpr) を特定します。
    3. 必要なハッシュ関数のインポート追加: 特定された呼び出しに応じて、対応するハッシュ関数パッケージ(例: crypto/md5crypto/sha1crypto/sha256)のインポートをファイルに追加します。これは addImport ヘルパー関数によって行われます。
    4. 関数呼び出しの書き換え:
      • 関数名 (ce.Fun) を hmac.New に変更します。
      • 元の引数リストの先頭に、対応するハッシュ関数のコンストラクタ(例: md5.Newsha1.Newsha256.New)を追加します。
      • 例: hmac.NewMD5(key)hmac.New(md5.New, key) に変換されます。
  • hmacnew_test.go: このファイルには、hmacnew gofix ルールの動作を検証するためのテストケースが含まれています。入力コード (In) と期待される出力コード (Out) のペアが定義されており、gofix ルールが正しく変換を行うことを保証します。

3. 標準ライブラリへの適用

このコミットでは、gofix ルールが作成されただけでなく、Goの標準ライブラリ内の3つのファイル(src/pkg/crypto/tls/cipher_suites.gosrc/pkg/exp/ssh/transport.gosrc/pkg/net/smtp/auth.go)に実際に適用されています。これにより、標準ライブラリ自体が新しいAPIに準拠し、他の開発者への模範となります。

これらの技術的詳細から、このコミットが単なるAPIの変更に留まらず、Go言語のエコシステム全体(ライブラリ設計、自動コード変換ツール、標準ライブラリの整合性)を考慮した包括的な改善であることがわかります。

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

このコミットにおけるコアとなるコードの変更箇所は以下のファイルに集中しています。

  1. src/pkg/crypto/hmac/hmac.go:

    • crypto/md5, crypto/sha1, crypto/sha256 のインポートが削除されました。
    • NewMD5, NewSHA1, NewSHA256 関数が削除されました。
    • New 関数のコメントが更新され、crypto.Hash ではなく hash.Hash 型を使用することが明記されました。
    --- a/src/pkg/crypto/hmac/hmac.go
    +++ b/src/pkg/crypto/hmac/hmac.go
    @@ -9,9 +9,6 @@
     package hmac
     
     import (
    -	"crypto/md5"
    -	"crypto/sha1"
    -	"crypto/sha256"
     	"hash"
     )
     
    @@ -63,7 +60,7 @@ func (h *hmac) Reset() {
     	h.inner.Write(h.tmp[0:h.blocksize])
     }
     
    -// New returns a new HMAC hash using the given crypto.Hash type and key.
    +// New returns a new HMAC hash using the given hash.Hash type and key.
     func New(h func() hash.Hash, key []byte) hash.Hash {
     	hm := new(hmac)
     	hm.outer = h()
    @@ -81,12 +78,3 @@ func New(h func() hash.Hash, key []byte) hash.Hash {
     	hm.Reset()
     	return hm
     }
    -
    -// NewMD5 returns a new HMAC-MD5 hash using the given key.
    -func NewMD5(key []byte) hash.Hash { return New(md5.New, key) }
    -
    -// NewSHA1 returns a new HMAC-SHA1 hash using the given key.
    -func NewSHA1(key []byte) hash.Hash { return New(sha1.New, key) }
    -
    -// NewSHA256 returns a new HMAC-SHA256 hash using the given key.
    -func NewSHA256(key []byte) { return New(sha256.New, key) }
    
  2. src/cmd/gofix/hmacnew.go:

    • hmacnew という新しい gofix ルールが追加されました。
    • このルールは、hmac.NewMD5, hmac.NewSHA1, hmac.NewSHA256 の呼び出しを検出し、hmac.New と対応するハッシュ関数のコンストラクタ(例: md5.New)に書き換えます。
    • 同時に、必要なハッシュ関数パッケージのインポートを追加します。
    // Copyright 2011 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.
    
    package main
    
    import "go/ast"
    
    func init() {
    	register(hmacNewFix)
    }
    
    var hmacNewFix = fix{
    	"hmacnew",
    	"2012-01-19",
    	hmacnew,
    	`Deprecate hmac.NewMD5, hmac.NewSHA1 and hmac.NewSHA256.
    
    This fix rewrites code using hmac.NewMD5, hmac.NewSHA1 and hmac.NewSHA256 to
    use hmac.New:
    
    	hmac.NewMD5(key) -> hmac.New(md5.New, key)
    	hmac.NewSHA1(key) -> hmac.New(sha1.New, key)
    	hmac.NewSHA256(key) -> hmac.New(sha256.New, key)
    
    `,
    }
    
    func hmacnew(f *ast.File) (fixed bool) {
    	if !imports(f, "crypto/hmac") {
    		return
    	}
    
    	walk(f, func(n interface{}) {
    		ce, ok := n.(*ast.CallExpr)
    		if !ok {
    			return
    		}
    
    		var pkg string
    		switch {
    		case isPkgDot(ce.Fun, "hmac", "NewMD5"):
    			pkg = "md5"
    		case isPkgDot(ce.Fun, "hmac", "NewSHA1"):
    			pkg = "sha1"
    		case isPkgDot(ce.Fun, "hmac", "NewSHA256"):
    			pkg = "sha256"
    		default:
    			return
    		}
    
    		addImport(f, "crypto/"+pkg)
    
    		ce.Fun = ast.NewIdent("hmac.New")
    		ce.Args = append([]ast.Expr{ast.NewIdent(pkg + ".New")}, ce.Args...)
    
    		fixed = true
    	})
    
    	return
    }
    
  3. src/cmd/gofix/hmacnew_test.go:

    • hmacnew gofix ルールのテストケースが定義されています。これにより、変換ロジックが期待通りに動作することが保証されます。
  4. 標準ライブラリ内の既存コードの修正:

    • src/pkg/crypto/tls/cipher_suites.go
    • src/pkg/exp/ssh/transport.go
    • src/pkg/net/smtp/auth.go これらのファイルでは、hmac.NewSHA1hmac.NewMD5 の呼び出しが、hmac.New(sha1.New, ...)hmac.New(md5.New, ...) の形式に修正され、対応するハッシュ関数パッケージがインポートされています。

    例 (src/pkg/crypto/tls/cipher_suites.go の変更):

    --- a/src/pkg/crypto/tls/cipher_suites.go
    +++ b/src/pkg/crypto/tls/cipher_suites.go
    @@ -91,7 +91,7 @@ func macSHA1(version uint16, key []byte) macFunction {
     		copy(mac.key, key)
     		return mac
     	}
    -	return tls10MAC{hmac.NewSHA1(key)}
    +	return tls10MAC{hmac.New(sha1.New, key)}
     }
    

これらの変更箇所は、APIの非推奨化、自動移行ツールの実装、そして既存コードへの適用という一連のプロセスを示しています。

コアとなるコードの解説

このコミットのコアとなるコードは、src/cmd/gofix/hmacnew.go に実装された gofix ルールです。このルールは、Goの抽象構文木 (AST) を操作して、古い hmac 関数の呼び出しを新しい形式に変換します。

以下に、hmacnew.go の主要な部分を解説します。

package main

import "go/ast" // GoのASTを扱うためのパッケージ

func init() {
	register(hmacNewFix) // gofixツールにこの新しいルールを登録
}

var hmacNewFix = fix{
	"hmacnew",        // ルールの名前
	"2012-01-19",     // ルールが導入された日付
	hmacnew,          // 実際にコード変換を行う関数
	`Deprecate hmac.NewMD5, hmac.NewSHA1 and hmac.NewSHA256.
    // ... (変換内容の説明) ...
    `,
}

// hmacnew関数は、与えられたASTファイル (f) を走査し、hmac関連の呼び出しを修正する
func hmacnew(f *ast.File) (fixed bool) {
	// まず、ファイルが "crypto/hmac" パッケージをインポートしているかを確認
	// インポートしていなければ、修正の必要はないので早期リターン
	if !imports(f, "crypto/hmac") {
		return
	}

	// ASTを走査するためのヘルパー関数 'walk' を使用
	// 各ノード (n) をチェックし、修正が必要な 'ast.CallExpr' (関数呼び出し) を探す
	walk(f, func(n interface{}) {
		ce, ok := n.(*ast.CallExpr) // ノードが関数呼び出しのASTノードかを確認
		if !ok {
			return // 関数呼び出しでなければスキップ
		}

		var pkg string // どのハッシュ関数パッケージ (md5, sha1, sha256) が必要かを示す変数
		switch {
		// 関数呼び出しが "hmac.NewMD5" の形式かを確認
		case isPkgDot(ce.Fun, "hmac", "NewMD5"):
			pkg = "md5" // MD5が必要
		// 関数呼び出しが "hmac.NewSHA1" の形式かを確認
		case isPkgDot(ce.Fun, "hmac", "NewSHA1"):
			pkg = "sha1" // SHA1が必要
		// 関数呼び出しが "hmac.NewSHA256" の形式かを確認
		case isPkgDot(ce.Fun, "hmac", "NewSHA256"):
			pkg = "sha256" // SHA256が必要
		default:
			return // 上記のいずれでもなければスキップ
		}

		// 必要なハッシュ関数パッケージ (例: "crypto/md5") をファイルにインポート追加
		addImport(f, "crypto/"+pkg)

		// 関数呼び出しの関数名部分を "hmac.New" に変更
		ce.Fun = ast.NewIdent("hmac.New")

		// 元の引数リストの先頭に、対応するハッシュ関数のコンストラクタ (例: "md5.New") を追加
		// 例: hmac.NewMD5(key) -> hmac.New(md5.New, key)
		ce.Args = append([]ast.Expr{ast.NewIdent(pkg + ".New")}, ce.Args...)

		fixed = true // 修正が行われたことを示すフラグを立てる
	})

	return // 修正が行われたかどうかを返す
}

解説のポイント:

  • go/ast パッケージ: Goのソースコードは、go/parser パッケージでパースされると go/ast パッケージで定義された構造体で表現されるASTになります。gofix ツールは、このASTを直接操作することで、コードの構造的な変更を安全に行います。
  • init() 関数と register(): init() 関数はGoプログラムの起動時に自動的に実行される特殊な関数です。ここで register(hmacNewFix) を呼び出すことで、この hmacnew ルールが gofix ツールに認識され、利用可能になります。
  • fix 構造体: hmacNewFixfix 構造体のインスタンスで、ルールのメタデータ(名前、日付、説明)と、実際に変換を行う関数 (hmacnew) を保持します。
  • imports(f, "crypto/hmac"): このヘルパー関数は、現在のファイル f が指定されたパッケージ(ここでは crypto/hmac)をインポートしているかどうかをチェックします。これにより、無関係なファイルでの処理をスキップし、効率を高めます。
  • walk(f, func(n interface{}) {...}): walk はASTを深さ優先で走査するための一般的なヘルパー関数です。匿名関数が各ASTノード n に対して実行されます。
  • n.(*ast.CallExpr) と型アサーション: ninterface{} 型なので、(*ast.CallExpr) に型アサーションを行うことで、それが関数呼び出しのASTノードであるかどうかを確認します。
  • isPkgDot(ce.Fun, "hmac", "NewMD5"): このヘルパー関数は、関数呼び出しの対象 (ce.Fun) が hmac.NewMD5 のような パッケージ名.関数名 の形式であるかを効率的にチェックします。
  • addImport(f, "crypto/"+pkg): 変換によって新しく必要となるインポート(例: crypto/md5)を、ファイルのインポートリストに自動的に追加します。これは、手動での修正では忘れがちな重要なステップです。
  • ASTノードの変更:
    • ce.Fun = ast.NewIdent("hmac.New"): 関数呼び出しの識別子(関数名)を hmac.New に変更します。
    • ce.Args = append([]ast.Expr{ast.NewIdent(pkg + ".New")}, ce.Args...): これが最も重要な変更です。append を使って、新しい引数(ハッシュ関数のコンストラクタ、例: md5.New)を既存の引数リストの先頭に追加しています。

この hmacnew.go のコードは、GoのAST操作の典型的な例であり、大規模なコードベースでAPI変更を安全かつ自動的に適用するための強力なメカニズムを示しています。

関連リンク

  • Go言語の crypto/hmac パッケージ: https://pkg.go.dev/crypto/hmac
  • Go言語の gofix ツールに関するドキュメント (古い情報を含む可能性あり): gofix はGoのバージョンアップに伴い統合されたり、機能が変更されたりしています。一般的な情報はGoの公式ドキュメントやブログ記事を参照してください。
  • Go言語のAST (Abstract Syntax Tree) に関する情報:
  • Go言語のコードレビューシステム (Gerrit): コミットメッセージに記載されている golang.org/cl/ のリンクは、Goプロジェクトが使用しているGerritベースのコードレビューシステムへのリンクです。

参考にした情報源リンク

  • Go言語の公式ドキュメント: crypto/hmachashgo/ast などのパッケージに関する公式ドキュメントは、Go言語の標準ライブラリの動作を理解する上で最も信頼できる情報源です。
  • Go言語のソースコード: このコミット自体がGo言語のソースコードの一部であり、変更内容を直接確認することで詳細な理解が得られます。
  • Go言語のブログやコミュニティの議論: コミットメッセージに記載されているGerritのリンクは、この変更に至るまでの議論の経緯や背景を理解する上で非常に有用です。
  • HMACに関する一般的な情報: WikipediaやRFCなどの標準化文書は、HMACの概念と動作原理を理解するのに役立ちます。
  • Go言語の gofix ツールに関する記事: gofix の具体的な使用方法や内部動作について解説している技術ブログ記事なども参考になります。