PKCS12 是一種用於將密碼編譯物件儲存為單一檔案的活動檔案格式。它可用於儲存秘密金鑰、私密金鑰和憑證。它是由 RSA 實驗室發佈的標準化格式,這意味著它不僅可以在 Java 中使用,還可以在 C、C++ 或 C# 等其他程式庫中使用。此檔案格式常用於從其他金鑰儲存庫類型匯入和匯出項目。
接下來,我們將說明可以在 PKCS12 金鑰儲存庫上執行的操作。
建立 PKCS12 金鑰儲存庫
在將項目儲存到 PKCS12 金鑰儲存庫之前,必須先載入金鑰儲存庫。這意味著我們必須先建立一個金鑰儲存庫。建立 PKCS12 金鑰儲存庫最簡單的方法是:
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
keyStore.store(new FileOutputStream("output.p12"), "password".toCharArray());
} catch (Exception ex){
ex.printStackTrace();
}
請注意,當呼叫 keyStore.load(null, null) 時,輸入金鑰儲存庫串流和密碼會傳遞兩個空值。這是因為我們還沒有可用的金鑰儲存庫。執行此程式後,在目前的作業目錄中應該會有一個名為 output.p12 的金鑰儲存庫檔案。
儲存秘密金鑰
PKCS12 允許在有限的基礎上儲存秘密金鑰。秘密金鑰常用於加密/解密資料。為了方便傳輸金鑰,可以將它們儲存在像 PKCS12 這樣的金鑰儲存庫中並進行傳輸。
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(null, null);
KeyGenerator keyGen = KeyGenerator.getInstance("AES");
keyGen.init(128);
Key key = keyGen.generateKey();
keyStore.setKeyEntry("secret", key, "password".toCharArray(), null);
keyStore.store(new FileOutputStream("output.p12"), "password".toCharArray());
} catch (Exception ex){
ex.printStackTrace();
}
某些使用演算法 AES 儲存在 PKCS12 金鑰儲存庫中的秘密金鑰無法在 Java 中提取。由於 PKCS12 是一個可移植的標準,因此其他程式庫可能支援提取秘密金鑰。
儲存私密金鑰
私密金鑰及其關聯的憑證鏈可以儲存在 PKCS12 金鑰儲存庫中。包含私密金鑰和憑證的金鑰儲存庫可用於網路上的 SSL 通訊。
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// keyStore.load(new FileInputStream("output.p12"),"password".toCharArray());
keyStore.load(null, null);;
CertAndKeyGen gen = new CertAndKeyGen("RSA","SHA1WithRSA");
gen.generate(1024);
Key key=gen.getPrivateKey();
X509Certificate cert=gen.getSelfCertificate(new X500Name("CN=ROOT"), (long)365*24*3600);
X509Certificate[] chain = new X509Certificate[1];
chain[0]=cert;
keyStore.setKeyEntry("private", key, "password".toCharArray(), chain);
keyStore.store(new FileOutputStream("output.p12"), "password".toCharArray());
}catch(Exception ex){
ex.printStackTrace();
}
使用 CertAndKeyGen 生成 RSA 私密金鑰,並生成關聯的憑證。然後通過呼叫 keyStore.setEntry() 將金鑰項目儲存到 keyStore 中。不要忘記通過呼叫 keyStore.store() 儲存 keyStore,否則程式結束時項目將會丟失。
儲存憑證
PKCS12 金鑰儲存庫還允許單獨儲存憑證,而無需儲存相應的私密金鑰。若要儲存憑證,可以呼叫 KeyStore.setCertificateEntry()。
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
// keyStore.load(new FileInputStream("output.p12"),"password".toCharArray());
keyStore.load(null, null);;
CertAndKeyGen gen = new CertAndKeyGen("RSA","SHA1WithRSA");
gen.generate(1024);
X509Certificate cert=gen.getSelfCertificate(new X500Name("CN=ROOT"), (long)365*24*3600);
keyStore.setCertificateEntry("cert", cert);
keyStore.store(new FileOutputStream("output.p12"), "password".toCharArray());
}catch(Exception ex){
ex.printStackTrace();
}
可以使用提供的別名呼叫 KeyStore.getCertificate() 來提取儲存的憑證。例如:
Certificate cert = keyStore.getCertificate("cert");
載入私密金鑰
PKCS12 金鑰儲存庫與其他金鑰儲存庫(例如 JKS)之間的一個區別是,PKCS12 的私密金鑰可以在沒有 NullPointerException 的情況下提取。可以使用提供的正確密碼正確提取私密金鑰。
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("output.p12"), "password".toCharArray());
Key pvtKey = keyStore.getKey("private", "password".toCharArray());
System.out.println(pvtKey.toString());
} catch (Exception ex){
ex.printStackTrace();
}
以上程式碼的輸出為:
sun.security.rsa.RSAPrivateCrtKeyImpl@ffff2466
載入憑證鏈
如果憑證鏈儲存在金鑰儲存庫中,則可以通過呼叫 KeyStore.getCertificateChain() 來載入它。以下程式碼用於提取與關聯的私密金鑰關聯的憑證鏈。
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("output.p12"), "password".toCharArray());
Key pvtKey = keyStore.getKey("private", "password".toCharArray());
System.out.println(pvtKey.toString());
java.security.cert.Certificate[] chain = keyStore.getCertificateChain("private");
for(java.security.cert.Certificate cert:chain){
System.out.println(cert.toString());
}
} catch (Exception ex){
ex.printStackTrace();
}
輸出為:
[
[
Version: V3
Subject: CN=ROOT
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 107262652552256813768678166856978781385254195794582600239703451044252881438814396239031781495369251659734172714120481593881055888193254336293673302267462500060447786562885955334870856482264000504019061160524587434562257067298291769329550807938162702640388267016365640782567817416484577163775446236245223552189
public exponent: 65537
Validity: [From: Mon Jan 05 13:03:29 SGT 2015,
To: Tue Jan 05 13:03:29 SGT 2016]
Issuer: CN=ROOT
SerialNumber: [ 5e5ca8a4]
]
Algorithm: [SHA1withRSA]
Signature:
0000: 22 21 BF 73 A6 6D 12 9B F7 49 6C 0E B3 50 6A 9D "!.s.m...Il..Pj.
0010: FA 30 43 22 32 FF 54 95 80 2E B3 8B 6F 59 D4 B5 .0C"2.T.....oY..
0020: 6C A6 AE 89 B7 18 9A A8 35 7D 65 37 BF ED A3 F4 l.......5.e7....
0030: E7 DB 5D 5F 9B DA 4B FA 39 04 9B 4D DB C2 3E FA ..]_..K.9..M..>.
0040: 3B C2 63 F8 1E BE 03 F3 BD 1C D4 8A 8E 3C 51 68 ;.c..........
若要了解如何在 Java 中建立憑證鏈,請參考 在 Java 中生成憑證 -- 憑證鏈。
載入憑證
如果提供的別名對應於憑證鏈,則通過呼叫 KeyStore.getCertificate() 載入憑證也很簡單,只會返回葉憑證。
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("output.p12"), "password".toCharArray());
java.security.cert.Certificate cert = keyStore.getCertificate("private");
System.out.println(cert);
} catch (Exception ex){
ex.printStackTrace();
}
輸出如下所示:
[
[
Version: V3
Subject: CN=ROOT
Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5
Key: Sun RSA public key, 1024 bits
modulus: 107262652552256813768678166856978781385254195794582600239703451044252881438814396239031781495369251659734172714120481593881055888193254336293673302267462500060447786562885955334870856482264000504019061160524587434562257067298291769329550807938162702640388267016365640782567817416484577163775446236245223552189
public exponent: 65537
Validity: [From: Mon Jan 05 13:03:29 SGT 2015,
To: Tue Jan 05 13:03:29 SGT 2016]
Issuer: CN=ROOT
SerialNumber: [ 5e5ca8a4]
]
Algorithm: [SHA1withRSA]
Signature:
0000: 22 21 BF 73 A6 6D 12 9B F7 49 6C 0E B3 50 6A 9D "!.s.m...Il..Pj.
0010: FA 30 43 22 32 FF 54 95 80 2E B3 8B 6F 59 D4 B5 .0C"2.T.....oY..
0020: 6C A6 AE 89 B7 18 9A A8 35 7D 65 37 BF ED A3 F4 l.......5.e7....
0030: E7 DB 5D 5F 9B DA 4B FA 39 04 9B 4D DB C2 3E FA ..]_..K.9..M..>.
0040: 3B C2 63 F8 1E BE 03 F3 BD 1C D4 8A 8E 3C 51 68 ;.c..........
匯入和匯出金鑰和憑證
PKCS12 金鑰儲存庫可用於匯入和匯出金鑰和憑證。由於可以從 PKCS12 金鑰儲存庫中提取私密金鑰,因此可以從 PKCS12 金鑰儲存庫中匯出項目,然後匯入到其他金鑰儲存庫類型(例如 JKS)。
以下程式碼片段演示了如何從 PKCS12 匯出私密金鑰項目並將其匯入 JSK 金鑰儲存庫:
try{
KeyStore keyStore = KeyStore.getInstance("PKCS12");
keyStore.load(new FileInputStream("output.p12"), "password".toCharArray());
Key pvtKey = keyStore.getKey("private", "password".toCharArray());
java.security.cert.Certificate[] chain = keyStore.getCertificateChain("private");
KeyStore jksStore = KeyStore.getInstance("JKS");
jksStore.load(null, null);;
jksStore.setKeyEntry("jksPrivate", pvtKey, "newpassword".toCharArray(), chain);
jksStore.store(new FileOutputStream("output.jks"), "password".toCharArray());
} catch (Exception ex){
ex.printStackTrace();
}
如果您想要一個可移植的金鑰儲存庫類型,並且您可能希望將其與其他非 Java 程式庫一起使用,則建議使用 PKCS12 金鑰儲存庫類型。
有關其他金鑰儲存庫的更多資訊,請參考 Java 中不同類型的金鑰儲存庫 -- 概述
How can I choose the extension, p12 or pfx, for a PKCS12 keystore? Is there a mandatory rule?
Another question about the KeyStore.setXXX() method. Why does it use setXXX() method name instead of addXXX() to add new entry? Any special thought?