Linux 隨機性 API

最後更新: 2024-10-22 20:12:02 +0800 +0800
原文以英文撰寫。您正在閱讀AI翻譯版本。

隨機性 API 服務兩種不同契約。確定性的偽隨機數產生器適合可重複的測試與模擬。安全敏感的金鑰、nonce、權杖、鹽值與種子則需要來自核心密碼學安全亂數產生器(CSPRNG)的位元組。

C 函式庫產生器 #

rand() 推進隱藏的程序狀態,並回傳 0 到 RAND_MAX 的整數。srand(seed) 為特定 C 函式庫實作選取可重複序列;省略它時,行為如同種子為 1。ISO C 未指定演算法,也不要求不同實作使用相同序列,因此需要跨平台可重現性的測試應選擇並記錄明確演算法。

random() 與 srandom() 提供另一個確定性系列。glibc 以加法回饋狀態實作 random(),並允許呼叫端使用 initstate() 與 setstate() 切換狀態陣列;random_r() 使該狀態明確。rand_r() 只儲存一個 unsigned int 狀態,且在 POSIX 中已過時,因此不適合獨立的高品質串流設計。

drand48() 系列指定 48 位元線性同餘遞迴:

text
X(n + 1) = (0x5DEECE66D * X(n) + 0xB) mod 2^48

drand48() 回傳 [0.0, 1.0) 中的 double,lrand48() 回傳 [0, 2^31) 中的 long,而 mrand48() 回傳 [-2^31, 2^31) 中的有號結果。erand48()、nrand48() 與 jrand48() 使用呼叫端提供的三元素狀態;_r 介面使用明確的可重入狀態結構。

攻擊者只要得知足夠狀態或輸出,上述每個系列都可預測。以目前時間、PID 或單一核心隨機值為其設種,不會讓後續輸出變成密碼學產生器。

核心熵與 CSPRNG #

Linux 會將計時與裝置資料混合至輸入池,並在初始化期間保守地計入不可預測的貢獻。計入足夠熵後,核心會將池狀態最終化為 CSPRNG 金鑰。目前上游程式碼使用以 ChaCha 為基礎的快速金鑰抹除產生器,並定期從池重新整理其金鑰。應用程式消耗 CSPRNG 輸出;它們不會每輸出一個位元組就耗盡一個位元組的原始硬體雜訊。

getrandom(2) 是主要的 Linux 介面,因為它不需要路徑名稱或檔案描述元:

  • getrandom(buf, len, 0) 會等待 CSPRNG 初始化。
  • GRND_NONBLOCK 在早期開機狀態等待時回傳 EAGAIN,而不是等待。
  • 已初始化的 urandom 來源請求若不超過 256 位元組,會回傳要求的長度且不會被訊號中斷。較大的請求可能回傳短讀,或以 EINTR 失敗。
  • getentropy(3) 是限制為 256 位元組的 libc 介面。只有填滿整個緩衝區後才回傳零,否則回傳 -1。

GRND_RANDOM 是舊式相容性旗標,不是更高安全性的模式。Linux 5.6 之前,隨機來源讀取可能依熵估計而阻塞。目前的上游 random.c 將 /dev/random 視同 getrandom(..., 0),只阻塞至初始化完成;舊核心保留舊行為。可攜式新程式碼應使用旗標 0。

裝置語意與早期開機 #

/dev/urandom 絕不等待初始化。目前核心原始碼將它等同於 GRND_INSECURE,因此異常早期的讀取可能發生在 CSPRNG 取得安全種子之前。getrandom(..., 0) 會等待,避免此競爭。在初始化後,/dev/urandom、/dev/random 與一般 getrandom 輸出都來自核心 CSPRNG;等待熵計數器重新填滿不會改善產生的金鑰。

自 Linux 5.6 起,/dev/random 在初始化後不再阻塞,而其 O_NONBLOCK 設定只在初始化尚未完成時有影響。軟體必須在較舊核心上執行時,歷史差異仍然重要。寫入任一裝置都會將資料混合至池中,但除非特權的隨機裝置 ioctl 明確如此,否則不會計入熵。

安全金鑰產生 #

務必檢查錯誤與長度。即使緩衝區日後增長超過小請求保證,迴圈仍保持正確:

c
#include <errno.h>
#include <stddef.h>
#include <sys/random.h>

static int
random_bytes(void *buffer, size_t length)
{
    unsigned char *p = buffer;

    while (length != 0) {
        ssize_t n = getrandom(p, length, 0);
        if (n > 0) {
            p += (size_t)n;
            length -= (size_t)n;
            continue;
        }
        if (n < 0 && errno == EINTR)
            continue;
        return -1;
    }
    return 0;
}

unsigned char key[32];
if (random_bytes(key, sizeof key) < 0) {
    /* Abort key creation; never continue with an unfilled key. */
}

直接產生金鑰位元組,而不要將它們送入 rand()。若要取得小於上限且無偏差的整數,請使用拒絕取樣;除非來源範圍是上限的整數倍,否則 value % bound 具有偏差。產生後,祕密生命週期、安全抹除、nonce 唯一性與檔案權限仍是分開的責任。

上游 getrandom(2) 與 random(4) 頁面定義使用者空間 ABI。目前的實作細節與 5.6 之後的裝置等價性記錄在核心 drivers/char/random.c 原始碼中。

評論

在 GitHub 上閱讀或評論(發表評論需要登入)