一張會議室裡沒人答得出的問題
在某次 PQC 遷移評估會議上,客戶的資安長丟了三個問題:
- 我們公司的內網主機,現在還有多少地方在用 SHA-1?
- 哪些 TLS 服務還允許 RSA-1024 或 3DES?
- 我們有多少張自簽憑證、各自還有多少天到期?
整個會議室安靜了三十秒。
不是沒人想回答,是真的沒人答得出來。資產清單可以列出 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 |
certificate | X.509 憑證 | 自簽 NTLS 憑證、企業 CA 憑證 |
protocol | 密碼學協定組合 | TLSv1.3 + cipherSuite[]、SSHv2 |
每個 component 都有自己的屬性塊(algorithmProperties / certificateProperties / protocolProperties),並且能透過 bom-ref 互相引用 — 例如憑證的 signatureAlgorithmRef 指向對應的演算法 component,組成一張可機讀的密碼學資產圖。

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:
- Target Collection —
--cidr與--hosts兩種來源彙整成 target list,BTreeMap 自動去重。 - Port Discovery — 對未指定 port 的 target,內建 TCP scanner 平行探測 13 個服務 port。
- Service Dispatcher — 依 port 對應到
sslscan/ssh-audit,TLS 上的 STARTTLS 自動加上對應旗標。 - Concurrent Scanning — 並發呼叫外部工具,semaphore 控制上限。
- 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
外部工具相依只有 sslscan 與 ssh-audit 兩支 — 兩者皆缺才會 abort,任一可用就會盡量掃,並把另一邊跳過。這是刻意的設計:在內網跳板機這種精簡環境下,限制相依數量等同於降低部署摩擦。
為什麼這樣設計:三個關鍵取捨
這是案例分享的部分,把幾個工程上的判斷攤出來。
1. 跨主機合併同名演算法
第一版的 CBOM 輸出有個問題:同一個演算法(例如 AES-256-GCM)出現在 200 台主機上,就會產生 200 個重複的 component。這份 JSON 很快會炸到 GB 等級,餵進 Dependency-Track 之後查詢也慢。
CycloneDX 1.6 spec 對這個情境有設計:同一個 bom-ref 可以累積多筆 occurrences[],每筆 occurrence 帶 location 與 additionalContext。
於是組裝邏輯改成:
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 enum | tag | 觸發條件 |
|---|---|---|
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 — 也就是「這禮拜被引進的密碼學風險」。對於有正式變更管控的組織,這就是一份天然的合規證據鏈。
為什麼掃描必須週期性化

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 的團隊,這就是一個務實的起點。
延伸閱讀:
- Luna HSM × Prometheus — 金鑰運維面的監控
- Code Signing 憑證縮短至 460 天 — PKI 產業趨勢 — 為什麼 CBOM 必須週期性掃描而非一次性盤點
- NG-CA — Pure Rust + Luna HSM 後量子 CA — 簽發端如何配合 CBOM 的需求