在前面章節中,我們介紹了如何將私鑰與憑證儲存於 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 ]
pkcs11.cfg)要讓 Java 連接硬體,第一步是撰寫 PKCS#11 配置文件,告知 Java 廠商 DLL/.so 的路徑與 Slot 插槽資訊:
在 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
# 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 # 強制私鑰不可導出!
}
一旦配置好 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);
}
}