[インデックス 17481] ファイルの概要
このコミットは、Go言語の標準ライブラリ crypto/rand
パッケージにおける Read
関数の実装変更に関するものです。具体的には、rand.Reader
からバイト列を読み込む際に、内部的に io.ReadFull
を使用するように修正されています。これにより、指定されたバイト数すべてが確実に読み込まれることが保証され、暗号論的に安全な乱数生成の堅牢性が向上します。
コミット
commit c327e82ddb306fb4730a0fe624553d9e45ebde60
Author: Brad Fitzpatrick <bradfitz@golang.org>
Date: Fri Sep 6 12:00:27 2013 -0700
crypto/rand: make Read use io.ReadFull
Fixes #6084
R=golang-dev, rsc, dave
CC=golang-dev
https://golang.org/cl/13523044
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/c327e82ddb306fb4730a0fe624553d9e45ebde60
元コミット内容
crypto/rand: make Read use io.ReadFull
Fixes #6084
R=golang-dev, rsc, dave
CC=golang-dev
https://golang.org/cl/13523044
変更の背景
この変更は、Go Issue #6084 に対応するものです。crypto/rand
パッケージの Read
関数は、暗号論的に安全な乱数を生成するために使用されます。従来の rand.Read(b)
は、内部で rand.Reader.Read(b)
を直接呼び出していました。しかし、io.Reader
インターフェースの Read
メソッドは、要求されたバイト数よりも少ないバイト数を読み込んで成功を返す可能性があります。これは、特に暗号用途において問題となります。暗号用途では、指定された長さの乱数バイトが完全に取得されることが極めて重要であり、部分的な読み込みはセキュリティ上の脆弱性につながる可能性があります。
この問題を解決するため、io.ReadFull
関数が導入されました。io.ReadFull
は、指定されたバイト数すべてが読み込まれるまで Read
メソッドを繰り返し呼び出すことを保証します。もし指定されたバイト数に満たない場合、またはエラーが発生した場合は、エラーを返します。これにより、crypto/rand.Read
が常に要求された量の乱数バイトを供給することが保証され、暗号論的強度が維持されます。
前提知識の解説
crypto/rand
パッケージ
crypto/rand
パッケージは、Go言語において暗号論的に安全な乱数(CSPRNG: Cryptographically Secure Pseudo-Random Number Generator)を生成するための機能を提供します。これは、鍵生成、セッションID、ノンス(nonce)など、セキュリティが要求される様々な場面で利用されます。このパッケージが提供する乱数は、予測不可能であり、統計的な偏りがなく、攻撃者が将来の出力を予測することを困難にするように設計されています。
crypto/rand
パッケージの主要な要素は以下の通りです。
rand.Reader
: これはio.Reader
インターフェースを満たすグローバルな変数で、オペレーティングシステムが提供する暗号論的に安全な乱数源(例: Linuxの/dev/urandom
、WindowsのCryptGenRandom
API)からバイトを読み込みます。rand.Read(b []byte)
: この関数は、rand.Reader
からb
の長さ分の暗号論的に安全な乱数バイトを読み込み、b
に格納します。
io.Reader
インターフェース
Go言語の io.Reader
インターフェースは、データを読み込むための基本的な抽象化を提供します。その定義は以下の通りです。
type Reader interface {
Read(p []byte) (n int, err error)
}
Read
メソッドは、p
に最大 len(p)
バイトを読み込み、読み込んだバイト数 n
とエラー err
を返します。重要なのは、Read
メソッドが len(p)
バイトすべてを読み込むことを保証しない点です。例えば、ネットワークの遅延やファイルの終端など、様々な理由で要求されたバイト数よりも少ないバイト数を読み込んで成功を返すことがあります。
io.ReadFull
関数
io.ReadFull
関数は、io
パッケージで提供されるヘルパー関数で、io.Reader
から指定されたバイト数すべてを読み込むことを保証します。そのシグネチャは以下の通りです。
func ReadFull(r Reader, buf []byte) (n int, err error)
ReadFull
は、r
から len(buf)
バイトを buf
に読み込もうとします。もし len(buf)
バイトすべてが読み込まれる前に r
がEOF(End Of File)に達した場合、または他のエラーが発生した場合、ReadFull
は io.ErrUnexpectedEOF
または元のエラーを返します。これにより、呼び出し元は、要求されたバイト数すべてが正常に読み込まれたかどうかを確実に判断できます。
技術的詳細
このコミットの技術的詳細を掘り下げると、crypto/rand.Read
が io.ReadFull
を利用することで、暗号論的乱数生成の信頼性と安全性がどのように向上するかが明確になります。
-
部分読み込みの防止: 従来の
rand.Read
はrand.Reader.Read
を直接呼び出していました。rand.Reader
はOSの乱数源に依存しており、OSの乱数源が一時的に枯渇したり、ブロックされたりする状況では、Read
メソッドが要求されたバイト数よりも少ないバイト数を返して成功する可能性がありました。暗号用途では、例えば128ビットの鍵を生成しようとして、実際には64ビットしか取得できなかった場合、その鍵は意図したセキュリティレベルを満たしません。io.ReadFull
を使用することで、rand.Read
は常に要求されたバイト数(len(b)
)を読み込むまでブロックするか、エラーを返すようになります。これにより、不完全な乱数バイトが使用されるリスクが排除されます。 -
エラーハンドリングの改善:
io.ReadFull
は、指定されたバイト数を読み込めなかった場合に明確なエラー(io.ErrUnexpectedEOF
または基底のRead
エラー)を返します。これにより、crypto/rand.Read
の呼び出し元は、乱数生成が成功したかどうかをより確実に判断できるようになります。暗号システムでは、乱数生成の失敗は致命的な問題につながるため、このような堅牢なエラーハンドリングは不可欠です。 -
Exampleコードの修正:
example_test.go
の変更は、この内部的な変更がcrypto/rand.Read
の外部APIに影響を与えないことを示しています。以前はio.ReadFull(rand.Reader, b)
を直接呼び出していた例が、新しいrand.Read(b)
を呼び出すように変更されています。これは、crypto/rand.Read
がio.ReadFull
の保証を内部的に提供するようになったため、ユーザーはより簡潔なAPIを使用できるようになったことを意味します。
この変更は、Go言語の暗号ライブラリが、潜在的なエッジケースやOSの乱数源の挙動に起因するセキュリティリスクを軽減するための、細心の注意を払った設計原則に従っていることを示しています。暗号システムにおいては、たとえ小さな不確実性であっても、それが大きな脆弱性につながる可能性があるため、このような細部の改善が非常に重要です。
コアとなるコードの変更箇所
このコミットにおけるコアとなるコードの変更は、src/pkg/crypto/rand/rand.go
ファイル内の Read
関数の実装です。
--- a/src/pkg/crypto/rand/rand.go
+++ b/src/pkg/crypto/rand/rand.go
@@ -14,5 +14,8 @@ import "io"
// On Windows systems, Reader uses the CryptGenRandom API.
var Reader io.Reader
-// Read is a helper function that calls Reader.Read.
-func Read(b []byte) (n int, err error) { return Reader.Read(b) }
+// Read is a helper function that calls Reader.Read using io.ReadFull.
+// On return, n == len(b) if and only if err == nil.
+func Read(b []byte) (n int, err error) {
+ return io.ReadFull(Reader, b)
+}
また、関連する変更として、src/pkg/crypto/rand/example_test.go
ファイルのExampleコードも修正されています。
--- a/src/pkg/crypto/rand/example_test.go
+++ b/src/pkg/crypto/rand/example_test.go
@@ -8,7 +8,6 @@ import (
"bytes"
"crypto/rand"
"fmt"
-\t"io"
)
// This example reads 10 cryptographically secure pseudorandom numbers from
@@ -16,7 +15,7 @@ import (
func ExampleRead() {
c := 10
b := make([]byte, c)
-\t_, err := io.ReadFull(rand.Reader, b)
+\t_, err := rand.Read(b)
if err != nil {
fmt.Println("error:", err)
return
コアとなるコードの解説
src/pkg/crypto/rand/rand.go
の変更
-
変更前:
func Read(b []byte) (n int, err error) { return Reader.Read(b) }
この行は、
crypto/rand.Read
関数が、グローバルなrand.Reader
のRead
メソッドを直接呼び出していることを示しています。前述の通り、io.Reader.Read
は部分的な読み込みを許容するため、b
の全バイトが読み込まれる保証はありませんでした。 -
変更後:
func Read(b []byte) (n int, err error) { return io.ReadFull(Reader, b) }
この変更により、
crypto/rand.Read
はio.ReadFull
関数を介してrand.Reader
からバイトを読み込むようになりました。io.ReadFull(Reader, b)
は、b
の長さ分のバイトがReader
から完全に読み込まれるまで処理を続行するか、エラーを返します。これにより、crypto/rand.Read
が常に要求された量の暗号論的に安全な乱数バイトを供給することが保証されます。 新しいコメント// On return, n == len(b) if and only if err == nil.
は、この変更によってRead
関数の振る舞いがより厳密になったことを明確に示しています。つまり、エラーが発生しない限り、常にb
の全バイトが読み込まれることを保証します。
src/pkg/crypto/rand/example_test.go
の変更
-
変更前:
_, err := io.ReadFull(rand.Reader, b)
Exampleコードでは、
crypto/rand.Read
の内部的な振る舞いを模倣するように、直接io.ReadFull
を呼び出していました。 -
変更後:
_, err := rand.Read(b)
crypto/rand.Read
関数自体がio.ReadFull
の保証を提供するようになったため、Exampleコードはより簡潔にrand.Read(b)
を呼び出すように変更されました。これにより、ユーザーはcrypto/rand
パッケージのAPIをより直感的に使用できるようになります。また、import "io"
が不要になったことも示されています。
これらの変更は、Go言語の暗号ライブラリが、セキュリティと堅牢性を向上させるために、内部実装をより厳密に制御していることを示しています。
関連リンク
- Go Issue #6084: https://github.com/golang/go/issues/6084
- Go CL 13523044: https://golang.org/cl/13523044
- Go
crypto/rand
package documentation: https://pkg.go.dev/crypto/rand - Go
io
package documentation: https://pkg.go.dev/io
参考にした情報源リンク
- Go言語の公式ドキュメント (
pkg.go.dev
) - GitHubのGo言語リポジトリのコミット履歴とIssueトラッカー
io.Reader
とio.ReadFull
の一般的なGo言語の解説記事- 暗号論的に安全な乱数生成に関する一般的な情報