Different types of keystore in Java -- PKCS12

  Pi Ke        2015-01-04 21:08:49       80,958        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)时,输入密钥库流和密码传递两个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密钥库中。包含私钥和证书的密钥库可用于Web上的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

When user requirement changes