
2025/12/10 6:10
Go Proposal: Secret Mode
RSS: https://news.ycombinator.com/rss
要約▶
Japanese Translation:
Go 1.26 では、関数が終了した際に自動的にメモリを消去し、暗号学的シークレットが RAM に残るのを防ぐ実験的ランタイムパッケージ
を導入しました。 secret
secret.Do でコードをラップすると、その関数がパニックしたり runtime.Goexit で終了しても、CPU レジスタ、スタックフレーム、ヒープ割り当てをクリアします。この機能は AMD64 と ARM64 アーキテクチャの Linux のみで利用可能で、ビルドフラグ GOEXPERIMENT=runtimesecret を有効にする必要があります。
secret.Do は通常終了でもパニックしても関数から戻る前にレジスタとスタックを消去しますが、ヒープ割り当てはガベージコレクタが参照を削除し実際に走った後でのみクリアされるため、タイミングは非決定的です。ラップされた関数によって書き込まれたグローバル変数は保護されず、f 内でゴルーチンを起動するとパニックします。
この実験的パッケージは、暗号ライブラリがフォワードシークレシーを自動化し、メモリダンプによるキー漏洩を減らす手段を提供しますが、プラットフォームサポートの限定性と GC の帳簿情報漏れの可能性により、万能な解決策ではありません。将来の Go リリースでプラットフォーム対応や消去タイミングの調整が拡張される可能性があります
本文
Accepted! シリーズの一部:Go の今後の変更点を簡潔に解説
使用済みメモリを自動で消去して秘密情報漏洩を防止
Ver. 1.26 • Stdlib • 低影響
要約
新しい
runtime/secret パッケージは、関数を「シークレットモード」で実行できます。関数が終了すると、その関数で使用したレジスタとスタックを直ちに消去(ゼロ化)します。ヒープへの割り当ては、ガベージコレクタが到達不能になった時点で即座に消去されます。
secret.Do(func() { // セッションキーを生成し // それを使ってデータを暗号化する。 })
これにより、機密情報が必要以上にメモリ上に残るのを防ぎ、攻撃者が取得できるリスクを低減します。パッケージは実験的であり、主に暗号ライブラリ開発者向けです(アプリ開発者ではなく)。
背景
WireGuard や TLS などの暗号プロトコルには フォワードシークレシー が求められます。攻撃者が長期秘密(例:TLS のプライベートキー)を入手しても、過去の通信を解読できないようにするためです。そのためには、使用後にセッションキーをメモリから消去しなければなりません。Go ではランタイムがメモリを管理し、いつ・どのようにクリアされるかは保証されていません。ヒープ割り当てやスタックフレームに残った機密データが、コアダンプやメモリアタックで露出する恐れがあります。開発者はリフレクションを使った不安定な「ハック」で内部バッファをゼロ化することが多いですが、それでも一部のデータは制御外に残ります。
解決策として、機密操作中に使用されるすべての一時領域を自動で消去するランタイムメカニズムを提供します。これによりライブラリ開発者がワークアラウンドを書く手間が省けます。
仕様
runtime/secret パッケージに Do と Enabled を追加:
// Do は f を呼び出す。 // // Do は、f が使用した一時領域を適切なタイミングで消去します。 // この文脈では「f」は f によって開始された全ての呼び出し木を指します。 // • f が使ったレジスタは Do が返る前に消去される。 // • f の使用したスタックは Do が返る前に消去される。 // • f によって行われたヒープ割り当ては、ガベージコレクタが到達不能と判断次第即座に消去される。 // • f がパニックしたり runtime.Goexit を呼んだ場合でも Do は機能し、 // パニックは実際には Do から発生したように見える。 func Do(f func()) // Enabled は、Do が現在のコールスタック上に存在するかを報告する。 func Enabled() bool
現在の制限点
とlinux/amd64
でのみサポート。その他ではlinux/arm64
はそのままDo
を呼び出す。f- グローバル変数への書き込みは保護対象外。
内でゴルーチンを起動するとパニックになる。f
を呼ぶと、消去は全ての defer が実行されるまで遅延する。runtime.Goexit- ヒープ割り当ては
- プログラムが参照を落とした瞬間に、
- ガベージコレクタがそのことを検知した時点で消去される。
前者はプログラム側制御、後者はランタイムのタイミングに依存。
がパニックすると、そのパニック値がf
内で割り当てられたメモリを参照している場合、f
そのメモリはパニック値が到達不能になるまで消去されない。- ポインタアドレスは GC が内部で保持するデータバッファに漏れる可能性があるため、機密情報をポインタに渡さないこと。
例: 配列
の 100 番目の要素が秘密なら、data
を作らないでください。GC がそのポインタを保持するとオフセットが漏洩します。&data[100]
パッケージは主に暗号ライブラリ開発者向けです。ほとんどのアプリは
secret.Do を内部で使用する高レベルライブラリを利用すべきです。Go 1.26 以降、
runtime/secret は実験的で、ビルド時に GOEXPERIMENT=runtimesecret を設定して有効化します。
例
AES‑GCM でメッセージを暗号化し、セッションキーと内部 AES ステートを確実に消去する方法:
// Encrypt は一時的な鍵を生成し、メッセージを暗号化する。 // secret.Do に機密操作全体を包むことで、 // 鍵と AES の内部構造がメモリから消えます。 func Encrypt(message []byte) ([]byte, error) { var ciphertext []byte var encErr error secret.Do(func() { // 1. 一時的な 32 バイト鍵を生成。 key := make([]byte, 32) if _, err := io.ReadFull(rand.Reader, key); err != nil { encErr = err return } // 2. キーを展開してブロック暗号を作成。 block, err := aes.NewCipher(key) if err != nil { encErr = err return } gcm, err := cipher.NewGCM(block) if err != nil { encErr = err return } nonce := make([]byte, gcm.NonceSize()) if _, err := io.ReadFull(rand.Reader, nonce); err != nil { encErr = err return } // 3. データを暗号化。 // このクロージャから出るのは ciphertext のみ。 ciphertext = gcm.Seal(nonce, nonce, message, nil) }) return ciphertext, encErr }
secret.Do は生鍵だけでなく、関数内で生成される cipher.Block 構造体(拡張キー表を保持)も保護します。これは簡易例です。実際の暗号交換では、鍵は安全に受信者へ渡す必要があります。
リンク & クレジット
- 作者:Dave Anderson, Filippo Valsorda, Jason A. Donenfeld, Russ Cox
- 参考文献:21865, 704615, Daniel Morsing, Keith Randall
★ 新規投稿を追いかけるには ★