iT邦幫忙

2026 iThome 鐵人賽

0
Software Development

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

Day36 - 硬體金鑰保護:HSM、智慧卡與 USB Token

  • 分享至 

  • xImage
  •  

前言

在前面章節中,我們介紹了如何將私鑰與憑證儲存於 PKCS#12(.p12 / .pfx)或 PEM 檔案中。然而,軟體檔案儲存存在一個根本上的資安風險:只要攻擊者取得伺服器 ROOT 權限、個人電腦控制權或金鑰庫密碼,私鑰檔案就能被複製並攜走。

在金融系統(如 SWIFT 轉帳、證券下單)、政府電子化服務(如報稅、電子發票、公文簽署)、車聯網(V2X)與 CA 根憑證簽發等高安全場景中,資安規範(如 FIPS 140-2/3 Level 3+、Common Criteria EAL5+)強制要求私鑰必須儲存於硬體安全載體中。

本章將剖析 HSM、智慧卡(IC 卡) 與 USB Token 的安全特性,深入探討台灣在地化的 自然人憑證 (MOICA) 與 工商憑證 (MOEACA) 硬體架構,解析 Java JCA 與硬體介接的標準 Bridge——PKCS#11 (SunPKCS11),並透過 Java 程式碼實作硬體導向的簽章與金鑰管理。

一、 三大硬體安全載體比對與核心優勢

硬體載體的核心防禦哲學是:「金鑰在硬體晶片內部生成,且永遠無法被導出(Non-Exportable)。所有加密與簽章運算皆由硬體晶片執行,軟體端僅能取得運算結果。」

[ 應用程式 (Java JCA) ] ──(傳送 Hash/明文)──> [ 硬體載體 (HSM/IC卡/Token) 專用晶片 ]
                                                       │
                                            (私鑰在晶片內運算,不出卡)
                                                       │
[ 應用程式 (Java JCA) ] <───(傳回 Signature/密文)─────┘

硬體載體規格比對表

維度 硬體安全模組 (HSM) 智慧卡 (Smart Card / IC 卡) USB Token / Hardware Key
典型代表 Thales Luna, Utimaco, AWS CloudHSM 自然人憑證 (MOICA), 工商憑證 (MOEACA), 醫事人員卡 YubiKey 5 Series, SafeNet eToken
實體形態 伺服器機架卡、獨立網路設備 (PCIe / Network) ISO-7816 接觸式/非接觸式晶片卡 (需搭讀卡機) USB 介面隨身硬體裝置
主要場景 銀行核心、CA 根憑證、高併發 TLS 卸載 個人報稅/簽章、企業公文/發票簽署、電子病歷 企業員工作業系統登入、SSH Key、程式碼簽署
併發效能 萬級 TPS(具備專用硬體加速器) 單人次操作(需要人手插卡與輸入 PIN 碼) 單人次/中低併發操作
防篡改防護 最高(感知侵入即自動銷毀硬體金鑰) 高(抗側信道攻擊與物理探針防護) 高(支援 PIN 碼嘗試次數鎖定)

二、 台灣在地化硬體憑證:自然人憑證與工商憑證架構

在台灣的軟體開發實務中,最具代表性的硬體安全載體莫過於內政部的自然人憑證 (MOICA) 與經濟部的工商憑證 (MOEACA)。

       ┌─────────────────────────────────────────────────────────┐
       │     台灣硬體 IC 憑證卡 (ISO-7816 Smart Card)             │
       │                                                         │
       │  ┌───────────────────────────────────────────────────┐  │
       │  │               抗篡改安全晶片 (Secure Element)      │  │
       │  │                                                   │  │
       │  │  1. 私鑰 (PrivateKey) ──► 發卡時內部生成,不可導出  │  │
       │  │  2. X.509 數位憑證    ──► 含身分證字號 / 統一編號  │  │
       │  │  3. PIN 碼校驗與鎖定  ──► 錯誤達 3 次自動鎖卡     │  │
       │  └───────────────────────────────────────────────────┘  │
       └─────────────────────────────────────────────────────────┘
                                   │ (PC/SC 讀卡機介面)
                                   ▼
                   [ 中間件驅動: HiCOS / OpenSC ]
                                   │ (PKCS#11 API)
                                   ▼
                       [ Java SunPKCS11 Provider ]

1. 內政部自然人憑證 (MOICA)

  • 發行機構:內政部憑證管理中心(MOICA),屬 GPKI 體系。
  • 載體特性:符合 ISO-7816 標準之防篡改晶片卡。私鑰於卡片出廠發卡時在晶片內部直接生成(On-Board Key Generation),硬體結構上完全不提供 Read/Export 指令,任何人皆無法將私鑰從卡片中抄出。
  • 資安防護:存取卡片必須通過 PIN 碼驗證(預設為預設預設 PIN,使用者需修改)。PIN 碼連續輸入錯誤 3 次即自動鎖卡,必須使用 PIN 碼解鎖碼 (PUK) 才能復原,有效防範暴力破解。
  • 應用場景:個人綜合所得稅申報、健保/勞保個人資料查詢、政府電子公文簽署、符合《電子簽章法》之不可否認性數位簽章。

2. 經濟部工商憑證 (MOEACA)

  • 發行機構:經濟部工商憑證管理中心(MOEACA),同樣屬 GPKI 體系。
  • 載體特性:專為企業/公司法人設計的硬體 IC 卡,憑證主體(Subject)欄位中強制記載公司統一編號(Tax ID)。
  • 應用場景:公司變更登記、政府採購網投標、電子發票開立與開立簽章、勞健保投保單位網路申報。

四、 設定 PKCS#11 配置文件 (pkcs11.cfg)

要讓 Java 連接硬體,第一步是撰寫 PKCS#11 配置文件,告知 Java 廠商 DLL/.so 的路徑與 Slot 插槽資訊:

1. 連接台灣自然人憑證 / 工商憑證 (HiCOS / OpenSC 中間件)

在 Windows 下通常使用政府 HiCOS 中間件提供的 HiPKCS11.dll,在 Linux/macOS 下可使用 OpenSC 提供的 PKCS#11 庫:

# Taiwan-SmartCard-pkcs11.cfg
name = TaiwanSmartCard
# Windows 環境下 HiCOS 中間件動態庫路徑
library = C:/Windows/System32/HiPKCS11.dll
# Linux/macOS 環境範例 (使用 OpenSC)
# library = /usr/lib/x86_64-linux-gnu/opensc-pkcs11.so
slot = 0

2. 連接企業級 HSM 範例

# hsm-pkcs11.cfg
name = EnterpriseHSM
library = /opt/hsm/lib/libcryptoki.so
slotListIndex = 0
attributes(generate, CKO_PRIVATE_KEY, *) = {
   CKA_PRIVATE = true
   CKA_SENSITIVE = true
   CKA_EXTRACTABLE = false  # 強制私鑰不可導出!
}


五、 Java JCA 生產級實戰:讀取自然人憑證/工商憑證執行數位簽章

一旦配置好 SunPKCS11,在 Java 代碼中操作自然人憑證卡片或 HSM 的體驗,與操作本地 .p12 檔案幾乎完全相同。Java 會將硬體載體抽象為一個名為 PKCS11 的 KeyStore。

以下範例演示如何載入 PKCS#11 Provider、輸入硬體 IC 卡 PIN 碼連線,並呼叫卡片內部的私鑰進行 RSA-PSS / RSA 數位簽章:

import java.io.ByteArrayInputStream;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.PrivateKey;
import java.security.Provider;
import java.security.Security;
import java.security.Signature;
import java.security.cert.X509Certificate;
import java.util.Base64;
import java.util.Enumeration;

public class TaiwanSmartCardPkcs11Example {

    public static void main(String[] args) throws Exception {
        System.out.println("=== Day 36 - 台灣自然人憑證 / 工商憑證 PKCS#11 Java 實戰 ===");

        // Step 1: 動態構建 PKCS#11 配置內容
        // 指向 HiCOS 中間件動態庫 (請依實際 OS 環境調整路徑)
        String pkcs11Config = """
            name = TaiwanSmartCard
            library = C:/Windows/System32/HiPKCS11.dll
            slot = 0
            """;

        // Step 2: 動態註冊 SunPKCS11 Provider
        Provider pkcs11Provider = Security.getProvider("SunPKCS11");
        pkcs11Provider = pkcs11Provider.configure(new ByteArrayInputStream(pkcs11Config.getBytes(StandardCharsets.UTF_8)));
        Security.addProvider(pkcs11Provider);

        System.out.println("PKCS#11 Provider 載入成功: " + pkcs11Provider.getName());

        // Step 3: 使用 PKCS11 類型初始化 KeyStore,並輸入卡片 PIN 碼
        // 注意:自然人憑證預設 PIN 碼為預設預設 PIN,連續輸入錯誤 3 次會鎖卡!
        char[] cardPin = "666666".toCharArray(); 
        KeyStore hardwareKeyStore = KeyStore.getInstance("PKCS11", pkcs11Provider);
      
        try {
            hardwareKeyStore.load(null, cardPin);
            System.out.println("自然人憑證 / 工商憑證 IC 卡 PIN 碼驗證成功!");
        } finally {
            // 安全守則:PIN 碼驗證完畢立刻從記憶體中清除
            java.util.Arrays.fill(cardPin, '\0');
        }

        // Step 4: 遍歷並取得卡片內存放的金鑰別名 (Key Alias)
        Enumeration<String> aliases = hardwareKeyStore.aliases();
        if (!aliases.hasMoreElements()) {
            System.err.println("未在卡片中偵測到有效的金鑰或憑證條目!");
            return;
        }

        String keyAlias = aliases.nextElement();
        System.out.println("硬體卡片內部金鑰別名 (Alias): " + keyAlias);

        // Step 5: 從卡片中取得 PrivateKey 參照物件
        // 關鍵觀念:此時取得的 PrivateKey 物件僅為控制控制代碼 (Handle),真正的私鑰數值依然封鎖於 IC 晶片內部!
        PrivateKey hardwarePrivateKey = (PrivateKey) hardwareKeyStore.getKey(keyAlias, null);
        X509Certificate hardwareCert = (X509Certificate) hardwareKeyStore.getCertificate(keyAlias);

        System.out.println("憑證 Subject: " + hardwareCert.getSubjectX500Principal().getName());
        System.out.println("憑證 Issuer : " + hardwareCert.getIssuerX500Principal().getName());

        // Step 6: 呼叫卡片晶片執行數位簽章 (On-Card Signing)
        String documentToSign = "Tax-Declaration-2026; Taxpayer-ID: A123456789; Amount: $120,000";
        byte[] documentBytes = documentToSign.getBytes(StandardCharsets.UTF_8);

        // 指定由 pkcs11Provider 執行簽章,運算指令會透過 PKCS#11 拋給 IC 卡晶片
        Signature signatureEngine = Signature.getInstance(hardwareCert.getSigAlgName(), pkcs11Provider);
        signatureEngine.initSign(hardwarePrivateKey);
        signatureEngine.update(documentBytes);
        byte[] signatureValue = signatureEngine.sign();

        System.out.println("硬體 IC 卡簽章完成!簽章位元組長度: " + signatureValue.length + " bytes");
        System.out.println("硬體簽章結果 (Base64): " + Base64.getEncoder().encodeToString(signatureValue));

        // Step 7: 使用公鑰驗證簽章 (驗證可在軟體端執行)
        Signature verifier = Signature.getInstance(hardwareCert.getSigAlgName());
        verifier.initVerify(hardwareCert.getPublicKey());
        verifier.update(documentBytes);
        boolean isValid = verifier.verify(signatureValue);

        System.out.println("簽章校驗結果: " + isValid);
    }
}


上一篇
Day35 - 現代網路安全基石:TLS 1.3 協定解析
系列文
我是Java工程師,關於密碼學我想懂的不多 共 37 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言