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

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

このコミットは、Go言語の標準ライブラリ crypto/rand パッケージがPlan 9オペレーティングシステム上で動作するように修正するものです。具体的には、暗号論的に安全な乱数ジェネレータである rand.Reader がPlan 9環境で適切に初期化され、利用できるようになります。

コミット

commit 3476c2312492947e1c300921c5d4f1bce0e07ef5
Author: Markus Sonderegger <marraison@gmail.com>
Date:   Wed Jun 6 16:05:47 2012 -0400

    crypto/rand: enable rand.Reader on plan9
    
    R=golang-dev, rsc
    CC=golang-dev
    https://golang.org/cl/6297044

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

https://github.com/golang/go/commit/3476c2312492947e1c300921c5d4f1bce0e07ef5

元コミット内容

crypto/rand: enable rand.Reader on plan9

変更の背景

Go言語の crypto/rand パッケージは、暗号論的に安全な乱数を生成するための機能を提供します。Unix系のシステム(Linux, macOS, FreeBSDなど)では、通常 /dev/urandom/dev/random といった特殊なデバイスファイルからシステムエントロピー(乱雑さの源)を読み取ることで乱数を生成します。

しかし、Plan 9オペレーティングシステムは、Unixとは異なる設計思想を持つため、/dev/urandom のような標準的なデバイスファイルが存在しません。このコミット以前は、crypto/rand パッケージのUnix固有の実装 (rand_unix.go) はPlan 9向けにビルドされず、結果としてPlan 9環境では rand.Reader が利用できませんでした。

この変更の背景には、Go言語がより多くのプラットフォームをサポートし、特にPlan 9のようなニッチな、しかし重要なシステムでも暗号機能を活用できるようにするという意図があります。Plan 9は、ベル研究所で開発された分散オペレーティングシステムであり、その「すべてがファイルである」という哲学は、Go言語の設計思想とも一部共通する点があります。このコミットは、Plan 9上でのGoアプリケーションがセキュアな乱数を必要とする場合に、その基盤を提供することを目的としています。

前提知識の解説

1. crypto/rand パッケージと rand.Reader

Go言語の crypto/rand パッケージは、暗号論的に安全な乱数ジェネレータ (CSPRNG) を提供します。これは、予測不可能な乱数を生成するために使用され、セキュリティが重要なアプリケーション(鍵生成、セッションID生成など)で不可欠です。 rand.Reader は、io.Reader インターフェースを満たすグローバルな変数であり、このパッケージの主要なエントリポイントです。アプリケーションは rand.Reader.Read(b []byte) を呼び出すことで、暗号論的に安全な乱数バイトを b に読み込むことができます。

2. /dev/urandom

/dev/urandom は、LinuxやmacOSなどのUnix系オペレーティングシステムに存在する特殊なデバイスファイルです。これは、システムが収集したエントロピー(マウスの動き、キーボード入力、ディスクI/O、ネットワークアクティビティなど)を基に、暗号論的に安全な擬似乱数を生成して提供します。/dev/urandom はブロックせず、常に乱数を供給し続けるため、多くのアプリケーションで利用されます。

3. Plan 9 オペレーティングシステム

Plan 9 from Bell Labsは、Unixの設計者の一部によって開発された分散オペレーティングシステムです。その最も特徴的な設計原則は「すべてがファイルである」というもので、システム内のあらゆるリソース(プロセス、ネットワーク接続、デバイスなど)がファイルシステムを通じてアクセスされます。この哲学は、Unixの「すべてがファイルである」という考え方をさらに推し進めたものです。 Plan 9は、Unixとは異なるカーネルアーキテクチャとシステムコールインターフェースを持ち、デバイスファイルの命名規則やアクセス方法も異なります。そのため、Unixで一般的な /dev/urandom のようなパスはPlan 9には存在しません。

4. Goのビルドタグ (+build)

Go言語では、ソースファイルの先頭に +build ディレクティブを記述することで、特定の環境でのみそのファイルをコンパイルするように制御できます。例えば、// +build linux と書かれたファイルはLinux環境でのみコンパイルされます。この機能は、OS固有のコードやアーキテクチャ固有のコードを分離するために使用されます。

5. bufio.Reader

bufio.Reader は、Go言語の bufio パッケージが提供するバッファリングされたリーダーです。これは、基になる io.Reader からデータを読み取る際に、内部バッファを使用してI/O操作の回数を減らし、パフォーマンスを向上させます。特に、小さなチャンクで頻繁に読み取りを行う場合に効果的です。

技術的詳細

このコミットは、src/pkg/crypto/rand/rand_unix.go ファイルに対して行われています。このファイルは、Unix系のシステムにおける crypto/rand の実装を提供しています。

主な変更点は以下の通りです。

  1. ビルドタグへの plan9 の追加: ファイルの先頭にあるビルドタグ // +build darwin freebsd linux netbsd openbsdplan9 が追加されました。これにより、このファイルがPlan 9環境でもコンパイルされるようになります。

    --- a/src/pkg/crypto/rand/rand_unix.go
    +++ b/src/pkg/crypto/rand/rand_unix.go
    @@ -2,7 +2,7 @@
     // Use of this source code is governed by a BSD-style
     // license that can be found in the LICENSE file.
     
    -// +build darwin freebsd linux netbsd openbsd
    +// +build darwin freebsd linux netbsd openbsd plan9
    
  2. init() 関数での rand.Reader の条件付き初期化: init() 関数は、パッケージがロードされる際に自動的に実行されます。この関数内で、rand.Reader の初期化方法が runtime.GOOS (現在のオペレーティングシステム) に応じて分岐されるようになりました。

    • runtime.GOOS == "plan9" の場合: Reader = newReader(nil) が使用されます。newReader は、/dev/urandom が利用できないシステム向けの代替乱数ジェネレータを生成します。nil を渡すことで、システムエントロピー源に依存しない(または内部で適切なフォールバックを持つ)ソフトウェアベースのPRNGが初期化されることを示唆しています。
    • それ以外の場合(Unix系OS): 従来通り Reader = &devReader{name: "/dev/urandom"} が使用され、/dev/urandom から乱数を読み取ります。
    --- a/src/pkg/crypto/rand/rand_unix.go
    +++ b/src/pkg/crypto/rand/rand_unix.go
    @@ -22,7 +23,13 @@ import (
     // Easy implementation: read from /dev/urandom.
     // This is sufficient on Linux, OS X, and FreeBSD.
     
    -func init() { Reader = &devReader{name: "/dev/urandom"} }
    +func init() {
    +\tif runtime.GOOS == "plan9" {
    +\t\tReader = newReader(nil)
    +\t} else {
    +\t\tReader = &devReader{name: "/dev/urandom"}\
    +\t}\
    +}
    
  3. devReader.Read メソッドでの bufio.Reader の条件付き適用: devReader は、指定されたファイル(通常は /dev/urandom)からバイトを読み取る構造体です。その Read メソッド内で、ファイルディスクリプタ fbufio.NewReader(f) でラップするかどうかが runtime.GOOS に応じて分岐されるようになりました。

    • runtime.GOOS == "plan9" の場合: r.f = f となり、os.File オブジェクトが直接 r.f に代入されます。これは、Plan 9のファイルI/Oがバッファリングを必要としないか、あるいは bufio.Reader を使用すると問題が発生する可能性があることを示唆しています。Plan 9のファイルシステムは、Unixとは異なるセマンティクスを持つため、直接ファイルディスクリプタを扱う方が適切である場合があります。
    • それ以外の場合: r.f = bufio.NewReader(f) となり、bufio.Reader を介してバッファリングされた読み取りが行われます。これは、Unix系システムでの /dev/urandom からの読み取り効率を向上させるためです。
    --- a/src/pkg/crypto/rand/rand_unix.go
    +++ b/src/pkg/crypto/rand/rand_unix.go
    @@ -39,14 +46,17 @@ func (r *devReader) Read(b []byte) (n int, err error) {
     \t\tif f == nil {\
     \t\t\treturn 0, err\
     \t\t}\
    -\t\tr.f = bufio.NewReader(f)\
    +\t\tif runtime.GOOS == "plan9" {\
    +\t\t\tr.f = f\
    +\t\t} else {\
    +\t\t\tr.f = bufio.NewReader(f)\
    +\t\t}\
     \t}\
     \treturn r.f.Read(b)\
     }\
    

これらの変更により、Goの crypto/rand パッケージはPlan 9環境で適切に動作するようになり、Plan 9上で動作するGoアプリケーションがセキュアな乱数を生成できるようになります。

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

変更は src/pkg/crypto/rand/rand_unix.go ファイルに集中しています。

  1. ビルドタグの変更:

    // -build darwin freebsd linux netbsd openbsd
    // +build darwin freebsd linux netbsd openbsd plan9
    

    この変更により、rand_unix.go がPlan 9向けにコンパイルされるようになります。

  2. init() 関数の変更:

    func init() {
    	if runtime.GOOS == "plan9" {
    		Reader = newReader(nil) // Plan 9では代替の乱数ジェネレータを使用
    	} else {
    		Reader = &devReader{name: "/dev/urandom"} // それ以外のUnix系OSでは/dev/urandomを使用
    	}
    }
    

    rand.Reader の初期化ロジックがOSによって分岐されます。

  3. devReader.Read メソッド内のファイルリーダー初期化の変更:

    		if f == nil {
    			return 0, err
    		}
    		if runtime.GOOS == "plan9" {
    			r.f = f // Plan 9では直接os.Fileを使用
    		} else {
    			r.f = bufio.NewReader(f) // それ以外のUnix系OSではbufio.Readerでラップ
    		}
    

    devReader が内部で使用するファイルリーダーの型がOSによって分岐されます。

コアとなるコードの解説

このコミットの核心は、Goのクロスプラットフォーム対応におけるOS固有の挙動の吸収です。

  • ビルドタグの追加: これは、GoのビルドシステムがこのファイルをPlan 9のビルドに含めるための最も基本的なステップです。これにより、Plan 9環境で crypto/rand パッケージが利用可能になります。

  • init() 関数での条件分岐: crypto/rand パッケージは、rand.Reader というグローバルな io.Reader インターフェースを提供します。この Reader は、パッケージが初期化される際に、システムから乱数を読み取るための具体的な実装に設定されます。 Unix系システムでは、/dev/urandom が乱数源として非常に信頼性が高く、効率的です。そのため、devReader という構造体が /dev/urandom を開いて読み取るように設定されます。 しかし、Plan 9には /dev/urandom が存在しないため、このアプローチは使えません。そこで、runtime.GOOS == "plan9" の場合に newReader(nil) が呼び出されます。この newReader 関数は、おそらくPlan 9のシステムコールやファイルシステムを通じて利用可能なエントロピー源(もしあれば)を利用するか、あるいは完全にソフトウェアベースで乱数を生成するフォールバックメカニズムを提供します。nil を引数として渡すことで、newReader が内部で適切なデフォルトのエントロピー源を見つけるか、あるいは自己シード型の擬似乱数ジェネレータとして動作することを示唆しています。

  • devReader.Read メソッドでの bufio.Reader の条件分岐: devReader は、実際にファイルからバイトを読み取るロジックをカプセル化しています。Unix系システムでは、/dev/urandom からの読み取りは、小さなチャンクで頻繁に行われる可能性があります。このような場合、bufio.NewReader を使用して読み取りをバッファリングすることで、システムコール(ファイルI/O)の回数を減らし、パフォーマンスを向上させることができます。 一方、Plan 9では、ファイルI/Oのセマンティクスが異なるため、bufio.Reader を介したバッファリングが必ずしも適切でないか、あるいは不要である可能性があります。Plan 9のファイルシステムは、ネットワーク透過性や分散性を重視しており、ファイルディスクリプタを直接扱う方が効率的であるか、あるいは予期せぬ挙動を避けるために推奨される場合があります。この変更は、Plan 9のI/Oモデルに合わせた最適化、または互換性確保のための調整と考えられます。

このコミットは、Go言語が異なるOSの特性を考慮し、それぞれのプラットフォームで最適なパフォーマンスと正しい動作を保証するための典型的な例を示しています。

関連リンク

参考にした情報源リンク

  • Go言語のソースコード (特に src/pkg/crypto/rand/)
  • Unix系OSにおける /dev/urandom の概念に関する一般的な情報
  • Plan 9オペレーティングシステムの設計原則に関する一般的な情報
  • Go言語のビルドタグに関するドキュメント
  • Go言語の io パッケージと bufio パッケージに関するドキュメント
  • Go CL 6297044: https://golang.org/cl/6297044 (コミットメッセージに記載されている変更リストへのリンク)