Different types of keystore in Java -- PKCS12

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

PKCS12 là một định dạng tập tin hoạt động để lưu trữ các đối tượng mật mã học dưới dạng một tệp duy nhất. Nó có thể được sử dụng để lưu trữ khóa bí mật, khóa riêng và chứng chỉ. Đây là một định dạng chuẩn được công bố bởi RSA Laboratories, có nghĩa là nó có thể được sử dụng không chỉ trong Java mà còn trong các thư viện khác ở C, C++ hoặc C# v.v. Định dạng tệp này thường được sử dụng để nhập và xuất các mục từ hoặc đến các loại kho khóa khác.

Tiếp theo, chúng ta sẽ giải thích các thao tác có thể được thực hiện trên kho khóa PKCS12.

Tạo kho khóa PKCS12

Trước khi lưu một mục vào kho khóa PKCS12, kho khóa phải được tải trước. Điều này có nghĩa là chúng ta phải có một kho khóa được tạo trước. Cách đơn giản nhất để tạo kho khóa PKCS12 là:

try{
	KeyStore keyStore = KeyStore.getInstance("PKCS12");
	keyStore.load(null, null);
	
	keyStore.store(new FileOutputStream("output.p12"), "password".toCharArray());
} catch (Exception ex){
	ex.printStackTrace();
}

Lưu ý, khi gọi keyStore.load(null, null), hai giá trị null được truyền làm luồng kho khóa và mật khẩu đầu vào. Điều này là bởi vì chúng ta chưa có kho khóa sẵn sàng. Sau khi chạy chương trình này, sẽ có một tệp kho khóa có tên output.p12 trong thư mục làm việc hiện tại.

Lưu khóa bí mật

PKCS12 cho phép lưu trữ các khóa bí mật trên cơ sở hạn chế. Khóa bí mật thường được sử dụng để mã hóa/giải mã dữ liệu. Để chuyển các khóa một cách thuận tiện, chúng có thể được lưu trữ trong một kho khóa như PKCS12 và được chuyển.

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();
}

Một số khóa bí mật với thuật toán AES được lưu trữ trên kho khóa PKCS12 không thể được trích xuất trong Java. Vì PKCS12 là một tiêu chuẩn di động, các thư viện khác có thể hỗ trợ trích xuất khóa bí mật.

Lưu khóa riêng

Khóa riêng và chuỗi chứng chỉ liên quan của nó có thể được lưu trữ trong kho khóa PKCS12. Kho khóa chứa khóa riêng và chứng chỉ có thể được sử dụng trong giao tiếp SSL trên web.

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();
}

Một khóa riêng RSA được tạo bằng CertAndKeyGen và chứng chỉ liên quan cũng được tạo. Sau đó, mục khóa được lưu trữ trong keyStore bằng cách gọi keyStore.setEntry(). Đừng quên lưu keyStore bằng cách gọi keyStore.store(), nếu không mục sẽ bị mất khi chương trình kết thúc.

Lưu chứng chỉ

Kho khóa PKCS12 cũng cho phép lưu trữ chứng chỉ một mình mà không cần lưu trữ khóa riêng tương ứng. Để lưu trữ chứng chỉ, có thể gọi 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();
}

Chứng chỉ được lưu trữ có thể được trích xuất bằng cách gọi KeyStore.getCertificate() với bí danh được cung cấp. Ví dụ:

Certificate cert = keyStore.getCertificate("cert");

Tải khóa riêng

Một điểm khác biệt giữa kho khóa PKCS12 và các kho khóa khác như JKS là khóa riêng của PKCS12 có thể được trích xuất mà không có NullPointerException. Khóa riêng có thể được trích xuất chính xác với mật khẩu chính xác được cung cấp.

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();
}

Kết quả đầu ra của mã trên là:

sun.security.rsa.RSAPrivateCrtKeyImpl@ffff2466

Tải chuỗi chứng chỉ

Nếu một chuỗi chứng chỉ được lưu trữ trong một kho khóa, nó có thể được tải bằng cách gọi KeyStore.getCertificateChain(). Mã bên dưới được sử dụng để trích xuất chuỗi chứng chỉ liên quan cho khóa riêng liên quan.

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();
}

Kết quả đầu ra là:

[
[
  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..........

Để hiểu cách tạo chuỗi chứng chỉ trong Java, vui lòng tham khảo Tạo chứng chỉ trong Java -- Chuỗi chứng chỉ.

Tải chứng chỉ

Việc tải chứng chỉ cũng đơn giản bằng cách gọi KeyStore.getCertificate(), nếu bí danh được cung cấp đang ánh xạ đến một chuỗi chứng chỉ, chỉ chứng chỉ lá sẽ được trả về.

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();
}

Kết quả đầu ra trông như thế này:

[
[
  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..........

Nhập và xuất khóa và chứng chỉ

Kho khóa PKCS12 có thể được sử dụng để nhập và xuất khóa và chứng chỉ. Vì khóa riêng có thể được trích xuất từ kho khóa PKCS12, nên các mục có thể được xuất từ kho khóa PKCS12 và sau đó được nhập vào các loại kho khóa khác như JKS.

Đoạn mã bên dưới minh họa việc xuất một mục khóa riêng từ PKCS12 và nhập nó vào kho khóa 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();
}

Kho khóa PKCS12 là loại kho khóa được khuyến nghị nếu bạn muốn có một loại kho khóa di động và bạn có thể muốn sử dụng nó với các thư viện không phải Java khác trong tương lai.

Để biết thêm thông tin về các kho khóa khác, vui lòng tham khảo Các loại kho khóa khác nhau trong Java -- Tổng quan

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

Coding interviews vs The actual work


  SUPPORT US