在現代系統中,API Token、Session Key、JWT Secret、資料庫連線密碼等 Secret 無所不在。這類機密與人類記憶的密碼不同 — 它們不需被人類記憶,而是由機器產生並儲存,因此其安全性幾乎完全取決於產生的那一瞬間。本文聚焦於單一隨機對稱金鑰的安全產生,介紹熵 (Entropy)、安全強度 (Security Strength)、CSPRNG 等核心概念,並提供可直接於生產環境使用的指令範例。

怎麼樣的 Secret 才是安全的?

衡量 Secret 的安全性,最直觀的指標是 熵 (Entropy),可以用以下公式表示:

其中L為字元長度、 R為字元集大小,熵值越大越難被暴力破解,此公式給出的是資訊熵 (Shannon Entropy),要等於實際的安全強度 (Security Strength),必須同時滿足:

  • 每個字元均勻分佈,即字元集中的各字元出現的機率為
  • 每個字元獨立取樣
  • 亂數來源為 CSPRNG,攻擊者無法從歷史輸出回推內部狀態,保證不可預測性

以下為常見字元集的熵值,比如 64 字元的 Hex 字串的熵值就是 256 bits。

字元集R每字元的熵
Hex (0-9a-f)164 bits
Base64URL (A-Za-z0-9-_)646 bits
RFC 3986 非保留字元 (A-Za-z0-9-._~)66≈ 6.044 bits

根據 NIST SP 800-131A,2030 年後對稱演算法的最低安全強度 (Security Strength) 為 128 bits,對於以 CSPRNG 直接產生的對稱金鑰而言,這等同於要求至少 128 bits 的熵值,換算回字元長度,即至少需要 32 個 hex 字元或 22 個 RFC 3986 非保留字元。

該用哪個字元集

參考上面的公式,字元集範圍越大熵值越高,但還需要考慮系統的保留字元來決定。
許多程式會以 URL 的形式傳遞 Secret,例如資料庫 PostgreSQLpostgres://user:password@host:5432/dbRedisredis://:password@host:6379 等連線字串,若混入了 +/=@:#?& 等保留字元,部分函式庫可能不會自動進行 URL 編碼 導致解析錯誤。
RFC 3986 非保留字元集可確保產生的字串在任何 URL 傳輸、設定檔、Shell 環境變數場景中都能安全傳遞,無需額外編碼。大部分生產環境的系統都支援 RFC 3986 非保留字元集,當然少數老舊的系統或者自製的程式也有可能只支援英數字。

如何產生安全的亂數?

前面提到熵值的計算需要符合三個條件,如果任一條件不成立,都會使得有效熵大幅降低

例如人類偏好以字母開頭、以數字結尾、把 1 或者 ! 放在密碼最後一位等等,這些統計偏差會讓某些位置的條件機率遠高於

一般程式語言內建的 rand() 通常屬於非密碼學安全偽亂數產生器 (non-cryptographic PRNG),它們只追求統計上的均勻分佈,並不抵抗預測攻擊,只要攻擊者觀察到部分輸出,就能反推內部狀態並預測後續輸出。如 Python 的 random 模組使用 Mersenne Twister (MT19937),攻擊者只要取得 624 個連續 32-bit 輸出即可完整重建內部狀態並預測未來所有輸出 (參考)。

因此我們需要先有一個能產生出符合條件的亂數來源,所幸我們不需要費心處理這部分,目前的作業系統核心通常都有提供可信任的 CSPRNG 來源,如 Linux 的 /dev/urandom 與 Windows 的 BCryptGenRandom

產生安全 Secret 指令

以下幾種方法皆能從 Shell 直接產生 Secret

工具平台依賴CSPRNG 來源字元集彈性
OpenSSL跨平台OpenSSL 內部 DRBG (SP 800-90A)hex / base64
Python跨平台OS CSPRNG完全自訂
Authelia CLIDockerGo內建多種 charset、可自訂
/dev/urandomLinux onlyKernel CSPRNG完全自訂

1. OpenSSL

OpenSSL 是一個開源的軟體函式庫套件,是網路安全領域中最核心的工具,包含了密碼演算法庫、常用金鑰和憑證管理功能,以及命令列工具,主要用於加密、解密、生成憑證與數位簽章。

openssl rand -hex 32

最精簡、相容性最高的方式,將 32 bytes 的隨機數轉換成 hex,輸出 64 個 0-9a-f 字元,熵值剛好等於現代對稱密鑰常用的 256 bits。

openssl rand -base64 48 | tr '+/' '-_' | tr -d '\n'

將 48 bytes 的隨機數轉換為 64 位 Base64 編碼,再透過 tr+ / 替換為 RFC 4648 §5 規定的 URL 安全字元 - _,也就是 Base64URL 編碼,熵值為 384 bits。

2. Python

python -c "import secrets, string; charset = string.ascii_letters + string.digits + '-._~'; print(''.join(secrets.choice(charset) for _ in range(64)))"

Python 自 3.6 起提供的 secrets 模組就是專為密碼學用途設計的高階介面,底層直接呼叫作業系統 CSPRNG。此寫法明確指定 RFC 3986 非保留字元集,並產生 64 位字串,熵值 ≈ 386.8 bits。

3. Authelia 容器化指令

docker run --rm authelia/authelia:latest authelia crypto rand --length 64 --charset rfc3986

Authelia 是知名的開源 SSO/2FA 解決方案,其 CLI 內建了 crypto rand 指令可用來產生亂數,搭配charset參數來指定需要的字元集。

4. /dev/urandom

LC_ALL=C tr -dc 'A-Za-z0-9._\~-' < /dev/urandom | head -c 64; echo

不依賴任何額外工具,直接從 Linux 核心提供的 CSPRNG 字元裝置讀取資料,再以 tr -dc 過濾出非保留字元並截斷至 64 字元。

結論

產生安全 Secret 並不需要複雜的程式碼,但必須掌握三個原則:

  • 熵源必須是 CSPRNG
  • 熵值至少達到 128 bits
  • 在 URL、連線字串、設定檔等傳輸情境中,將字元集限制為 RFC 3986 非保留字元,可徹底消除 URL 編碼造成的解析歧異。

只要這三件事同時做對,產生的 Secret 在任何情境中都能安全傳遞,無需額外編碼、無需擔心解析歧異、被預測或暴力破解。

本文的討論範圍僅限於單一隨機對稱金鑰的安全產生,例如資料庫密碼、API Token、Session Key、JWT 對稱簽章用的 HS256 Secret 等情境。實務上密碼學工程涵蓋的議題非常廣泛:

  • 金鑰儲存、傳遞、輪替、稽核、撤銷
  • 密碼雜湊 (Hash)
  • 非對稱金鑰對 (RSA、ECDSA、Ed25519)
  • 數位簽章 (Digital Signature)
  • 加密金鑰的派生與管理 (KMS/HSM)
  • 後量子密碼學 (PQC) 的金鑰產生

產生安全亂數是密碼學工程中基礎但重要的一環,至於更複雜的議題,未來有機會再以獨立文章逐一展開。

參考資料