異構系統 (Java 服務端 ↔ C++ 客戶端) 建立 TLS 雙向認證,核心難點在於 **證書格式互轉 (JKS ↔ PEM/DER/PKCS12)** 與 **信任鏈一致性**。本文提供基於 OpenSSL CLI 與 Java Keytool 的標準化操作手冊,涵蓋 CA 簽發、雙向導入、格式轉換、驗證排錯全鏈路。
| 工具 | 原生格式 | 支援轉出 | 典型場景 |
|---|---|---|---|
| OpenSSL | PEM (Base64 + Header) | DER, PKCS12 (.p12/.pfx), PKCS8 | C/C++、Nginx、Go、Python |
| Keytool | JKS / PKCS12 (Java 9+ 默認) | DER (.cer/.crt), PEM (需額外轉換) | Java、Tomcat、Jetty、Kafka |
# 1. 生成 CA 私鑰 (4096-bit 建議)
openssl genrsa -out ca.key 4096
# 2. 自簽 CA 根證書 (有效期 10 年)
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=CA/CN=MyRootCA"
# 3. 初始化 CA 資料庫
mkdir -p demoCA/{private,newcerts}
touch demoCA/index.txt
echo 01 > demoCA/serial
cp ca.key demoCA/private/cakey.pem
cp ca.crt demoCA/cacert.pem
# 1. 生成服務端私鑰 (無密碼,便於服務啟動)
openssl genrsa -out server.key 2048
# 2. 生成 CSR
openssl req -new -key server.key -out server.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=Server/CN=server.example.com"
# 3. CA 簽發服務端證書
openssl ca -days 3650 -in server.csr -out server.crt \
-cert demoCA/cacert.pem -keyfile demoCA/private/cakey.pem \
-config openssl.cnf -extensions v3_req
# 4. 轉 PKCS12 (供 Keytool 導入)
openssl pkcs12 -export -in server.crt -inkey server.key \
-out server.p12 -name "server" -passout pass:changeit
# 5. Keytool 導入 JKS (Java 8) 或直接用 PKCS12 (Java 9+)
keytool -importkeystore -srckeystore server.p12 -srcstoretype PKCS12 \
-destkeystore server.jks -deststoretype JKS \
-srcstorepass changeit -deststorepass changeit \
-alias server -srckeypass changeit -destkeypass changeit
# 6. 導入 CA 根到信任庫
keytool -import -trustcacerts -alias ca_root -file ca.crt \
-keystore server.jks -storepass changeit -noprompt
# 1. 生成客戶端私鑰
openssl genrsa -out client.key 2048
# 2. 生成 CSR
openssl req -new -key client.key -out client.csr \
-subj "/C=CN/ST=Beijing/L=Beijing/O=MyOrg/OU=Client/CN=client.example.com"
# 3. CA 簽發
openssl ca -days 3650 -in client.csr -out client.crt \
-cert demoCA/cacert.pem -keyfile demoCA/private/cakey.pem \
-config openssl.cnf -extensions v3_req
# 4. C++ 直接使用 PEM 格式 (無需轉換)
# server: ca.crt + server.crt + server.key
# client: ca.crt + client.crt + client.key
# 1. 生成 Java 客戶端金鑰對
keytool -genkeypair -alias client -keyalg RSA -keysize 2048 \
-keystore client.jks -storepass changeit -keypass changeit \
-dname "CN=client.example.com, OU=Client, O=MyOrg, L=Beijing, ST=Beijing, C=CN"
# 2. 導出 CSR
keytool -certreq -alias client -keyalg RSA -file client.csr \
-keystore client.jks -storepass changeit
# 3. OpenSSL CA 簽發
openssl ca -days 3650 -in client.csr -out client.crt \
-cert demoCA/cacert.pem -keyfile demoCA/private/cakey.pem
# 4. 導入簽發證書回 JKS (回覆導入)
keytool -import -alias client -file client.crt \
-keystore client.jks -storepass changeit -noprompt
# 5. 導入 CA 根到信任庫
keytool -import -trustcacerts -alias ca_root -file ca.crt \
-keystore client.jks -storepass changeit -noprompt
# 6. (可選) 導出信任庫供 SSLContext 使用
keytool -import -trustcacerts -alias ca_root -file ca.crt \
-keystore truststore.jks -storepass changeit -noprompt
| 來源 → 目標 | 命令 |
|---|---|
| PEM (.crt/.key) → DER (.cer/.der) | openssl x509 -in cert.pem -outform DER -out cert.der |
| DER → PEM | openssl x509 -inform DER -in cert.der -out cert.pem |
| PEM + Key → PKCS12 (.p12) | openssl pkcs12 -export -in cert.pem -inkey key.pem -out cert.p12 -name "alias" |
| PKCS12 → JKS | keytool -importkeystore -srckeystore cert.p12 -srcstoretype PKCS12 -destkeystore keystore.jks |
| JKS → PKCS12 (Java 9+) | keytool -importkeystore -srckeystore keystore.jks -destkeystore keystore.p12 -deststoretype PKCS12 |
| PKCS8 加密私鑰 → 無密碼 PEM | openssl pkcs8 -inform PEM -in encrypted.key -out plain.key -nocrypt |
SSL_CTX *ctx = SSL_CTX_new(TLS_client_method());
SSL_CTX_load_verify_locations(ctx, "ca.crt", NULL); // 信任 CA
SSL_CTX_use_certificate_file(ctx, "client.crt", SSL_FILETYPE_PEM);
SSL_CTX_use_PrivateKey_file(ctx, "client.key", SSL_FILETYPE_PEM);
SSL_CTX_check_private_key(ctx); // 必須檢查
SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER | SSL_VERIFY_FAIL_IF_NO_PEER_CERT, NULL);
// SSL_connect(ssl) 後可用 SSL_get_verify_result(ssl) 檢驗 X509_V_OK
// 服務端
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream("server.jks"), "changeit".toCharArray());
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
kmf.init(ks, "changeit".toCharArray());
KeyStore ts = KeyStore.getInstance("JKS");
ts.load(new FileInputStream("truststore.jks"), "changeit".toCharArray());
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
tmf.init(ts);
SSLContext sslContext = SSLContext.getInstance("TLS");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);
SSLServerSocketFactory ssf = sslContext.getServerSocketFactory();
SSLServerSocket serverSocket = (SSLServerSocket) ssf.createServerSocket(8443);
serverSocket.setNeedClientAuth(true); // 強制雙向認證
PKIX path building failed:信任庫缺少 CA 根或中間 CACertificate does not match hostname:CN/SAN 不匹配實際連線主機名 → 簽發時加 subjectAltName=DNS:host.example.comPrivate key does not match certificate:Key/Cert 配對錯誤 → openssl x509 -noout -modulus -in cert.crt | openssl md5 對比No cipher suites in common:雙端 TLS 版本/加密套件不兼容 → 統一啟用 TLS 1.2+、ECDHE_RSA_AES256_GCM_SHA384Handshake timeout:防火牆攔截、MTU 導致分片丟失 → 抓包分析 TCP 握手生產環境請引入 **cert-manager (K8s)**、**HashiCorp Vault PKI** 或 **Smallstep** 自動化證書全生命週期 (簽發、續期、撤銷、輪換),消除人工操作風險。
標準化流程 + 自動化工具 = 零故障的跨語言 mTLS 落地。