Different types of keystore in Java -- PKCS12

  Pi Ke        2015-01-04 21:08:49       80,956        10          English  简体中文  繁体中文  ภาษาไทย  Tiếng Việt 

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 中不同類型的金鑰儲存庫 -- 概述

JAVA  PKCS12  KEYSTORE  TUTORIAL 

       

  RELATED


  10 COMMENTS


lintao [Reply]@ 2015-01-15 01:42:54

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?

Pi Ke [Reply]@ 2015-01-15 06:14:30

There is no difference for choosing p12 or pfx as the file extension. Actually, you can choose specify other extension name as well. The PKCS12 keystore will parse the actual contents not based on the file extension. But p12 is more preferable as it is the standard name for PKCS12. pfx was created by Microsoft which means Personal Information Exchange. It is the predecessor of p12.

The reason why using setXXX() instead of addXXX() is because setXXX() can mean both addXXX() and updateXXX(). So it's a combination of both add and update. This saves extra method creation.

Hope it helps.

lintao [Reply]@ 2015-01-15 20:29:29

Thanks! For the setXXX() method name question, I acctually want to know if there is any story why it's chosen to combine the 2 functions( add and update) in one method. In my understanding, using separated methods make the API more clear for others to use. Just curious about if there is any restriction or special consideration. :)

Pi Ke [Reply]@ 2015-01-16 04:46:15

Hmm, actually I think the reason is that since there is a getXXX(0 method, as a user, s/he would naturally expects there should be a setXXX() method. So a setXXX() is created. And later, the setXXX() can achieve both the update and add capability. So no updateXXX() and addXXX() are needed anymore. Want to keep the API concise.

mohamed [Reply]@ 2015-06-07 06:36:07

How i can store an existing certificate .p12 (located in the root )in a keystore which i can use lately to sign a pdf document !!!

Pi Ke [Reply]@ 2015-06-07 07:07:47

In this case, I think your best friend is the keytool, it can be used to import an existing certificate into a keystore. See docs here : docs.oracle.com/javase/8/docs/technotes/tools/unix/keytool.html

You can also import it progrmammtically by reading the certificate data and generate the certificate object with the proper methods provided by Java API. I amy write a post to demonstrate it later.

mohamed [Reply]@ 2015-06-07 08:05:46

thank u , i need this very much because i m trying from long time to read a certificate programatically and use it in signing a pdf document so please help me it's urgent .

Pi Ke [Reply]@ 2015-06-08 07:06:43

I have written a post which demonstrates how to read a certificate file programmatically and then store it in a p12 file. www.pixelstech.net/article/1433764001-Generate-certificate-from-cert-file-in-Java

BTW : What do you mean by "an existing certificate .p12" in your previous comment? As far as I know p12 is an extension of PKCS12 keystore but not a certificate extension.

Jon [Reply]@ 2017-03-30 08:36:43

Great documentation.  I've got a use case where I'd like to programmatically convert a a PKCS12 to a JKS.  Any thoughts?

Ke Pi [Reply]@ 2017-03-31 19:12:31

Technically this is doable but comes with some limitation. Since JKS cannot have secret key stored while PKCS12 can have it. Hence when converting the PKCS12 to JKS, only private keys and certificates can be transferred. 

The steps would be first load the PKCS12 keystore as mentioned in this post and read all key entries and certificate chains out, then create a JKS keystore/load existing JKS keystore as described in Different types of keystore in Java -- JKS. Then put those entries into the created/loaded JKS keystore.



  RANDOM FUN

The truth about software development