iT邦幫忙

2026 iThome 鐵人賽

0
Software Development

我是Java工程師,關於密碼學我想懂的不多系列 第 33 篇

Day32 - 數位憑證與公鑰基礎設施 (PKI)

  • 分享至 

  • xImage
  •  

前言

在 Day 30 中,我們學習了數位簽章(如 Ed25519 與 RSA-PSS)如何確保資料的完整性與不可否認性。然而,非對稱密碼學面臨著一個致命的信任前提:「你如何確定手上的這把公鑰,真的屬於你以為的那個人或機構?」

如果攻擊者 Eve 在網路傳輸中發送了自己的公鑰,並宣稱自己是 google.com 或 bank.com.tw,那麼對 Eve 公鑰進行的任何簽章驗證都會順利通過。這就是經典的中間人攻擊(Man-in-the-Middle Attack, MITM)。

為了解決「公鑰真實性」的根本問題,密碼學界建立了 公鑰基礎設施(Public Key Infrastructure, PKI) 體系,透過數位憑證將「身份(Identity)」與「公鑰(Public Key)」進行具備法律與技術效力的綁定。本章將剖析 數位憑證本質、X.509 憑證結構、CSR 簽發流程、信任鏈(Chain of Trust) 以及 台灣在地化的 PKI 憑證生態系。

一、 觀念剖析:到底什麼是「數位憑證」?

如果把非對稱密碼學比喻為建立安全通道的基石,那麼 數位憑證(Digital Certificate,簡稱憑證) 就是這座建築的「數位身分證」或「網路護照」。

在日常生活裡,如果有人自稱是警察,你不會只憑他身上的對講機(公鑰)就相信他,而是會要求看他的「警察證(憑證)」,並確認上面有「警政署(可信任的發照機構,CA)」的防偽印章與簽名。

數位憑證的本質,就是一個經由可信任第三方機構(Certificate Authority, CA)簽署的電子檔案。它的核心使命只有一個:權威地證明「某把公鑰,確實屬於某個特定的人、公司或網站域名」。

  【實體世界:護照】                       【數位世界:X.509 憑證】
┌──────────────────────────┐             ┌──────────────────────────┐
│ 持有者照片 & 個人資料    │             │ 伺服器域名 (example.com) │
│ 外交部發行與官方防偽鋼印 │   <====>    │ 持有者的公鑰 (Public Key)│
│ 有效期限 (Issue/Expiry)  │             │ CA 的數位簽章 (Signature)│
└──────────────────────────┘             │ 有效期限 (Not Before/After)│
                                         └──────────────────────────┘

數位憑證的三大核心要素

  1. 主體身份資訊(Subject Identity):記載憑證屬於誰(如網站域名 [www.example.com](https://www.example.com)、公司統一編號或個人姓名)。
  2. 持有者的公鑰(Subject Public Key):憑證持有者綁定的公鑰(如 RSA 2048-bit 或 EC secp256r1)。用於 TLS 握手金鑰交換或簽章驗證。
  3. CA 的數位簽章(CA Digital Signature):憑證頒發機構(CA)使用其私鑰對上述「身份資訊 + 公鑰」進行數位簽署。這是確保憑證防篡改、防偽造的防偽印章。

二、 X.509 v3 憑證架構 (RFC 5280)

PKI 體系中最廣泛使用的憑證格式為 X.509 v3 標準。一個標準的 X.509 v3 憑證包含以下核心欄位:

+-----------------------------------------------------------------------+
|                        X.509 v3 Certificate                           |
+-----------------------------------------------------------------------+
| Version                   : v3 (0x02)                                 |
| Serial Number             : 0x4A8F9E... (CA 簽發的唯一隨機序號)       |
| Signature Algorithm       : ecdsa-with-SHA256 / sha256WithRSAEncryption|
| Issuer (簽發者/CA 資訊)   : CN=DigiCert Global Root G2, O=DigiCert Inc|
| Validity (有效期限)       : Not Before: 2026-01-01, Not After: 2027-01-01|
| Subject (持有者/主體資訊) : CN=www.example.com, O=Example Corp        |
| Subject Public Key Info   : Algorithm: EC (secp256r1)                 |
|                             Public Key: 04:A1:8F...                   |
| Extensions (v3 擴充欄位)  :                                           |
|   ├── Basic Constraints   : CA:FALSE (標記此憑證是否能簽發其他憑證)   |
|   ├── Key Usage           : Digital Signature, Key Encipherment       |
|   ├── Extended Key Usage  : TLS Web Server Authentication             |
|   └── Subject Alt Name    : DNS:example.com, DNS:*.example.com (SAN)  |
+-----------------------------------------------------------------------+
| Signature Value (CA 對上述內容的數位簽章)                              |
| e.g. 30:45:02:21:00:8F...                                             |
+-----------------------------------------------------------------------+

關鍵欄位解析

欄位名稱 工程定義與安全性作用
Serial Number CA 分配的唯一序號。必須採用高熵隨機數,防範憑證碰撞攻擊。
Basic Constraints 最重要的安全擴充。CA:TRUE 代表該憑證為 CA,可簽發下級憑證;CA:FALSE 代表終端憑證(Leaf Cert)。若驗證端未校驗此欄位,攻擊者可用一般網站憑證偽造為 CA 並簽發任意域名憑證。
Subject Alternative Name (SAN) 現代瀏覽器與 TLS 用戶端強制校驗的欄位。記載憑證適用的所有域名(如 *.example.com)。傳統的 Common Name (CN) 已被棄用。
Key Usage / EKU 限制金鑰用途(如僅限「Web 伺服器身份驗證」或「電子簽章」),落實權限最小化原則。

三、 CSR (Certificate Signing Request) 簽發流程

申請數位憑證時,金鑰對(Key Pair)的生成與維護遵守最高原則:私鑰永遠在本地生成,且絕對不能傳送給 CA 或離機!

為此,申請者必須生成 CSR(憑證簽署申請書,RFC 2986 / PKCS#10) 並傳送給 CA:

[ 申請者伺服器 (Subscriber) ]                             [ 憑證頒發機構 (CA) ]
       │                                                         │
       ├─ 1. 本地生成金鑰對 (k_priv, Q_pub)                       │
       │    👉 私鑰 k_priv 保留於本地                               │
       ├─ 2. 填寫身份資訊 (Subject DN, SAN)                       │
       ├─ 3. 用自己的 k_priv 對 (資訊+Q_pub) 簽署 ──> 產生 CSR    │
       │                                                         │
       ├───────────────────── 4. 傳送 CSR ──────────────────────>│
       │                                                         │
       │                                                         ├─ 5. 驗證 CSR 內部的 PoP 簽章
       │                                                         ├─ 6. 執行身份審驗 (DV / OV / EV)
       │                                                         └─ 7. 使用 CA 私鑰對 Q_pub 簽署
       │<────────────────── 8. 發還 X.509 憑證 ───────────────────┤

CSR 的三大核心要素

  1. 主體身份資訊(Subject Info):申請的域名(SAN)、公司名稱、國家等。
  2. 申請者公鑰(Public Key):即將被寫入最終憑證的公鑰 $Q_{\text{pub}}$。
  3. 持有證明簽章(Proof of Possession, PoP):申請者使用自己的私鑰 $k_{\text{priv}}$ 對 CSR 內容進行簽章。CA 收到後會驗證該簽章,確保申請者確實持有該公鑰對應的私鑰。

四、 台灣 PKI 憑證生態系與實務應用

在瞭解 PKI 通用理論後,我們聚焦於台灣實務開發(如政府報稅、公文系統、公司登記、證券下單與醫院電子病歷)中最常見的在地化 PKI 體系:

                                [ 台灣 PKI 信任體系 ]
                                          │
       ┌──────────────────────────────────┴──────────────────────────────────┐
       ▼                                                                     ▼
【政府網際網路單一窗口 PKI (GPKI)】                                 【金融與證券 PKI (Financial PKI)】
       │                                                                     │
       ├── 國家級根簽發機構 (GRCA)                                           └── 台灣網路認證 (TWCA)
       │                                                                             │
       ├── 內政部自然人憑證 (MOICA) ───► 個人身份認證、報稅、健保/勞保                    └── 證期憑證 / 金融憑證
       ├── 經濟部工商憑證 (MOEACA) ───► 法人/公司憑證 (載有統編)、電子發票、採購網               └── 股票/期貨高安規下單簽章
       └── 衛福部醫事憑證 (HCA)   ───► 醫師/護理師電子病歷簽章

1. 政府網際網路單一窗口 PKI (GPKI)

台灣政府建立了 GPKI (Government PKI) 體系,頂層為國家級根簽發機構(GRCA, Government Root CA),其 Root 憑證預先內建於常見作業系統與 Java cacerts 中。GPKI 下轄多個專用 CA:

  • 內政部自然人憑證 (MOICA, Ministry of the Interior CA):
  • 發行對象:年滿 18 歲之台灣國民。
  • 載體與安全:預設儲存於 IC 晶片卡(自然人憑證卡)中,私鑰不可導出。
  • 應用場景:個人綜合所得稅申報、健保/勞保個人資料查詢、電子公文與符合《電子簽章法》之不可否認性簽章。
  • 經濟部工商憑證 (MOEACA, Ministry of Economic Affairs CA):
  • 發行對象:在台灣立案登記的公司、商業或法人組織。
  • 特徵:憑證的 Subject 欄位中強制記載公司統一編號(Tax ID)。
  • 應用場景:公司變更登記、政府採購網投標、勞健保投保單位申報、電子發票開立與簽署。
  • 衛福部醫事人員憑證 (HCA, Healthcare CA):
  • 發行對象:醫師、藥師、護理師等醫事從業人員。
  • 應用場景:醫院電子病歷(EMR)簽署,符合醫療法規之法律效力。

2. 金融與證券 PKI (Financial PKI)

  • 台灣網路認證公司 (TWCA, Taiwan Certificate Authority):
  • 台灣金融業與證券期貨業主要的商業憑證機構。
  • 證期憑證 / 金融憑證:
  • 應用場景:投資人進行網路股票下單、期貨交易或高風險銀行轉帳時,券商/銀行系統強制要求使用證期憑證進行下單委託封包簽章。
  • 資安防範:防止下單後因市場波動發生虧損時,客戶誣陷系統「未經同意擅自下單」(防範否認交易)。

五、 信任鏈 (Chain of Trust) 與憑證廢止機制

信任鏈層級架構

作業系統與 JVM(如 cacerts)不可能預先內建全球數百萬個網站的憑證,而是採用分層信任鏈(Chain of Trust):

[ Root CA Certificate (根憑證) ] ────────── 自簽憑證 (Self-Signed)
       │                                  預先內建於 OS / Java TrustStore
       ▼ (使用 Root CA 私鑰簽署)
[ Intermediate CA Certificate (中繼憑證) ] ── 隔離保護 Root CA 私鑰
       │                                  平時在線執行日常簽發業務
       ▼ (使用 Intermediate CA 私鑰簽署)
[ End-Entity / Leaf Certificate (終端憑證) ] ─ 安裝於 Web 伺服器 (例如 google.com)

  1. Root CA(根憑證):自我簽署(Self-Signed),私鑰存放在實體隔離(Air-Gapped)的金鑰安全模組(HSM)中。
  2. Intermediate CA(中繼憑證):由 Root CA 簽發。引入中繼層可避免 Root CA 私鑰高頻使用帶來的洩漏風險。
  3. Leaf Certificate(終端憑證):安裝於伺服器上,Basic Constraints 為 CA:FALSE,不可再簽發下級憑證。

憑證廢止機制:CRL vs. OCSP Stapling

當憑證私鑰洩漏或公司倒閉時,CA 必須廢止該憑證:

  • CRL(憑證廢止清單):CA 定期發布包含所有已廢止憑證序號的黑名單檔案(體積龐大,更新有時間差)。
  • OCSP Stapling(最佳實務):由 Web 伺服器定期向 CA 取得帶有 CA 簽章的 OCSP 狀態回應,並在 TLS 握手時主動「釘選(Staple)」傳給用戶端,兼顧效能、隱私與實時性。

六、 Java JCA 實戰:解析 X.509 憑證與驗證簽章

以下程式碼演示如何使用 Java 原生 JCA (java.security.cert.CertificateFactory) 解析 PEM 格式憑證、提取 SAN 欄位,並校驗憑證效期與數位簽章:

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.security.cert.CertificateFactory;
import java.security.cert.X509Certificate;
import java.util.Collection;
import java.util.List;

public class X509CertificateValidatorExample {

    // 模擬 PEM 格式的範例 X.509 憑證
    private static final String SAMPLE_PEM_CERTIFICATE =
            "-----BEGIN CERTIFICATE-----\n" +
            "MIIA/TCCAWWgAwIBAgIUY3a123456789ABCDEF01MAoGCCqGSM49BAMCMDExCzAJ\n" +
            "BgNVBAYTAlRXIxEzARBgNVBAoMCkRlbW8gQ0EgSW5jMRMwEQYDVQQDDApEZW1v\n" +
            "IFJvb3QgQ0EwHhcNMjYwMTAxMDAwMDAwWhcNMjcwMTAxMDAwMDAwWjA1MQswCQYD\n" +
            "VQQGE3JUVzETMBEGA1UECgwKRGVtbyBPcmcxFTATBgNVBAMMDGV4YW1wbGUuY29t\n" +
            "MFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAE7p9K8L3X9Z8mY... [省略] ...\n" +
            "-----END CERTIFICATE-----";

    public static void main(String[] args) throws Exception {
        System.out.println("=== Day 31 - Java JCA X.509 憑證解析與驗證實戰 ===");

        // 1. 初始化 X.509 憑證工廠
        CertificateFactory certFactory = CertificateFactory.getInstance("X.509");

        // 2. 解析 PEM 憑證為 X509Certificate 物件
        ByteArrayInputStream certInputStream = new ByteArrayInputStream(SAMPLE_PEM_CERTIFICATE.getBytes(StandardCharsets.UTF_8));
        X509Certificate certificate = (X509Certificate) certFactory.generateCertificate(certInputStream);

        // 3. 提取並印出憑證核心欄位
        System.out.println("\n--- [ 憑證基本欄位解析 ] ---");
        System.out.println("憑證版本 (Version)     : v" + certificate.getVersion());
        System.out.println("憑證序號 (Serial Number): " + certificate.getSerialNumber().toString(16));
        System.out.println("簽章演算法 (Sig Algo)  : " + certificate.getSigAlgName());
        System.out.println("主體 DN (Subject DN)   : " + certificate.getSubjectX500Principal().getName());
        System.out.println("簽發者 DN (Issuer DN)  : " + certificate.getIssuerX500Principal().getName());
        System.out.println("生效日期 (Not Before)  : " + certificate.getNotBefore());
        System.out.println("到期日期 (Not After)   : " + certificate.getNotAfter());

        // 4. 解析 SAN (Subject Alternative Name) 擴充欄位
        System.out.println("\n--- [ 擴充欄位: Subject Alternative Names (SAN) ] ---");
        Collection<List<?>> sanEntries = certificate.getSubjectAlternativeNames();
        if (sanEntries != null) {
            for (List<?> entry : sanEntries) {
                Integer type = (Integer) entry.get(0); // 2 代表 DNS Name
                String name = (String) entry.get(1);
                if (type == 2) {
                    System.out.println("DNS 域名: " + name);
                }
            }
        } else {
            System.out.println("此憑證未配置 SAN 欄位。");
        }

        // 5. 校驗憑證時間效期
        try {
            certificate.checkValidity();
            System.out.println("憑證時間效期檢查: 有效 (Within Validity Period)");
        } catch (Exception e) {
            System.err.println("憑證時間效期檢查: 已過期或尚未生效 (" + e.getMessage() + ")");
        }

        // 6. 驗證數位簽章 (驗證憑證是否由 Issuer 的公鑰簽署)
        PublicKey issuerPublicKey = certificate.getPublicKey(); // 若為根憑證,以自身公鑰校驗
        try {
            certificate.verify(issuerPublicKey);
            System.out.println("憑證數位簽章校驗: 成功 (Signature Verified by Issuer Public Key)");
        } catch (Exception e) {
            System.err.println("憑證數位簽章校驗: 失敗 (" + e.getMessage() + ")");
        }
    }
}


上一篇
Day31 - 數位簽章 (Digital Signature):不可否認性與 Ed25519 / RSA-PSS 實戰
下一篇
Day33 - 金鑰與憑證容器標準
系列文
我是Java工程師,關於密碼學我想懂的不多 共 34 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言