案例分享

PQC 遷移的第一步:用 cbom_scanner 替內網密碼學資產建檔

PQC 過渡期最大的盲點不是換不到演算法,而是不知道哪裡還在用 SHA-1、3DES、RSA-1024。cbom_scanner 是一支內網密碼學資產掃描器,整合 sslscan / ssh-audit,輸出符合 CycloneDX 1.6 的 CBOM(Cryptography Bill of Materials),讓你在量子日來臨前先把家底盤清楚。

作者:雲杉科技
#CBOM #CycloneDX #PQC #資安治理 #密碼學盤點 #後量子密碼
PQC 遷移的第一步:用 cbom_scanner 替內網密碼學資產建檔

一張會議室裡沒人答得出的問題

在某次 PQC 遷移評估會議上,客戶的資安長丟了三個問題:

  1. 我們公司的內網主機,現在還有多少地方在用 SHA-1?
  2. 哪些 TLS 服務還允許 RSA-1024 或 3DES?
  3. 我們有多少張自簽憑證、各自還有多少天到期?

整個會議室安靜了三十秒。

不是沒人想回答,是真的沒人答得出來。資產清單可以列出 IP 與作業系統,弱點掃描可以告訴你哪台主機有 CVE,但「我們還在用哪些密碼學演算法、各出現在哪台主機的哪個 port」這種問題,傳統工具就是沒有設計來回答它。

這個盲點在 SBOM 時代並不嚴重,因為大家還在處理「軟體裡有哪些函式庫」這個層次的問題。但進入 PQC 過渡期之後,盲點立刻變成風險

  • NIST 已經正式發布 ML-KEM(FIPS 203)、ML-DSA(FIPS 204)、SLH-DSA(FIPS 205)三套標準。
  • NSA 的 CNSA 2.0 給出明確時程,2030 年前所有 NSS(國安系統)必須完成 PQC 遷移,2033 年完成淘汰。
  • 各國金管、CA/Browser Forum、ETSI TC CYBER 都在制定對應規範。

這時候你會發現:沒辦法盤點,就沒辦法遷移。Q-Day 並不是某個特定日期,而是一段過渡期 — 而過渡期的工程任務,第一步永遠是「把現況清出來」。

cbom_scanner 就是針對這個第一步寫的工具。


CBOM 是什麼,為什麼不是 SBOM

SBOM(Software Bill of Materials)描述軟體的組成:哪些 npm 套件、哪些 maven artifact、哪些 OS package。它的目標是供應鏈透明度(log4shell 之後的標配)。

CBOM(Cryptography Bill of Materials)描述的是另一個維度:這個系統正在使用哪些密碼學原語、密鑰、憑證、協定。它的目標是:

  • 量子過渡風險評估:哪些演算法在 Q-Day 後會被破解?
  • 合規證據:例如 PCI DSS、CNSA 2.0、台灣金管會的 PQC 規範。
  • 金鑰生命週期治理:憑證何時到期?金鑰用了多久?

業界目前事實上的格式標準是 CycloneDX 1.6 — OWASP 維護、被 NIST、Linux Foundation、各大雲廠採納,schema 把密碼學資產分成三類 assetType

assetType描述例子
algorithm密碼學演算法本身AES-256-GCM、RSA-SHA256、ML-KEM-768
certificateX.509 憑證自簽 NTLS 憑證、企業 CA 憑證
protocol密碼學協定組合TLSv1.3 + cipherSuite[]、SSHv2

每個 component 都有自己的屬性塊(algorithmProperties / certificateProperties / protocolProperties),並且能透過 bom-ref 互相引用 — 例如憑證的 signatureAlgorithmRef 指向對應的演算法 component,組成一張可機讀的密碼學資產圖。

CBOM 1.6 的三類 assetType:algorithm、certificate、protocol 與其欄位

Spec 看起來很複雜,實際上對使用者只有一個重點:有了 CBOM 之後,你可以拿這份 JSON 餵進 Dependency-Track 之類的工具,做風險查詢、過期警示、PQC 遷移地圖


cbom_scanner 在做什麼

給它一段 CIDR,它在幾分鐘內告訴你這個網段所有 TLS 與 SSH 服務在用哪些密碼學資產,並輸出標準 CBOM JSON。

整支工具是一支 CLI,編譯後就是一個 static binary,部署到內網跳板機上即可執行。一行指令的範例:

crypto-scanner --cidr 192.168.1.0/24 --out cbom.json

它的內部資料流如本文 hero 圖所示,五個階段串成一條 pipeline:

  1. Target Collection--cidr--hosts 兩種來源彙整成 target list,BTreeMap 自動去重。
  2. Port Discovery — 對未指定 port 的 target,內建 TCP scanner 平行探測 13 個服務 port。
  3. Service Dispatcher — 依 port 對應到 sslscan / ssh-audit,TLS 上的 STARTTLS 自動加上對應旗標。
  4. Concurrent Scanning — 並發呼叫外部工具,semaphore 控制上限。
  5. CBOM Assembly — 解析、跨主機合併、貼 Strength tag,輸出 CycloneDX 1.6 JSON。

幾個更實用的指令:

# 主機清單(每行一個 HOST 或 HOST:PORT,支援 IPv6 [fe80::1]:8443)
crypto-scanner --hosts hosts.txt --out cbom.json

# CIDR + 額外清單一起掃,自訂並發、timeout、verbose
crypto-scanner \
  --cidr 10.0.0.0/24 \
  --hosts extra.txt \
  --out cbom.json \
  --concurrency 20 \
  --timeout 60 \
  --connect-timeout 3 \
  --verbose

# 外部工具不在 PATH 上時可手動指定
crypto-scanner --cidr 10.0.0.0/24 \
  --sslscan-path /opt/sslscan/bin/sslscan \
  --ssh-audit-path /opt/ssh-audit/ssh-audit

外部工具相依只有 sslscanssh-audit 兩支 — 兩者皆缺才會 abort,任一可用就會盡量掃,並把另一邊跳過。這是刻意的設計:在內網跳板機這種精簡環境下,限制相依數量等同於降低部署摩擦。


為什麼這樣設計:三個關鍵取捨

這是案例分享的部分,把幾個工程上的判斷攤出來。

1. 跨主機合併同名演算法

第一版的 CBOM 輸出有個問題:同一個演算法(例如 AES-256-GCM)出現在 200 台主機上,就會產生 200 個重複的 component。這份 JSON 很快會炸到 GB 等級,餵進 Dependency-Track 之後查詢也慢。

CycloneDX 1.6 spec 對這個情境有設計:同一個 bom-ref 可以累積多筆 occurrences[],每筆 occurrence 帶 locationadditionalContext

於是組裝邏輯改成:

bom_ref = "algo-" + slug(algorithm.name)
entry   = algo_map.get_or_insert(bom_ref, AlgoEntry{
    name           : algorithm.name,
    occurrences    : [],
    worst_strength : Good,
})
entry.occurrences.append({
    location          : "<host>:<port>",
    additionalContext : algorithm.context,   // "TLSv1.3 cipher" / "SSH KEX" / ...
})
if severity(algorithm.strength) > severity(entry.worst_strength):
    entry.worst_strength = algorithm.strength

一個 AES-256-GCM component,無論出現在幾台主機,都共用一個 bom-ref,所有出現位置進到 occurrences[]。掃完 /24 之後 component 數量從幾千降到幾十。

2. Strength tag:weak / broken / warning

CycloneDX 1.6 標準允許 component 上掛任意 tags[]。我們把演算法強度寫成三種 tag:

Strength enumtag觸發條件
Broken"broken"RC4、SHA-1(簽章用)、3DES、NULL cipher、SHA-1 of cert
Weak"weak"RSA < 2048 bits、DH-1024、CBC mode in TLS 1.2、HMAC-SHA1
Acceptable"warning"仍可用但建議遷移:例如不支援 PFS 的 cipher、TLS 1.0/1.1
Good(無 tag)TLS 1.3 AEAD cipher、X25519、Ed25519、ML-KEM、ML-DSA

而且這個 tag 是最壞情況優先:同一個演算法在不同主機可能用法不同(例如 SHA-256 既用在 cipher 也用在簽章),組裝時取所有 occurrence 中最差的那個 strength 寫進 component。

這個設計讓使用者可以一行 jq 抓出所有 broken/weak component:

jq '.components[] | select(.tags // [] | index("broken"))' cbom.json
jq '.components[] | select(.tags // [] | index("weak")) | .name' cbom.json

3. subprocess 失敗不中斷整體掃描

掃 /24 網段時很常遇到單一主機行為怪異:某台 server 把 sslscan 卡 5 分鐘、某台 ssh-audit parse 失敗、某台 timeout。傳統腳本的反應是:整批掛掉、留一個半成品 JSON。

cbom_scanner 的策略是永遠完成整批掃描

  • 每個外部工具呼叫都包 timeout(預設 60 秒)。
  • 所有 subprocess error 與 parse error 一律降級為 warn log,繼續處理下一個 target。
  • 即使 sslscan 整支不可用(沒安裝),程式會走「只掃 SSH」的路徑而不是 abort。

換句話說,不完美的 CBOM 也比沒有 CBOM 好。先把能掃到的部分輸出,再針對 warn log 中的失敗 target 補掃,是更務實的工作流程。


掃完一份 CBOM 能解什麼問題

回到一開始那位資安長的三個問題。掃過 /16 之後,這些查詢都變成 jq 一行:

Q1:我們還有多少地方在用 SHA-1?

jq '.components[]
  | select(.name | test("SHA-?1$"; "i"))
  | {name, occurrences: .occurrences | length, hosts: [.occurrences[].location]}' cbom.json

回傳:演算法名稱、出現次數、出現的所有主機與 port。資安長要的不是抽象百分比,而是「就是這 47 個 endpoint 還在跑」。

Q2:哪些 TLS 服務還允許 RSA-1024?

jq '.components[]
  | select(.cryptoProperties.assetType == "certificate")
  | select(.certificateProperties.subjectPublicKeyAlgorithm == "RSA")
  | select(.certificateProperties.subjectPublicKeyBits < 2048)
  | {cn: .certificateProperties.subjectName, hosts: [.occurrences[].location]}' cbom.json

Q3:我們有多少張自簽憑證、各自還有多少天到期?

jq '.components[]
  | select(.cryptoProperties.assetType == "certificate")
  | select(.certificateProperties.subjectName == .certificateProperties.issuerName)
  | {cn: .certificateProperties.subjectName,
     expiry: .certificateProperties.notValidAfter,
     hosts: [.occurrences[].location]}' cbom.json

這是 CBOM 的真正價值:它不只是給人看的報表,而是一份可機讀的密碼學資產圖。任何問題都能用結構化查詢回答,不再需要每次資安檢查都重跑一輪人工盤點。

對於走 PQC 遷移路線的組織來說,更直接的價值是遷移地圖

# 列出所有還沒支援 PQC 的 TLS 端點
jq '.components[]
  | select(.cryptoProperties.assetType == "protocol")
  | select(.protocolProperties.cipherSuites
      | all(.algorithms[] | test("ML-KEM|X25519MLKEM"; "i") | not))
  | .occurrences[].location' cbom.json

或者反過來,列出已經混合 PQC 的模範生,把它們的設定當基線推廣:

jq '.components[]
  | select(.cryptoProperties.assetType == "protocol")
  | select(.protocolProperties.cipherSuites
      | any(.algorithms[] | test("ML-KEM"; "i")))
  | .occurrences[].location' cbom.json

CBOM 配合定期掃描還能做 delta 比對:上週掃了一次、這週再掃一次、diff 兩份 JSON 找出新增的 broken/weak component — 也就是「這禮拜被引進的密碼學風險」。對於有正式變更管控的組織,這就是一份天然的合規證據鏈。

為什麼掃描必須週期性化

一次性盤點失效、憑證半衰期週級化、CBOM 必須當持續訊號

CA/Browser Forum 已把 Code Signing 憑證從 39 月降到 460 天;TLS 預計 2027 年降至 100 天、2029 年降至 47 天。憑證的「半衰期」進入週級數,意味著任何「一次性盤點 → 鎖死狀態」的策略都會迅速過時。CBOM 必須被當成持續訊號而非快照 — 每週甚至每日掃過一次,配合 diff 工具追蹤新進入的弱點、即將過期的憑證、與新部署的 PQC 演算法。Code Signing 那篇文章詳細鋪陳了這條時程,這裡只指出 CBOM 在這條趨勢下的角色:自動化盤點工具


下一步

cbom_scanner 目前是 v0.1.0,定位是「能夠輸出標準 CycloneDX 1.6 CBOM 的最小可用工具」。後續路線圖上有幾個方向:

  • 整合 Dependency-Track:把 CBOM 自動上傳,做為長期資產資料庫,配合 webhook 在 broken/weak 出現時送告警。
  • 掃描排程化:定期以 systemd timer 或 k8s CronJob 執行,產出 cbom-2026-W19.json 形式的 weekly snapshot。
  • PKCS#11 / KMIP collector:除了網路服務之外,把 Luna HSM 上的 partition 金鑰、KMIP server 的 key inventory 也納入 CBOM。這樣才是完整的「金鑰 + 演算法 + 憑證」三位一體盤點。
  • Diff 工具:讀兩份 CBOM、輸出 component-level 的 added / removed / strength-changed 報告。

結語

PQC 遷移是一條至少十年的路,但第一步是把家底盤清楚。cbom_scanner 不是最炫的工具 — 它只是把 sslscan、ssh-audit 這些已經很可靠的命令列工具串起來,產出一份結構化的 CycloneDX 1.6 JSON。

但這份 JSON 把資安長那三個原本沒人答得出的問題,變成 jq 一行能回答的查詢。對於正在準備 PQC 遷移、CNSA 2.0 合規、或是單純想知道自己內網有多少 SHA-1 的團隊,這就是一個務實的起點。

延伸閱讀: