crypto/tlsのConfig.Randが非推奨に、決定的な乱数を使うテストにはtesting/cryptotest.SetGlobalRandomを使う
crypto/tls
概要
Config.Randが非推奨になった。決定的なテストを行いたい場合は、代わりにGo 1.26で追加されたtesting/cryptotest.SetGlobalRandomを使う。
導入経緯
Config.Randは、crypto系パッケージの中でも最も重要な本番用のConfig構造体に生えた、テスト用のフックという位置付けだった。しかし提案者によれば、これは以前から「wart(いぼ)」であり、すでに実態と合わなくなっていた。具体的には、サーバー側でX25519MLKEM768がデフォルト有効化された際、ML-KEMコンポーネントのEncapsulate処理が常にデフォルトの乱数源を使う実装になっているため、Config.Randを設定してもハイブリッド鍵交換の一部には反映されなくなっており、この状態はGo 1.24以降ずっと見過ごされていたという。
提案者は、crypto/tlsのテストスイート自体をすでにcryptotest.SetGlobalRandomに移行できたことから、他の利用者にとってもこの代替手段で十分だと判断し、凍結前にGo 1.27へ取り込む方針を示した。
議論のハイライト
- 提案者は「これは壊れていたものを追認するだけの変更」と位置付け、異論がなければ凍結前にGo 1.27に取り込むとした。
- 一部のコメントでは、crypto系APIのカスタマイズ余地が段階的に縮小されていく流れへの懸念が示され、関連する過去の議論として#76856や#78551が挙げられている。
使用例
Before
package example
import (
"crypto/tls"
"math/rand"
)
func newDeterministicConfig() *tls.Config {
return &tls.Config{
Rand: rand.New(rand.NewSource(1)),
}
}
After
package example
import (
"testing"
"testing/cryptotest"
)
func TestHandshakeWithDeterministicRandomness(t *testing.T) {
cryptotest.SetGlobalRandom(t, 1)
// この時点以降、crypto/tlsを含むcrypto/...配下の暗黙の乱数源が
// テストtの間だけ決定的なものに置き換わる。
}
移行時の注意
cryptotest.SetGlobalRandomはConfig.Randと異なり、特定のConfigだけでなくプロセス全体の暗黙的な乱数源(crypto/randなど)を差し替える。そのため並行テスト(t.Parallel)やその祖先を持つテストでは使用できない。また、Go Cryptographic Module v1.0.0でビルドされている場合(crypto/fips140.Versionが"v1.0.0"を返す場合)はサポートされない。Config.Randは非推奨になったが引き続き動作はするものの、Config.rand経由で参照されない処理(ハイブリッド鍵交換のML-KEM部分など)があるため、本番環境での決定的乱数指定の手段としては信頼できない点に注意する。
実装解説
Config.Randのフィールドコメントに非推奨表示が追加され、代替としてtesting/cryptotest.SetGlobalRandomを案内している。
実際に、hybridKeyExchange.serverSharedSecret(key_schedule.go;l=225)を見ると、ECDH側の共有鍵計算には引数のrand io.Readerが渡される一方、ML-KEM側はmlkemPeerKey.Encapsulate()をio.Readerなしで呼び出しており、Config.Randで指定した乱数源が反映されない。これはissue本文で説明されている「X25519MLKEM768のEncapsulateが常にデフォルトの乱数源を使う」という指摘と一致しており、Config.Randが本番用途では信頼できなくなっていたことの直接的な裏付けになっている。