Share Notes

chundev

View the Project on GitHub latteouka/share-notes

SSL/TLS 憑證格式全解析 — 拆解、辨識、打包 CA Chain 的實戰指南

日期:2026-03-31


TL;DR

憑證檔案的本質就是兩種東西:編碼格式(文字 or 二進位)和內容類型(只有憑證 or 含私鑰)。副檔名經常騙人,用 fileopenssl 指令才能確認真實格式。CA Chain 的正確順序是 Leaf → Intermediate → Root,用 Subject/Issuer 比對就能驗證。


憑證檔案的本質 — 都是文字嗎?

不全是,但大部分是。 憑證世界只有兩種編碼格式:

兩種編碼格式

格式 本質 用文字編輯器打開 特徵
PEM Base64 文字 ✅ 看得到內容 -----BEGIN CERTIFICATE----- 開頭
DER 二進位 ❌ 看到亂碼 原始的 ASN.1 二進位編碼

PEM 和 DER 的關係:

DER(二進位)  →  Base64 編碼  →  加上 header/footer  →  PEM(文字)

也就是說,PEM = DER 的 Base64 文字版。兩者承載的資訊完全一樣,只是表達方式不同。

PEM 格式長這樣:

-----BEGIN CERTIFICATE-----
MIIDXTCCAkWgAwIBAgIJALaGSC3J9gL4MA0GCSqGSIb3DQEBCwUA
MDExCzAJBgNVBAYTAlVTMRMwEQYDVQQIDApDYWxpZm9ybmlhMQ0w
... (Base64 encoded binary data) ...
CwYDVQQHDARTYW4gRnJhbmNpc2NvMB4XDTE4MDEwMTAwMDAwMFoX
-----END CERTIFICATE-----

中間那堆看起來像亂碼的東西,其實是二進位資料經過 Base64 編碼後的結果。用 openssl x509 -text 就能把它解碼成人類看得懂的資訊。


副檔名 vs 實際格式 — 副檔名會騙人

這是最容易搞混的地方。副檔名代表的是「慣例」,不是「保證」。

副檔名對照表

副檔名 通常的編碼 內容 說明
.pem PEM(文字) 憑證、私鑰、或兩者都有 最通用的副檔名,什麼都可能裝
.crt PEM 或 DER 憑證(不含私鑰) Linux/macOS 常用
.cer PEM 或 DER 憑證(不含私鑰) Windows 常用,和 .crt 本質一樣
.key PEM(文字) 私鑰 -----BEGIN PRIVATE KEY----------BEGIN RSA PRIVATE KEY----- 開頭
.der DER(二進位) 憑證 明確標示為 DER 格式
.p12 / .pfx 二進位(PKCS#12) 憑證 + 私鑰 + CA 鏈,有密碼保護 Windows/IIS 愛用,macOS 也認
.p7b / .p7c PEM 或 DER(PKCS#7) 只有憑證鏈,不含私鑰 Windows/Java 環境常見
.jks 二進位(Java Keystore) Java 專用的金鑰庫 舊版 Java 用,新版建議改用 PKCS#12
.csr PEM(文字) 憑證簽署請求 -----BEGIN CERTIFICATE REQUEST----- 開頭

重點


拆解流程 — 拿到檔案的第一步

當你拿到一個憑證檔案,用以下流程判斷它是什麼:

Step 1:用 file 指令初判

file certificate.*

輸出範例:

certificate.pem: PEM certificate           # → PEM 格式
certificate.der: data                      # → 可能是 DER 二進位
certificate.pfx: data                      # → 可能是 PKCS#12
certificate.p7b: data                      # → 可能是 PKCS#7

Step 2:用文字編輯器或 head 看一眼

head -5 certificate.crt
看到什麼 代表什麼
-----BEGIN CERTIFICATE----- PEM 格式的憑證
-----BEGIN RSA PRIVATE KEY----- PEM 格式的 RSA 私鑰
-----BEGIN PRIVATE KEY----- PEM 格式的私鑰(通用格式)
-----BEGIN ENCRYPTED PRIVATE KEY----- 有密碼保護的私鑰
-----BEGIN CERTIFICATE REQUEST----- CSR 憑證簽署請求
-----BEGIN PKCS7----- PKCS#7 格式
亂碼/二進位垃圾 DER 或 PKCS#12(二進位格式)

Step 3:用 openssl 確認並解讀

依據 Step 2 的判斷,用對應的指令:

# PEM 格式憑證
openssl x509 -in cert.pem -text -noout

# DER 格式憑證(二進位)
openssl x509 -in cert.der -inform DER -text -noout

# PKCS#12(.pfx / .p12)— 會問密碼
openssl pkcs12 -in cert.pfx -info -nokeys

# PKCS#7(.p7b)
openssl pkcs7 -in cert.p7b -print_certs -noout

# 私鑰
openssl rsa -in private.key -check -noout

# CSR
openssl req -in request.csr -text -noout

Step 4:如果前面都失敗,暴力試

# 依序試,哪個成功就是哪種格式
openssl x509 -in mystery.file -text -noout 2>/dev/null && echo "==> PEM certificate"
openssl x509 -in mystery.file -inform DER -text -noout 2>/dev/null && echo "==> DER certificate"
openssl pkcs12 -in mystery.file -info -nokeys 2>/dev/null && echo "==> PKCS#12"
openssl pkcs7 -in mystery.file -print_certs 2>/dev/null && echo "==> PKCS#7"
openssl rsa -in mystery.file -check -noout 2>/dev/null && echo "==> RSA private key"

憑證鏈(Certificate Chain)的觀念

信任模型

🏛️ Root CA(根憑證)
  ├── 自己簽自己(Self-Signed)
  ├── 預先安裝在 OS / 瀏覽器的信任庫裡
  └── 簽發 ↓

    🏢 Intermediate CA(中繼憑證)
      ├── 由 Root CA 簽發
      ├── 可能有多層(Intermediate 1 → Intermediate 2)
      └── 簽發 ↓

        📄 Leaf Certificate(葉憑證 / Server 憑證)
          ├── 由 Intermediate CA 簽發
          └── 就是你的網站 / 服務用的那張憑證

為什麼需要 Intermediate?

Root CA 的私鑰太珍貴,不會直接拿來簽發終端憑證。用 Intermediate CA 當中間人:


如何判斷一張憑證的角色

openssl x509 -in cert.pem -text -noout | grep -E '(Subject:|Issuer:|CA:)'
特徵 Leaf(Server) Intermediate CA Root CA
Subject ≠ Issuer ❌(Subject = Issuer)
CA:TRUE
CA:FALSE 或無此欄位
可以簽發其他憑證

快速判斷指令

# 看 Subject 和 Issuer
openssl x509 -in cert.pem -noout -subject -issuer

# 輸出範例(Leaf):
# subject= /CN=www.example.com
# issuer= /CN=Let's Encrypt Authority X3    ← 不同,是別人簽的

# 輸出範例(Root):
# subject= /CN=DST Root CA X3
# issuer= /CN=DST Root CA X3                ← 相同,自己簽自己

CA Chain 的正確順序與打包

正確順序

1. Leaf Certificate(你的 Server 憑證)
2. Intermediate CA 1(離 Leaf 最近的)
3. Intermediate CA 2(如果有多層)
4. Root CA(通常省略,因為 OS/瀏覽器已經有)

驗證順序的方法 — Subject/Issuer 接龍

每張憑證的 Issuer 必須等於下一張憑證的 Subject

# 假設你有三個檔案
for cert in leaf.pem intermediate.pem root.pem; do
  echo "=== $cert ==="
  openssl x509 -in "$cert" -noout -subject -issuer
  echo ""
done

正確的輸出應該是一條完整的鏈:

=== leaf.pem ===
subject= /CN=www.example.com
issuer= /O=Let's Encrypt/CN=R3          ← 指向 intermediate

=== intermediate.pem ===
subject= /O=Let's Encrypt/CN=R3         ← 被 leaf 指向
issuer= /O=Digital Signature Trust/CN=DST Root CA X3  ← 指向 root

=== root.pem ===
subject= /O=Digital Signature Trust/CN=DST Root CA X3  ← 被 intermediate 指向
issuer= /O=Digital Signature Trust/CN=DST Root CA X3   ← 自己簽自己

打包成 fullchain 檔案

確認順序正確後,直接串接:

cat leaf.pem intermediate.pem > fullchain.pem

# 如果需要包含 Root(少數情況)
cat leaf.pem intermediate.pem root.pem > fullchain-with-root.pem

⚠️ 注意:每個 PEM 區塊之間不能有空行缺失。確保每個 -----END CERTIFICATE----- 後面接著下一個 -----BEGIN CERTIFICATE-----(可以有一個換行)。

用 openssl verify 驗證整條鏈

# 驗證 leaf 是否能透過 intermediate 追溯到 root
openssl verify -CAfile root.pem -untrusted intermediate.pem leaf.pem

# 驗證 fullchain(用系統信任庫)
openssl verify fullchain.pem

# 顯示完整鏈的細節
openssl verify -show_chain -CAfile root.pem -untrusted intermediate.pem leaf.pem

如果你拿到的檔案缺 CA — 怎麼找回來

方法 1:從憑證本身找線索

openssl x509 -in leaf.pem -text -noout | grep -A 3 "Authority Information Access"

輸出可能會有:

Authority Information Access:
    CA Issuers - URI:http://r3.i.lencr.org/
    OCSP - URI:http://r3.o.lencr.org

那個 CA Issuers 的 URL 就是 Intermediate CA 的下載位址:

# 通常下載下來是 DER 格式,需要轉換
curl -o intermediate.der http://r3.i.lencr.org/
openssl x509 -in intermediate.der -inform DER -out intermediate.pem -outform PEM

方法 2:從線上服務抓

# 連到目標伺服器,把整條鏈抓下來
openssl s_client -connect example.com:443 -showcerts < /dev/null 2>/dev/null

輸出會包含所有憑證,每個 -----BEGIN CERTIFICATE----------END CERTIFICATE----- 就是一張。

方法 3:去 CA 官網下載

各大 CA 都有公開的 Intermediate / Root 憑證下載頁面。用 Issuer 裡的組織名稱去搜尋即可。


常見格式轉換指令

指令
PEM → DER 二進位 openssl x509 -in cert.pem -outform DER -out cert.der
DER → PEM 文字 openssl x509 -in cert.der -inform DER -outform PEM -out cert.pem
PEM → PKCS#12 打包含私鑰 openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.pfx -certfile ca.pem
PKCS#12 → PEM 拆出所有內容 openssl pkcs12 -in cert.pfx -out all.pem -nodes
PKCS#12 → 只取憑證 不要私鑰 openssl pkcs12 -in cert.pfx -clcerts -nokeys -out cert.pem
PKCS#12 → 只取私鑰 不要憑證 openssl pkcs12 -in cert.pfx -nocerts -nodes -out key.pem
PKCS#12 → 取 CA 鏈 只要 CA openssl pkcs12 -in cert.pfx -cacerts -nokeys -out ca.pem
PKCS#7 → PEM 展開 openssl pkcs7 -in cert.p7b -print_certs -out certs.pem
PEM → PKCS#7 打包(無私鑰) openssl crl2pkcs7 -nocrl -certfile cert.pem -out cert.p7b

實戰 Cheatsheet

# === 拿到檔案的 SOP ===

# 1. 這是什麼格式?
file mystery.crt
head -3 mystery.crt

# 2. 讀出憑證資訊
openssl x509 -in cert.pem -text -noout

# 3. 看有效期限
openssl x509 -in cert.pem -noout -dates

# 4. 看 Subject 和 Issuer(判斷角色 + 鏈的順序)
openssl x509 -in cert.pem -noout -subject -issuer

# 5. 看是不是 CA
openssl x509 -in cert.pem -text -noout | grep "CA:"

# 6. 驗證私鑰和憑證是否匹配
openssl x509 -in cert.pem -noout -modulus | openssl md5
openssl rsa -in key.pem -noout -modulus | openssl md5
# 兩個 MD5 值一樣就是配對的

# 7. 驗證完整憑證鏈
openssl verify -CAfile ca-bundle.pem cert.pem

# 8. 從線上伺服器抓憑證鏈
openssl s_client -connect example.com:443 -showcerts < /dev/null

# 9. 檢查線上伺服器的憑證到期日
openssl s_client -connect example.com:443 < /dev/null 2>/dev/null | \
  openssl x509 -noout -dates

學到的事


參考資料