[インデックス 15722] ファイルの概要
このコミットは、Goランタイムにおけるハッシュ関数の高速化とセキュリティ強化を目的としています。特に、IntelおよびAMDプロセッサが提供するAES (Advanced Encryption Standard) ハードウェア命令(AES-NI)を活用することで、ハッシュ計算のパフォーマンスを大幅に向上させています。また、ハッシュ衝突によるサービス拒否(DoS)攻撃を防ぐために、ハッシュ関数にランダムなシードを導入しています。
コミット
commit a5d4024139231ca10b5347d17bbf702cfdf5fd5b
Author: Keith Randall <khr@golang.org>
Date: Tue Mar 12 10:47:44 2013 -0700
runtime: faster & safer hash function
Uses AES hardware instructions on 386/amd64 to implement
a fast hash function. Incorporates a random key to
thwart hash collision DOS attacks.
Depends on CL#7548043 for new assembly instructions.
Update #3885
Helps some by making hashing faster. Go time drops from
0.65s to 0.51s.
R=rsc, r, bradfitz, remyoudompheng, khr, dsymonds, minux.ma, elias.naur
CC=golang-dev
https://golang.org/cl/7543043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a5d4024139231ca10b5347d17bbf702cfdf5fd5b
元コミット内容
runtime: faster & safer hash function
Uses AES hardware instructions on 386/amd64 to implement
a fast hash function. Incorporates a random key to
thwart hash collision DOS attacks.
Depends on CL#7548043 for new assembly instructions.
Update #3885
Helps some by making hashing faster. Go time drops from
0.65s to 0.51s.
R=rsc, r, bradfitz, remyoudompheng, khr, dsymonds, minux.ma, elias.naur
CC=golang-dev
https://golang.org/cl/7543043
変更の背景
この変更の主な背景には、Go言語のランタイムにおけるハッシュ関数の性能とセキュリティの課題がありました。
- 性能の向上: 従来のハッシュ関数はソフトウェアで実装されており、特に大量のデータをハッシュする際に性能ボトルネックとなる可能性がありました。コミットメッセージにあるように、「Go time drops from 0.65s to 0.51s」という具体的な性能改善が示されており、ハッシュ処理がGoプログラム全体の実行時間に与える影響が大きかったことが伺えます。
- ハッシュ衝突DoS攻撃への対策: ハッシュ関数は、異なる入力に対して異なる出力を生成することが理想ですが、悪意のある攻撃者が意図的に多数の入力に対して同じハッシュ値を生成させる「ハッシュ衝突」を引き起こす可能性があります。これにより、ハッシュテーブル(Goの
map
型など)の性能が著しく低下し、サービス拒否(DoS)状態に陥る可能性があります。このコミットでは、ハッシュ関数にランダムなシードを導入することで、攻撃者が事前に衝突を計算することを困難にし、この種の攻撃に対する耐性を高めています。 - ハードウェア支援の活用: 近年のCPUには、暗号化処理を高速化するための専用命令セット(AES-NIなど)が搭載されています。これらの命令を活用することで、ソフトウェア実装よりもはるかに高速にハッシュ計算を行うことが可能になります。このコミットは、これらのハードウェア機能をGoランタイムに統合し、性能とセキュリティの両面でメリットを享受しようとしています。
前提知識の解説
このコミットを理解するためには、以下の技術的な前提知識が必要です。
-
ハッシュ関数:
- 任意の長さの入力データ(メッセージ)を受け取り、固定長の出力データ(ハッシュ値、メッセージダイジェスト、フィンガープリントなどと呼ばれる)を生成する関数です。
- 主な特性として、入力が少しでも変わると出力が大きく変わる「雪崩効果」、異なる入力から同じ出力が生成される「衝突」が起こりにくいこと(衝突耐性)、一方向性(ハッシュ値から元の入力を復元するのが困難であること)などがあります。
- Go言語の
map
型(ハッシュマップ)は、キーのハッシュ値を使ってデータを格納・検索するため、ハッシュ関数の性能と衝突耐性が直接的にmap
の性能とセキュリティに影響します。
-
AES (Advanced Encryption Standard) ハードウェア命令 (AES-NI):
- IntelおよびAMDの最新のCPUに搭載されている、AES暗号化・復号化処理を高速化するための専用命令セットです。
AESENC
(AES Encrypt),AESENCLAST
(AES Encrypt Last),AESDEC
(AES Decrypt),AESDECLAST
(AES Decrypt Last),AESIMC
(AES Inverse Mix Columns),AESKEYGENASSIST
(AES Key Generation Assist) などの命令が含まれます。- これらの命令は、ソフトウェアでAESを実装するよりもはるかに少ないCPUサイクルで処理を完了できるため、暗号化処理だけでなく、ハッシュ関数など、同様のビット操作や巡回シフトを多用する処理の高速化にも利用されます。
-
CPUID命令:
- x86アーキテクチャのCPUが持つ命令で、プロセッサの機能や情報を取得するために使用されます。
- 特定のレジスタ(EAXなど)に値を設定してCPUID命令を実行すると、CPUがサポートする機能(例: SSE, AVX, AES-NIなど)に関する情報が他のレジスタ(EBX, ECX, EDXなど)に返されます。
- このコミットでは、
runtime·cpuid_ecx
とruntime·cpuid_edx
という変数にCPUIDの結果を格納し、AES-NI命令が利用可能かどうかをチェックするために使用しています。
-
ハッシュ衝突DoS攻撃:
- 攻撃者が、ハッシュテーブルの性能を意図的に低下させるために、多数の異なる入力データに対して同じハッシュ値(または少数のハッシュ値)を生成するような入力セットを作成し、それをサーバーに送信する攻撃です。
- ハッシュテーブルは、衝突が発生すると線形探索などの遅いアルゴリズムにフォールバックすることがあり、これによりCPU使用率が急増し、正当なリクエストを処理できなくなる可能性があります。
- ランダムなシードをハッシュ関数に導入することで、攻撃者が事前に衝突を計算することが困難になり、この種の攻撃に対する耐性が向上します。
-
Goランタイムとアセンブリ:
- Go言語のランタイムは、ガベージコレクション、スケジューラ、メモリ管理など、Goプログラムの実行を支える低レベルな機能を提供します。
- 性能が重視される部分や、特定のハードウェア機能を利用する部分では、Goのコードではなく、アセンブリ言語で直接実装されることがあります。このコミットの
asm_386.s
やasm_amd64.s
がこれに該当します。 - Goのアセンブリは、Plan 9アセンブラの構文に似ており、一般的なx86アセンブラとは異なる表記規則を持ちます。
-
ELF AT_RANDOM:
- Linuxシステムにおいて、ELF (Executable and Linkable Format) 実行ファイルが起動される際に、カーネルがプロセスに渡す補助ベクトル(Auxiliary Vector)の一つです。
AT_RANDOM
は、セキュリティ目的で使用されるランダムなバイト列へのポインタを提供します。これにより、アプリケーションは/dev/urandom
などのデバイスファイルを開くことなく、起動時にカーネルから提供される高品質な乱数を取得できます。これは、特にサンドボックス化された環境や、ファイルシステムへのアクセスが制限される環境で有用です。
-
Windows CryptGenRandom:
- Windows APIの一部で、暗号学的に強力な乱数を生成するために使用されます。
CryptAcquireContext
関数で暗号サービスプロバイダ(CSP)のハンドルを取得し、そのハンドルを使ってCryptGenRandom
を呼び出すことで乱数を生成します。- このコミットでは、Windows環境でハッシュ関数のランダムシードを生成するために利用されています。
技術的詳細
このコミットの技術的な核心は、Goランタイムのハッシュ関数をAES-NI命令セットを利用するように変更し、さらにランダムなシードを導入した点にあります。
-
AES-NIによるハッシュ関数の実装:
src/pkg/runtime/asm_386.s
とsrc/pkg/runtime/asm_amd64.s
に、runtime·aeshash
、runtime·aeshashstr
、runtime·aeshash32
、runtime·aeshash64
といった新しいアセンブリ関数が追加されました。これらは、それぞれ任意のメモリブロック、文字列、32ビット整数、64ビット整数のハッシュを計算します。- これらの関数は、内部的に
runtime·aeshashbody
を呼び出します。このaeshashbody
がAES-NI命令(特にAESENC
)を駆使してハッシュ計算を行います。 - 基本的なアイデアは、入力データをAESのブロック暗号のように扱い、ハッシュ値を「状態」として、入力ブロックと状態を組み合わせて
AESENC
命令で繰り返し処理することで、最終的なハッシュ値を生成するというものです。 PINSRD
(Packed Insert Doubleword) やPINSRQ
(Packed Insert Quadword) 命令は、XMMレジスタ(SSE命令で使用される128ビットレジスタ)の特定の位置にデータを挿入するために使用されます。これにより、シード値や入力データの一部をハッシュ計算の初期状態に効率的に組み込むことができます。PSHUFB
(Packed Shuffle Bytes) 命令は、バイト単位のシャッフルを行う命令で、部分的なブロックの処理や、アライメントされていないデータからの読み込み後のバイト操作に利用されます。特に、入力データの残りの部分(16バイト未満の「partial block」)を効率的に処理するために使用されます。
-
CPU機能の検出と動的なハッシュ関数の選択:
src/pkg/runtime/alg.c
のruntime·hashinit
関数が、Goランタイムの初期化時に呼び出されます。- この関数は、
CPUID
命令の結果(runtime·cpuid_ecx
)をチェックし、CPUがAES-NI ((1 << 25)
)、SSE3 ((1 << 9)
,PSHUFB
のために必要)、SSE4.1 ((1 << 19)
,PINSRD/PINSRQ
のために必要) の各命令セットをサポートしているかを確認します。 - もしこれらの命令がすべて利用可能であれば、
runtime·algarray
というテーブルのエントリを、新しく実装されたAES-NIベースのハッシュ関数(runtime·aeshash
,runtime·aeshash32
,runtime·aeshash64
,runtime·aeshashstr
)に置き換えます。これにより、Goのmap
操作などでこれらの高速なハッシュ関数が自動的に使用されるようになります。 - 利用できない場合は、従来のソフトウェアベースのハッシュ関数が引き続き使用されます。
-
ランダムシードの導入:
- ハッシュ衝突DoS攻撃を防ぐため、
runtime·hashinit
関数内でハッシュ関数にランダムなシード(runtime·aeskeysched
)が初期化されます。 - このシードは、システムから提供される乱数データを使用して生成されます。
- Linux:
src/pkg/runtime/signal_linux_386.c
およびsrc/pkg/runtime/vdso_linux_amd64.c
で、ELF補助ベクトルのAT_RANDOM
からランダムデータを取得しようとします。これが利用できない場合は、/dev/urandom
から読み込みます。 - macOS (Darwin)/FreeBSD/NetBSD/OpenBSD:
src/pkg/runtime/thread_darwin.c
などの各OS固有のファイルで、/dev/urandom
からランダムデータを読み込みます。 - Windows:
src/pkg/runtime/thread_windows.c
で、Windows CryptoAPIのCryptAcquireContextW
とCryptGenRandom
関数を使用して乱数を生成します。
- Linux:
- 取得したランダムデータが
HashRandomBytes
(32バイト)に満たない場合、runtime·nanotime()
(現在のナノ秒単位の時刻)をシードとして使用し、不足分を埋めます。これは、完全にランダムではないものの、全くランダムでないよりはマシという考えに基づいています。 - このランダムシードは、ハッシュ計算の初期状態に組み込まれることで、同じ入力データであっても、プログラムの実行ごとに異なるハッシュ値が生成される可能性があり、攻撃者が事前に衝突を計算することを困難にします。
- ハッシュ衝突DoS攻撃を防ぐため、
-
ベンチマークの追加:
src/pkg/runtime/mapspeed_test.go
という新しいテストファイルが追加されました。- このファイルには、文字列、32ビット整数、64ビット整数、文字列配列のハッシュ性能を測定するためのベンチマークテストが含まれています。これにより、ハッシュ関数の変更が実際の性能にどのように影響するかを継続的に監視できるようになります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更箇所は以下のファイルに集中しています。
src/pkg/runtime/alg.c
:runtime·aeskeysched
というグローバル変数の宣言。これはAESハッシュ関数のランダムシードとして使用されます。runtime·hashinit
関数の追加。この関数は、CPUがAES-NI命令をサポートしているかをチェックし、サポートしていればGoランタイムのハッシュ関数ポインタをAES-NIベースの実装に切り替えます。また、ハッシュ関数のランダムシードを初期化します。
src/pkg/runtime/asm_386.s
/src/pkg/runtime/asm_amd64.s
:_rt0_386
/_rt0_amd64
(Goプログラムのエントリポイント) にCPUID命令の呼び出しを追加し、CPUの機能情報をruntime·cpuid_ecx
とruntime·cpuid_edx
に格納するように変更。_rt0_386
/_rt0_amd64
からruntime·hashinit(SB)
を呼び出すように変更。runtime·aeshash
,runtime·aeshashstr
,runtime·aeshash32
,runtime·aeshash64
といったAES-NI命令を利用した新しいハッシュ関数群のアセンブリ実装を追加。- これらのハッシュ関数が内部的に使用する
masks
とshifts
という定数データのアセンブリ定義を追加。
src/pkg/runtime/asm_arm.s
:_rt0_arm
からruntime·hashinit(SB)
を呼び出すように変更。- ARMアーキテクチャではAES-NIが利用できないため、
runtime·aeshash
などの関数が未実装であることを示すダミーの定義を追加。
src/pkg/runtime/mapspeed_test.go
:- 新しいファイルとして追加され、文字列、整数、文字列配列のハッシュ性能を測定するベンチマークテストが含まれています。
src/pkg/runtime/runtime.c
:runtime·cpuid_ecx
とruntime·cpuid_edx
の宣言を追加。
src/pkg/runtime/runtime.h
:runtime·startup_random_data
,runtime·startup_random_data_len
,runtime·get_random_data
の宣言を追加。HashRandomBytes
定数(32バイト)の定義を追加。runtime·hashinit
関数の宣言を追加。- 新しいAES-NIベースのハッシュ関数群(
runtime·aeshash
など)の宣言を追加。 runtime·cpuid_ecx
とruntime·cpuid_edx
の外部宣言を追加。runtime·open
,runtime·read
,runtime·close
といったシステムコールラッパーの宣言を追加。
src/pkg/runtime/signal_linux_386.c
/src/pkg/runtime/vdso_linux_amd64.c
:- Linux環境でELF補助ベクトルの
AT_RANDOM
からランダムデータを取得するロジックを追加。
- Linux環境でELF補助ベクトルの
src/pkg/runtime/sys_darwin_386.s
/sys_darwin_amd64.s
/sys_freebsd_386.s
/sys_freebsd_amd64.s
/sys_netbsd_386.s
/sys_netbsd_amd64.s
/sys_openbsd_386.s
/sys_openbsd_amd64.s
:- 各OS/アーキテクチャ向けに、
open
,close
,read
システムコールのアセンブリラッパーを追加。これらはruntime·get_random_data
関数で/dev/urandom
から乱数を読み込むために使用されます。
- 各OS/アーキテクチャ向けに、
src/pkg/runtime/thread_darwin.c
/thread_freebsd.c
/thread_linux.c
/thread_netbsd.c
/thread_openbsd.c
/thread_windows.c
:runtime·get_random_data
関数の実装を追加。この関数は、各OSのメカニズム(/dev/urandom
、AT_RANDOM
、CryptGenRandom
)を使用して、ハッシュ関数のランダムシードを生成するための乱数を取得します。thread_linux.c
では、runtime·open
,runtime·close
,runtime·read
の宣言が削除され、runtime.h
に移動しました。
src/pkg/runtime/thread_windows.c
:- Windows CryptoAPIの関数(
CryptAcquireContextW
,CryptGenRandom
,CryptReleaseContext
)の動的インポート宣言を追加。
- Windows CryptoAPIの関数(
コアとなるコードの解説
src/pkg/runtime/alg.c
の runtime·hashinit
関数
// used in asm_{386,amd64}.s
byte runtime·aeskeysched[HashRandomBytes];
void
runtime·hashinit(void)
{
// Install aes hash algorithm if we have the instructions we need
if((runtime·cpuid_ecx & (1 << 25)) != 0 && // aes (aesenc)
(runtime·cpuid_ecx & (1 << 9)) != 0 && // sse3 (pshufb)
(runtime·cpuid_ecx & (1 << 19)) != 0) { // sse4.1 (pinsr{d,q})
byte *rnd;
int32 n;
runtime·algarray[AMEM].hash = runtime·aeshash;
runtime·algarray[AMEM8].hash = runtime·aeshash;
runtime·algarray[AMEM16].hash = runtime·aeshash;
runtime·algarray[AMEM32].hash = runtime·aeshash32;
runtime·algarray[AMEM64].hash = runtime·aeshash64;
runtime·algarray[AMEM128].hash = runtime·aeshash;
runtime·algarray[ASTRING].hash = runtime·aeshashstr;
// Initialize with random data so hash collisions will be hard to engineer.
runtime·get_random_data(&rnd, &n);
if(n > HashRandomBytes)
n = HashRandomBytes;
runtime·memmove(runtime·aeskeysched, rnd, n);
if(n < HashRandomBytes) {
// Not very random, but better than nothing.
int64 t = runtime·nanotime();
while (n < HashRandomBytes) {
runtime·aeskeysched[n++] = (int8)(t >> (8 * (n % 8)));
}
}
}
}
このC言語の関数は、Goランタイムの起動時に一度だけ実行されます。
- CPU機能チェック:
runtime·cpuid_ecx
レジスタの値を確認し、CPUがAES-NI ((1 << 25)
), SSE3 ((1 << 9)
), SSE4.1 ((1 << 19)
) 命令をサポートしているかをチェックします。これらのビットは、それぞれAESENC
、PSHUFB
、PINSRD/PINSRQ
命令の利用可能性を示します。 - ハッシュ関数ポインタの切り替え: もし必要な命令がすべて利用可能であれば、
runtime·algarray
というテーブルの対応するエントリを、新しく実装されたAES-NIベースのアセンブリハッシュ関数(runtime·aeshash
,runtime·aeshash32
,runtime·aeshash64
,runtime·aeshashstr
)に設定します。これにより、Goのmap
操作などでこれらの高速なハッシュ関数が使用されるようになります。 - ランダムシードの初期化:
runtime·get_random_data
を呼び出して、システムからランダムなバイト列を取得します。このデータはruntime·aeskeysched
にコピーされ、ハッシュ関数の初期シードとして使用されます。 - シードの補完: 取得したランダムデータが
HashRandomBytes
(32バイト)に満たない場合、runtime·nanotime()
(現在のナノ秒単位の時刻)を基に不足分を埋めます。これは、完全にランダムではないものの、ハッシュ衝突攻撃に対する耐性を高めるためのフォールバックメカニズムです。
src/pkg/runtime/asm_amd64.s
の runtime·aeshashbody
関数 (AMD64版)
// AX: data
// CX: length
// DX: ptr to seed input / hash output
TEXT runtime·aeshashbody(SB),7,$0
MOVQ (DX), X0 // seed to low 64 bits of xmm0
PINSRQ $1, CX, X0 // size to high 64 bits of xmm0
MOVOU runtime·aeskeysched+0(SB), X2
MOVOU runtime·aeskeysched+16(SB), X3
aesloop:
CMPQ CX, $16
JB aesloopend
MOVOU (AX), X1
AESENC X2, X0
AESENC X1, X0
SUBQ $16, CX
ADDQ $16, AX
JMP aesloop
aesloopend:
TESTQ CX, CX
JE finalize // no partial block
TESTQ $16, AX
JNE highpartial
// address ends in 0xxxx. 16 bytes loaded
// at this address won't cross a page boundary, so
// we can load it directly.
MOVOU (AX), X1
ADDQ CX, CX
PAND masks(SB)(CX*8), X1
JMP partial
highpartial:
// address ends in 1xxxx. Might be up against
// a page boundary, so load ending at last byte.
// Then shift bytes down using pshufb.
MOVOU -16(AX)(CX*1), X1
ADDQ CX, CX
PSHUFB shifts(SB)(CX*8), X1
partial:
// incorporate partial block into hash
AESENC X3, X0
AESENC X1, X0
finalize:
// finalize hash
AESENC X2, X0
AESENC X3, X0
AESENC X2, X0
MOVQ X0, (DX)
RET
このアセンブリ関数は、実際のハッシュ計算のコアロジックを実装しています。
- 初期化:
DX
レジスタが指すシード値(ハッシュの初期値)をXMM0レジスタの下位64ビットにロードします。- 入力データの長さ(
CX
)をXMM0レジスタの上位64ビットにPINSRQ
命令で挿入します。これにより、シードと長さがハッシュ計算の初期状態に組み込まれます。 runtime·aeskeysched
からAESキーをXMM2とXMM3レジスタにロードします。これらはハッシュ計算のラウンドキーとして機能します。
- メインループ (
aesloop
):- 入力データの残りの長さ(
CX
)が16バイト以上である限りループします。 MOVOU (AX), X1
:入力データから16バイトをXMM1レジスタにロードします。AESENC X2, X0
:XMM0(現在のハッシュ状態)とXMM2(キー)を使ってAES暗号化ラウンドを実行し、結果をXMM0に格納します。AESENC X1, X0
:XMM0(現在のハッシュ状態)とXMM1(入力データブロック)を使ってAES暗号化ラウンドを実行し、結果をXMM0に格納します。これにより、入力データがハッシュ状態に組み込まれます。SUBQ $16, CX
:処理した16バイト分、長さを減らします。ADDQ $16, AX
:入力データポインタを16バイト進めます。
- 入力データの残りの長さ(
- 部分ブロックの処理 (
aesloopend
,highpartial
,partial
):- メインループの後に、残りのデータが16バイト未満の場合(部分ブロック)を処理します。
- データのメモリアドレスのアライメント(16バイト境界に揃っているか)によって処理が分岐します。
MOVOU
でデータをロードし、PAND
(論理AND)とPSHUFB
(バイトシャッフル)命令を組み合わせて、残りのバイトだけをXMM1レジスタに適切に配置します。AESENC X3, X0
、AESENC X1, X0
:部分ブロックをハッシュ状態に組み込みます。
- 最終処理 (
finalize
):- 残りのデータがすべて処理された後、ハッシュ状態をさらに数回
AESENC
命令で処理し、最終的なハッシュ値を生成します。これにより、ハッシュ値の拡散性が高まります。 MOVQ X0, (DX)
:最終的なハッシュ値をDX
が指すメモリ位置に書き込みます。
- 残りのデータがすべて処理された後、ハッシュ状態をさらに数回
runtime·get_random_data
関数 (Linux版の例)
// Random bytes initialized at startup. These come
// from the ELF AT_RANDOM auxiliary vector (vdso_linux_amd64.c).
byte* runtime·startup_random_data;
uint32 runtime·startup_random_data_len;
void
runtime·get_random_data(byte **rnd, int32 *rnd_len)
{
if(runtime·startup_random_data != nil) {
*rnd = runtime·startup_random_data;
*rnd_len = runtime·startup_random_data_len;
} else {
static byte urandom_data[HashRandomBytes];
int32 fd;
fd = runtime·open("/dev/urandom", 0 /* O_RDONLY */, 0);
if(runtime·read(fd, urandom_data, HashRandomBytes) == HashRandomBytes) {
*rnd = urandom_data;
*rnd_len = HashRandomBytes;
} else {
*rnd = nil;
*rnd_len = 0;
}
runtime·close(fd);
}
}
この関数は、ハッシュ関数のランダムシードを生成するための乱数を取得します。
AT_RANDOM
からの取得: まず、Goランタイムの起動時にカーネルから提供されたAT_RANDOM
補助ベクトルにデータがあるかを確認します。もしあれば、そのデータを直接使用します。これは、最も効率的で安全な方法です。/dev/urandom
からの読み込み:AT_RANDOM
が利用できない場合、/dev/urandom
デバイスファイルを開き、そこからHashRandomBytes
(32バイト)の乱数を読み込みます。- エラーハンドリング: 乱数の取得に失敗した場合(例:
/dev/urandom
からの読み込みが不足した場合)、rnd
とrnd_len
をnil/0に設定します。この場合、runtime·hashinit
関数がruntime·nanotime()
を使用してシードを補完します。
関連リンク
- Go issue #3885: https://github.com/golang/go/issues/3885 (このコミットが解決しようとしている問題)
- Go Code Review 7543043: https://golang.org/cl/7543043 (このコミットのコードレビューページ)
- Go Code Review 7548043: https://golang.org/cl/7548043 (このコミットが依存しているアセンブリ命令の変更)
参考にした情報源リンク
- AES-NI (Wikipedia): https://ja.wikipedia.org/wiki/AES%E3%83%BB%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%BB%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
- CPUID (Wikipedia): https://ja.wikipedia.org/wiki/CPUID
- ハッシュ衝突攻撃 (Wikipedia): https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E8%A1%9D%E7%AA%81%E6%94%BB%E6%92%83
- Go Assembly Language (Go公式ドキュメント): https://go.dev/doc/asm
- ELF Auxiliary Vectors (Linux man page): https://man7.org/linux/man-pages/man3/getauxval.3.html (AT_RANDOMに関する情報)
- CryptGenRandom function (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals (Intel): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html (AES-NI命令の詳細)
- Goのmap実装とハッシュ関数 (Qiita記事など、具体的なURLは検索結果による)
- Goのランタイム初期化 (ブログ記事など、具体的なURLは検索結果による)
[インデックス 15722] ファイルの概要
このコミットは、Go言語のランタイムにおけるハッシュ関数の性能とセキュリティを大幅に向上させることを目的としています。具体的には、IntelおよびAMDプロセッサに搭載されているAES (Advanced Encryption Standard) ハードウェア命令(AES-NI)を活用してハッシュ計算を高速化し、さらにハッシュ衝突によるサービス拒否(DoS)攻撃を防ぐために、ハッシュ関数にランダムなシードを導入しています。これにより、Goのmap
型などのハッシュテーブルを利用する操作がより高速かつ安全になります。
コミット
commit a5d4024139231ca10b5347d17bbf702cfdf5fd5b
Author: Keith Randall <khr@golang.org>
Date: Tue Mar 12 10:47:44 2013 -0700
runtime: faster & safer hash function
Uses AES hardware instructions on 386/amd64 to implement
a fast hash function. Incorporates a random key to
thwart hash collision DOS attacks.
Depends on CL#7548043 for new assembly instructions.
Update #3885
Helps some by making hashing faster. Go time drops from
0.65s to 0.51s.
R=rsc, r, bradfitz, remyoudompheng, khr, dsymonds, minux.ma, elias.naur
CC=golang-dev
https://golang.org/cl/7543043
GitHub上でのコミットページへのリンク
https://github.com/golang/go/commit/a5d4024139231ca10b5347d17bbf702cfdf5fd5b
元コミット内容
runtime: faster & safer hash function
Uses AES hardware instructions on 386/amd64 to implement
a fast hash function. Incorporates a random key to
thwart hash collision DOS attacks.
Depends on CL#7548043 for new assembly instructions.
Update #3885
Helps some by making hashing faster. Go time drops from
0.65s to 0.51s.
R=rsc, r, bradfitz, remyoudompheng, khr, dsymonds, minux.ma, elias.naur
CC=golang-dev
https://golang.org/cl/7543043
変更の背景
このコミットは、Go言語のランタイムにおけるハッシュ関数の性能とセキュリティに関する複数の課題に対処するために行われました。
- 性能の最適化: Go言語の
map
型は内部的にハッシュテーブルを使用しており、その性能は基盤となるハッシュ関数の効率に大きく依存します。従来のソフトウェアベースのハッシュ関数では、特に大量のデータや頻繁なmap
操作において性能のボトルネックとなる可能性がありました。コミットメッセージに「Go time drops from 0.65s to 0.51s」とあるように、ハッシュ処理の高速化がGoプログラム全体の実行時間短縮に寄与することが期待されました。 - ハッシュ衝突DoS攻撃への対策: ハッシュ関数は、異なる入力に対して異なるハッシュ値を生成することが理想ですが、意図的に同じハッシュ値を生成するような入力(ハッシュ衝突)を多数作成することが可能です。悪意のある攻撃者がこのような衝突を多数引き起こす入力データをサーバーに送信すると、ハッシュテーブルの性能が著しく低下し、CPUリソースを大量に消費することでサービス拒否(DoS)状態に陥る可能性があります。このコミットでは、ハッシュ関数にランダムなシードを導入することで、攻撃者が事前に衝突を計算することを困難にし、この種の攻撃に対する耐性を高めることを目指しました。
- ハードウェア支援の活用: 近年のIntelおよびAMDプロセッサには、AES暗号化・復号化処理をハードウェアレベルで高速化するAES-NI(Advanced Encryption Standard New Instructions)が搭載されています。これらの命令は、暗号処理だけでなく、ハッシュ計算のようなビット操作や巡回シフトを多用する処理にも応用でき、ソフトウェア実装に比べて格段に高い性能を発揮します。このコミットは、これらのハードウェア機能をGoランタイムに統合し、性能とセキュリティの両面でメリットを享受しようとするものです。
前提知識の解説
このコミットの技術的詳細を理解するためには、以下の概念を把握しておく必要があります。
-
ハッシュ関数:
- 定義: 任意の長さの入力データ(メッセージ)を受け取り、固定長の短い出力データ(ハッシュ値、メッセージダイジェスト、フィンガープリントなど)を生成する一方向性の関数です。
- 特性:
- 一方向性: ハッシュ値から元の入力データを効率的に復元することは困難です。
- 衝突耐性: 異なる入力データから同じハッシュ値が生成されること(ハッシュ衝突)が非常に稀であるべきです。
- 雪崩効果: 入力データがわずかに変化するだけで、ハッシュ値が大きく変化します。
- Goにおける利用: Go言語の
map
型はハッシュテーブルとして実装されており、キーのハッシュ値を用いてデータの格納と検索を行います。そのため、ハッシュ関数の性能と衝突耐性は、map
操作の効率とセキュリティに直結します。
-
AES (Advanced Encryption Standard) ハードウェア命令 (AES-NI):
- 概要: IntelおよびAMDのCPUに搭載されている、AES暗号化・復号化処理を高速化するための専用命令セットです。
- 主要命令:
AESENC
(AES Encrypt),AESENCLAST
(AES Encrypt Last) などが含まれます。これらの命令は、ソフトウェアでAESを実装するよりもはるかに少ないCPUサイクルで処理を完了できます。 - ハッシュへの応用: AES-NIはブロック暗号の命令ですが、その高速なビット操作能力は、暗号学的ハッシュ関数だけでなく、Goの
map
のような非暗号学的ハッシュ関数の高速化にも利用されます。入力データをAESのブロックのように扱い、ハッシュ値を「状態」として、入力ブロックと状態を組み合わせてAESENC
命令で繰り返し処理することで、効率的にハッシュ値を生成できます。
-
CPUID命令:
- 概要: x86アーキテクチャのCPUが提供する命令で、プロセッサの機能や情報をプログラムから問い合わせるために使用されます。
- 利用方法: 特定の値をEAXレジスタに設定して
CPUID
命令を実行すると、CPUがサポートする機能(例: SSE, AVX, AES-NIなど)に関する情報がEBX, ECX, EDXなどのレジスタに返されます。 - 本コミットでの利用: Goランタイムは、この
CPUID
命令を用いて、実行中のCPUがAES-NI命令をサポートしているかを検出し、サポートしている場合にのみAES-NIベースの高速なハッシュ関数を使用するように動的に切り替えます。
-
ハッシュ衝突DoS攻撃:
- 概要: 攻撃者が、ハッシュテーブルの性能を意図的に低下させるために、多数の異なる入力データに対して同じハッシュ値(または少数のハッシュ値)を生成するような入力セットを作成し、それをサーバーに送信する攻撃です。
- 影響: ハッシュテーブルは、衝突が発生すると、線形探索などの効率の悪いアルゴリズムにフォールバックすることがあります。これにより、CPU使用率が急増し、正当なリクエストを処理できなくなることで、サービス拒否(DoS)状態を引き起こします。
- 対策: ハッシュ関数にランダムなシードを導入することで、攻撃者が事前に衝突を計算することが困難になり、この種の攻撃に対する耐性が向上します。
-
Goランタイムとアセンブリ:
- Goランタイム: Goプログラムの実行を支える低レベルな機能(ガベージコレクション、スケジューラ、メモリ管理など)を提供する部分です。
- アセンブリ言語: 性能が極めて重要となる部分や、特定のハードウェア機能(本コミットのAES-NIなど)を直接利用する部分では、Go言語ではなく、アセンブリ言語で実装されることがあります。Goのアセンブリは、Plan 9アセンブラの構文に似ており、一般的なx86アセンブラとは異なる表記規則を持ちます。
-
ELF AT_RANDOM:
- 概要: Linuxシステムにおいて、ELF (Executable and Linkable Format) 実行ファイルが起動される際に、カーネルがプロセスに渡す補助ベクトル(Auxiliary Vector)の一つです。
- 目的:
AT_RANDOM
は、セキュリティ目的で使用されるランダムなバイト列へのポインタを提供します。これにより、アプリケーションは/dev/urandom
などのデバイスファイルを開くことなく、起動時にカーネルから提供される高品質な乱数を取得できます。これは、特にサンドボックス化された環境や、ファイルシステムへのアクセスが制限される環境で有用です。
-
Windows CryptGenRandom:
- 概要: Windows APIの一部で、暗号学的に強力な乱数を生成するために使用されます。
- 利用方法:
CryptAcquireContext
関数で暗号サービスプロバイダ(CSP)のハンドルを取得し、そのハンドルを使ってCryptGenRandom
を呼び出すことで乱数を生成します。 - 本コミットでの利用: Windows環境でハッシュ関数のランダムシードを生成するために利用されています。
技術的詳細
このコミットの技術的な核心は、Goランタイムのハッシュ関数をAES-NI命令セットを利用するように変更し、さらにハッシュ衝突DoS攻撃を防ぐためのランダムなシードを導入した点にあります。
-
AES-NIによるハッシュ関数の実装:
src/pkg/runtime/asm_386.s
とsrc/pkg/runtime/asm_amd64.s
に、runtime·aeshash
、runtime·aeshashstr
、runtime·aeshash32
、runtime·aeshash64
といった新しいアセンブリ関数が追加されました。これらは、それぞれ任意のメモリブロック、文字列、32ビット整数、64ビット整数のハッシュを計算します。- これらの関数は、内部的に
runtime·aeshashbody
という共通のアセンブリルーチンを呼び出します。このaeshashbody
が、入力データと現在のハッシュ状態を組み合わせてAESENC
命令を繰り返し実行することで、ハッシュ値を生成します。 PINSRD
(Packed Insert Doubleword) やPINSRQ
(Packed Insert Quadword) 命令は、XMMレジスタ(SSE命令で使用される128ビットレジスタ)の特定の位置にデータを挿入するために使用されます。これにより、シード値や入力データの一部をハッシュ計算の初期状態に効率的に組み込むことができます。PSHUFB
(Packed Shuffle Bytes) 命令は、バイト単位のシャッフルを行う命令で、特に16バイト未満の「部分ブロック」の処理や、アライメントされていないデータからの読み込み後のバイト操作に利用され、効率的なデータ処理を実現します。
-
CPU機能の検出と動的なハッシュ関数の選択:
- Goランタイムの初期化時に、
src/pkg/runtime/alg.c
内のruntime·hashinit
関数が呼び出されます。 - この関数は、
CPUID
命令の結果を格納したruntime·cpuid_ecx
レジスタのビットをチェックし、CPUがAES-NI ((1 << 25)
), SSE3 ((1 << 9)
,PSHUFB
のために必要), SSE4.1 ((1 << 19)
,PINSRD/PINSRQ
のために必要) の各命令セットをサポートしているかを確認します。 - もしこれらの命令がすべて利用可能であれば、
runtime·algarray
というGoランタイム内部のハッシュ関数ポインタテーブルのエントリを、新しく実装されたAES-NIベースのハッシュ関数(runtime·aeshash
,runtime·aeshash32
,runtime·aeshash64
,runtime·aeshashstr
)に置き換えます。これにより、Goのmap
操作などでこれらの高速なハッシュ関数が自動的に使用されるようになります。 - 必要な命令が利用できない環境では、Goランタイムは引き続き従来のソフトウェアベースのハッシュ関数を使用します。
- Goランタイムの初期化時に、
-
ランダムシードの導入:
- ハッシュ衝突DoS攻撃を防ぐため、
runtime·hashinit
関数内でハッシュ関数にランダムなシード(runtime·aeskeysched
)が初期化されます。 - このシードは、システムから提供される乱数データを使用して生成されます。
- Linux:
src/pkg/runtime/signal_linux_386.c
およびsrc/pkg/runtime/vdso_linux_amd64.c
で、ELF補助ベクトルのAT_RANDOM
からランダムデータを取得しようとします。これが利用できない場合は、/dev/urandom
から読み込みます。 - macOS (Darwin)/FreeBSD/NetBSD/OpenBSD:
src/pkg/runtime/thread_darwin.c
などの各OS固有のファイルで、/dev/urandom
からランダムデータを読み込みます。 - Windows:
src/pkg/runtime/thread_windows.c
で、Windows CryptoAPIのCryptAcquireContextW
とCryptGenRandom
関数を使用して乱数を生成します。
- Linux:
- 取得したランダムデータが
HashRandomBytes
(32バイト)に満たない場合、runtime·nanotime()
(現在のナノ秒単位の時刻)をシードとして使用し、不足分を埋めます。これは、完全にランダムではないものの、全くランダムでないよりはマシという考えに基づいています。 - このランダムシードは、ハッシュ計算の初期状態に組み込まれることで、同じ入力データであっても、プログラムの実行ごとに異なるハッシュ値が生成される可能性があり、攻撃者が事前に衝突を計算することを困難にします。
- ハッシュ衝突DoS攻撃を防ぐため、
-
ベンチマークの追加:
src/pkg/runtime/mapspeed_test.go
という新しいテストファイルが追加されました。このファイルには、文字列、32ビット整数、64ビット整数、文字列配列のハッシュ性能を測定するためのベンチマークテストが含まれており、ハッシュ関数の変更が実際の性能にどのように影響するかを継続的に監視できるようになります。
コアとなるコードの変更箇所
このコミットにおける主要なコード変更箇所は以下のファイルに集中しています。
src/pkg/runtime/alg.c
:runtime·aeskeysched
というAESハッシュ関数のランダムシード用グローバル変数を宣言。runtime·hashinit
関数を追加。この関数は、CPUがAES-NI命令をサポートしているかをチェックし、サポートしていればGoランタイムのハッシュ関数ポインタをAES-NIベースの実装に切り替えます。また、ハッシュ関数のランダムシードを初期化します。
src/pkg/runtime/asm_386.s
/src/pkg/runtime/asm_amd64.s
:- Goプログラムのエントリポイントである
_rt0_386
/_rt0_amd64
にCPUID命令の呼び出しを追加し、CPUの機能情報をruntime·cpuid_ecx
とruntime·cpuid_edx
に格納するように変更。 _rt0_386
/_rt0_amd64
からruntime·hashinit(SB)
を呼び出すように変更。runtime·aeshash
,runtime·aeshashstr
,runtime·aeshash32
,runtime·aeshash64
といったAES-NI命令を利用した新しいハッシュ関数群のアセンブリ実装を追加。- これらのハッシュ関数が内部的に使用する
masks
とshifts
という定数データのアセンブリ定義を追加。
- Goプログラムのエントリポイントである
src/pkg/runtime/asm_arm.s
:_rt0_arm
からruntime·hashinit(SB)
を呼び出すように変更。- ARMアーキテクチャではAES-NIが利用できないため、
runtime·aeshash
などの関数が未実装であることを示すダミーの定義を追加。
src/pkg/runtime/mapspeed_test.go
:- 新しいファイルとして追加され、文字列、整数、文字列配列のハッシュ性能を測定するベンチマークテストが含まれています。
src/pkg/runtime/runtime.c
:runtime·cpuid_ecx
とruntime·cpuid_edx
の宣言を追加。
src/pkg/runtime/runtime.h
:runtime·startup_random_data
,runtime·startup_random_data_len
,runtime·get_random_data
の宣言を追加。HashRandomBytes
定数(32バイト)の定義を追加。runtime·hashinit
関数の宣言を追加。- 新しいAES-NIベースのハッシュ関数群(
runtime·aeshash
など)の宣言を追加。 runtime·cpuid_ecx
とruntime·cpuid_edx
の外部宣言を追加。runtime·open
,runtime·read
,runtime·close
といったシステムコールラッパーの宣言を追加。
src/pkg/runtime/signal_linux_386.c
/src/pkg/runtime/vdso_linux_amd64.c
:- Linux環境でELF補助ベクトルの
AT_RANDOM
からランダムデータを取得するロジックを追加。
- Linux環境でELF補助ベクトルの
src/pkg/runtime/sys_darwin_386.s
/sys_darwin_amd64.s
/sys_freebsd_386.s
/sys_freebsd_amd64.s
/sys_netbsd_386.s
/sys_netbsd_amd64.s
/sys_openbsd_386.s
/sys_openbsd_amd64.s
:- 各OS/アーキテクチャ向けに、
open
,close
,read
システムコールのアセンブリラッパーを追加。これらはruntime·get_random_data
関数で/dev/urandom
から乱数を読み込むために使用されます。
- 各OS/アーキテクチャ向けに、
src/pkg/runtime/thread_darwin.c
/thread_freebsd.c
/thread_linux.c
/thread_netbsd.c
/thread_openbsd.c
/thread_windows.c
:runtime·get_random_data
関数の実装を追加。この関数は、各OSのメカニズム(/dev/urandom
、AT_RANDOM
、CryptGenRandom
)を使用して、ハッシュ関数のランダムシードを生成するための乱数を取得します。thread_linux.c
では、runtime·open
,runtime·close
,runtime·read
の宣言が削除され、runtime.h
に移動しました。
src/pkg/runtime/thread_windows.c
:- Windows CryptoAPIの関数(
CryptAcquireContextW
,CryptGenRandom
,CryptReleaseContext
)の動的インポート宣言を追加。
- Windows CryptoAPIの関数(
コアとなるコードの解説
src/pkg/runtime/alg.c
の runtime·hashinit
関数
// used in asm_{386,amd64}.s
byte runtime·aeskeysched[HashRandomBytes];
void
runtime·hashinit(void)
{
// Install aes hash algorithm if we have the instructions we need
if((runtime·cpuid_ecx & (1 << 25)) != 0 && // aes (aesenc)
(runtime·cpuid_ecx & (1 << 9)) != 0 && // sse3 (pshufb)
(runtime·cpuid_ecx & (1 << 19)) != 0) { // sse4.1 (pinsr{d,q})
byte *rnd;
int32 n;
runtime·algarray[AMEM].hash = runtime·aeshash;
runtime·algarray[AMEM8].hash = runtime·aeshash;
runtime·algarray[AMEM16].hash = runtime·aeshash;
runtime·algarray[AMEM32].hash = runtime·aeshash32;
runtime·algarray[AMEM64].hash = runtime·aeshash64;
runtime·algarray[AMEM128].hash = runtime·aeshash;
runtime·algarray[ASTRING].hash = runtime·aeshashstr;
// Initialize with random data so hash collisions will be hard to engineer.
runtime·get_random_data(&rnd, &n);
if(n > HashRandomBytes)
n = HashRandomBytes;
runtime·memmove(runtime·aeskeysched, rnd, n);
if(n < HashRandomBytes) {
// Not very random, but better than nothing.
int64 t = runtime·nanotime();
while (n < HashRandomBytes) {
runtime·aeskeysched[n++] = (int8)(t >> (8 * (n % 8)));
}
}
}
}
このC言語の関数は、Goランタイムの起動時に一度だけ実行され、ハッシュ関数の初期設定を行います。
- CPU機能チェック:
runtime·cpuid_ecx
レジスタのビットを検査し、CPUがAES-NI ((1 << 25)
), SSE3 ((1 << 9)
), SSE4.1 ((1 << 19)
) 命令をサポートしているかを確認します。これらの命令は、それぞれAESENC
、PSHUFB
、PINSRD/PINSRQ
命令の利用可能性を示します。 - ハッシュ関数ポインタの切り替え: もし必要な命令がすべて利用可能であれば、Goランタイム内部の
runtime·algarray
というハッシュ関数ポインタテーブルの対応するエントリを、新しく実装されたAES-NIベースのアセンブリハッシュ関数(runtime·aeshash
,runtime·aeshash32
,runtime·aeshash64
,runtime·aeshashstr
)に設定します。これにより、Goのmap
操作などでこれらの高速なハッシュ関数が自動的に使用されるようになります。 - ランダムシードの初期化:
runtime·get_random_data
を呼び出して、システムからランダムなバイト列を取得します。このデータはruntime·aeskeysched
にコピーされ、ハッシュ関数の初期シードとして使用されます。 - シードの補完: 取得したランダムデータが
HashRandomBytes
(32バイト)に満たない場合、runtime·nanotime()
(現在のナノ秒単位の時刻)を基に不足分を埋めます。これは、完全にランダムではないものの、ハッシュ衝突攻撃に対する耐性を高めるためのフォールバックメカニズムです。
src/pkg/runtime/asm_amd64.s
の runtime·aeshashbody
関数 (AMD64版)
// AX: data
// CX: length
// DX: ptr to seed input / hash output
TEXT runtime·aeshashbody(SB),7,$0
MOVQ (DX), X0 // seed to low 64 bits of xmm0
PINSRQ $1, CX, X0 // size to high 64 bits of xmm0
MOVOU runtime·aeskeysched+0(SB), X2
MOVOU runtime·aeskeysched+16(SB), X3
aesloop:
CMPQ CX, $16
JB aesloopend
MOVOU (AX), X1
AESENC X2, X0
AESENC X1, X0
SUBQ $16, CX
ADDQ $16, AX
JMP aesloop
aesloopend:
TESTQ CX, CX
JE finalize // no partial block
TESTQ $16, AX
JNE highpartial
// address ends in 0xxxx. 16 bytes loaded
// at this address won\'t cross a page boundary, so
// we can load it directly.
MOVOU (AX), X1
ADDQ CX, CX
PAND masks(SB)(CX*8), X1
JMP partial
highpartial:
// address ends in 1xxxx. Might be up against
// a page boundary, so load ending at last byte.
// Then shift bytes down using pshufb.
MOVOU -16(AX)(CX*1), X1
ADDQ CX, CX
PSHUFB shifts(SB)(CX*8), X1
partial:
// incorporate partial block into hash
AESENC X3, X0
AESENC X1, X0
finalize:
// finalize hash
AESENC X2, X0
AESENC X3, X0
AESENC X2, X0
MOVQ X0, (DX)
RET
このアセンブリ関数は、実際のハッシュ計算のコアロジックを実装しています。
- 初期化:
DX
レジスタが指すシード値(ハッシュの初期値)をXMM0レジスタの下位64ビットにロードします。- 入力データの長さ(
CX
)をXMM0レジスタの上位64ビットにPINSRQ
命令で挿入します。これにより、シードと長さがハッシュ計算の初期状態に組み込まれます。 runtime·aeskeysched
からAESキーをXMM2とXMM3レジスタにロードします。これらはハッシュ計算のラウンドキーとして機能します。
- メインループ (
aesloop
):- 入力データの残りの長さ(
CX
)が16バイト以上である限りループします。 MOVOU (AX), X1
:入力データから16バイトをXMM1レジスタにロードします。AESENC X2, X0
:XMM0(現在のハッシュ状態)とXMM2(キー)を使ってAES暗号化ラウンドを実行し、結果をXMM0に格納します。AESENC X1, X0
:XMM0(現在のハッシュ状態)とXMM1(入力データブロック)を使ってAES暗号化ラウンドを実行し、結果をXMM0に格納します。これにより、入力データがハッシュ状態に組み込まれます。SUBQ $16, CX
:処理した16バイト分、長さを減らします。ADDQ $16, AX
:入力データポインタを16バイト進めます。
- 入力データの残りの長さ(
- 部分ブロックの処理 (
aesloopend
,highpartial
,partial
):- メインループの後に、残りのデータが16バイト未満の場合(部分ブロック)を処理します。
- データのメモリアドレスのアライメント(16バイト境界に揃っているか)によって処理が分岐します。
MOVOU
でデータをロードし、PAND
(論理AND)とPSHUFB
(バイトシャッフル)命令を組み合わせて、残りのバイトだけをXMM1レジスタに適切に配置します。AESENC X3, X0
、AESENC X1, X0
:部分ブロックをハッシュ状態に組み込みます。
- 最終処理 (
finalize
):- 残りのデータがすべて処理された後、ハッシュ状態をさらに数回
AESENC
命令で処理し、最終的なハッシュ値を生成します。これにより、ハッシュ値の拡散性が高まります。 MOVQ X0, (DX)
:最終的なハッシュ値をDX
が指すメモリ位置に書き込みます。
- 残りのデータがすべて処理された後、ハッシュ状態をさらに数回
runtime·get_random_data
関数 (Linux版の例)
// Random bytes initialized at startup. These come
// from the ELF AT_RANDOM auxiliary vector (vdso_linux_amd64.c).
byte* runtime·startup_random_data;
uint32 runtime·startup_random_data_len;
void
runtime·get_random_data(byte **rnd, int32 *rnd_len)
{
if(runtime·startup_random_data != nil) {
*rnd = runtime·startup_random_data;
*rnd_len = runtime·startup_random_data_len;
} else {
static byte urandom_data[HashRandomBytes];
int32 fd;
fd = runtime·open("/dev/urandom", 0 /* O_RDONLY */, 0);
if(runtime·read(fd, urandom_data, HashRandomBytes) == HashRandomBytes) {
*rnd = urandom_data;
*rnd_len = HashRandomBytes;
} else {
*rnd = nil;
*rnd_len = 0;
}
runtime·close(fd);
}
}
この関数は、ハッシュ関数のランダムシードを生成するための乱数を取得します。
AT_RANDOM
からの取得: まず、Goランタイムの起動時にカーネルから提供されたAT_RANDOM
補助ベクトルにデータがあるかを確認します。もしあれば、そのデータを直接使用します。これは、最も効率的で安全な方法です。/dev/urandom
からの読み込み:AT_RANDOM
が利用できない場合、/dev/urandom
デバイスファイルを開き、そこからHashRandomBytes
(32バイト)の乱数を読み込みます。- エラーハンドリング: 乱数の取得に失敗した場合(例:
/dev/urandom
からの読み込みが不足した場合)、rnd
とrnd_len
をnil/0に設定します。この場合、runtime·hashinit
関数がruntime·nanotime()
を使用してシードを補完します。
関連リンク
- Go issue #3885: https://github.com/golang/go/issues/3885
- Go Code Review 7543043: https://golang.org/cl/7543043
- Go Code Review 7548043: https://golang.org/cl/7548043
参考にした情報源リンク
- AES-NI (Wikipedia): https://ja.wikipedia.org/wiki/AES%E3%83%BB%E3%83%8B%E3%83%A5%E3%83%BC%E3%83%BB%E3%82%A4%E3%83%B3%E3%82%B9%E3%83%88%E3%83%A9%E3%82%AF%E3%82%B7%E3%83%A7%E3%83%B3
- CPUID (Wikipedia): https://ja.wikipedia.org/wiki/CPUID
- ハッシュ衝突攻撃 (Wikipedia): https://ja.wikipedia.org/wiki/%E3%83%8F%E3%83%83%E3%82%B7%E3%83%A5%E8%A1%9D%E7%AA%81%E6%94%BB%E6%92%83
- Go Assembly Language (Go公式ドキュメント): https://go.dev/doc/asm
- ELF Auxiliary Vectors (Linux man page): https://man7.org/linux/man-pages/man3/getauxval.3.html
- CryptGenRandom function (Microsoft Learn): https://learn.microsoft.com/en-us/windows/win32/api/wincrypt/nf-wincrypt-cryptgenrandom
- Intel® 64 and IA-32 Architectures Software Developer’s Manuals (Intel): https://www.intel.com/content/www/us/en/developer/articles/technical/intel-sdm.html
- Go runtime hash function (Stack Overflow): https://stackoverflow.com/questions/20400750/go-runtime-hash-function
- Hash collision DoS attack (LayerLogix): https://layerlogix.com/hash-collision-dos-attack/
- CPUID AES instruction set (Stack Overflow): https://stackoverflow.com/questions/10717099/how-to-check-if-processor-supports-aes-instruction-set
- AT_RANDOM ELF auxiliary vector (LWN.net): https://lwn.net/Articles/519085/
- CryptGenRandom Windows API (Wikipedia): https://en.wikipedia.org/wiki/CryptGenRandom