std:crypto/random — 暗号用乱数
標準ライブラリモジュール (../index.md)。本書中の裸の §N は本書の節を指す。言語の意味論は ../../language-spec.md を参照。
import crand := "std:crypto/random" はホストのエントロピー源から予測不能なバイト列を取る slot 1 つを持つ namespace object を crand に束縛する。std:crypto/random の import 自体が「このコードはエントロピーを引く」ことの能力宣言になる (../index.md)。
std:crypto (../crypto.md) とは別のモジュールである。あちらは入力から出力が一意に決まる純粋なハッシュと HMAC を持つのに対し、こちらは呼ぶたびに違う値を返す。純粋であることは std:crypto の契約なので、そこへエントロピーの口を混ぜない。鍵とトークンを作るのはこちら、それを署名するのがあちらである。
std:random (../random.md) とも別である。あちらは統計的な擬似乱数で、再現できる列 (seed) を主な用途に持ち、ジェネレーター object に可変状態を閉じ込める。こちらは再現できず、状態を持たず、値を直接返す。鍵・トークン・パスワードの生成には必ずこちらを使う — std:random は統計的性質しか主張しない。
std:fs のような外部リソース I/O ではなく、std:math と同じく同期で値を直接返す (Future を返さない)。エントロピーの取得はホストの 1 回の呼び出しで完結し、名前で指す外部リソースを開くわけでもないので、! を強いる形にしていない。
import crand := "std:crypto/random" key := crand.bytes(32).unwrap! #> <32 bytes> 呼ぶたびに異なる token := crand.bytes(16).unwrap! #> <16 bytes>
以下では import crand := "std:crypto/random" で束縛したものとして記す。
1. slot 一覧
| 名前 | 形 | 効果 | 意味 |
|---|---|---|---|
bytes |
crand.bytes(n) |
Rand |
エントロピー由来の n バイト → Result(Bytes) (§2) |
2. crand.bytes
- 引数
nは Int。0以上でなければ呼び出し位置でError(バグ層。負の長さは要求として意味を持たない)。非 Int もError。 nが0ならOkの空 Bytes。エントロピーは引かない。- 上限は設けない。大きな
nはホストの読み取りが分割されうるが、要求した長さちょうどを返すかErrを返すかのどちらかである — 短く返ることは無い。ただし、実行環境が長さとして表現できる範囲を超えるnはバイト数の要求として成り立たないので、そのnはErrではなく呼び出し位置のErrorである。32 ビットの環境 (wasm 等) ではこの境界がIntの表現できる範囲よりも手前に来る。 - 成功時は
Ok(bs)でbs.length!がnに等しい。失敗時はErr({kind, message})(§4)。
返り値は Result であって生の Bytes ではない。 エントロピー源は「無い環境が実在する」もので (§3)、unwrap! を強いる形にしておかないと、持っていない能力が黙って弱い値に化ける。std:crypto のハッシュが Result を返さないのは、あちらが失敗しうる外界に触れないためである。
2.1 品質の主張
予測不能性を主張する。 ホストが提供する暗号論的擬似乱数生成器 (§3) をそのまま通し、この処理系の側で伸長・混合・キャッシュをしない。過去の出力から次を予測できないこと、内部状態が漏れても過去の出力を再構成できないことは、ホストの保証に従う。
再現する道を持たない。 種を与える口は設けない。試験のために固定値が要るなら、値を作る関数の側を引数で受ける形に書き換えること (pkg:record の run が時刻を注入するのと同じ形)。種を与えられる暗号乱数は、その種が漏れた瞬間にすべての鍵が漏れる。
3. 対応プラットフォーム
ホストのエントロピー源は次を使う。いずれもブロックしない (プールの初期化待ちを除く)。
| プラットフォーム | 源 |
|---|---|
| Linux | getrandom(2) |
| macOS | getentropy(2) |
どちらも同じ 1 本の系統呼び出しの層で埋まり、追加の設定やインストールを要しない。
これ以外のプラットフォームでは提供しない — n が 0 より大きければ bytes は Err(unavailable) で返る (n が 0 なら §2 のとおりエントロピーを引かずに Ok である)。対象は wasm (Playground と REPL の面) と Windows である。したがって wasm では、これに依るコード (署名付きセッション等) は動かない。import は解決するが呼ぶと失敗する形は std:db/sqlite (../db/sqlite.md §10) と同じである。
Windows を挙げていないのは、この処理系が Windows を対象に持たないからである — hikari build の対応ターゲットはホスト 1 つだけで (../../build.md)、ホスト能力の実装は Unix の系統呼び出しの上に立っている。ここだけ Windows の源を約束しても、それを確かめる道が無い。
4. エラーモデル
失敗時の err は回復可能エラーオブジェクト {kind, message} (../../language-spec.md §16)。正規 kind (閉じた一覧):
| kind | 意味 |
|---|---|
unavailable |
このプラットフォームにエントロピー源が無い (wasm) |
io_error |
源はあるが読めなかった (ファイル記述子の枯渇・プールの初期化待ちの中断ほか) |
message は crypto/random.bytes: ... のように関数名を含む人間可読文字列。分岐は kind で行い、message の文字列マッチに依存しないこと。
引数の型・範囲違反 (非 Int・負の n) は Error (バグ層、../../language-spec.md §16.2) であり Err ではない。
5. 将来枠
uint64()/int(lo, hi)— 偏りの無い範囲付き整数。今はbytesから呼び手が組む。剰余の偏りを避ける書き方 (棄却サンプリング) を std が持つ値打ちはあるが、bytesの上に純 Hikari で書けるので急がないfill(buf)— 呼び手が確保した可変なバイト列へ直に書き込む口。std:arrayのArrayは要素がバイトではなく Hikari の値を持つハンドルなので使えない (../array.md §1。32 バイトの鍵のために 32 個の箱入り値を確保してからbytes()でまとめるのでは、割り当ては減るどころか増える)。std に可変なバイト列の型が入れば、そちらへ直に埋める口を足せる — 確保する主体が呼び手へ移るだけで、bytes(n)はその上に立つ便宜口にできる。それでも上限が無くなるわけではない — 確保する主体が変わるだけである