Different types of keystore in Java -- PKCS12

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

PKCS12 คือ รูปแบบไฟล์ที่ใช้งานอยู่สำหรับการจัดเก็บวัตถุการเข้ารหัสลับเป็นไฟล์เดียว สามารถใช้เพื่อจัดเก็บคีย์ลับ คีย์ส่วนตัว และใบรับรอง เป็นรูปแบบมาตรฐานที่เผยแพร่โดย RSA Laboratories ซึ่งหมายความว่าสามารถใช้ได้ไม่เพียงแต่ใน Java เท่านั้น แต่ยังอยู่ในไลบรารี่อื่นๆ ใน C, C++ หรือ C# เป็นต้น รูปแบบไฟล์นี้มักใช้ในการนำเข้าและส่งออกรายการจากหรือไปยังประเภท keystore อื่นๆ

ต่อไปเราจะอธิบายการดำเนินการที่สามารถทำได้บน PKCS12 keystore

สร้าง PKCS12 keystore

ก่อนที่จะจัดเก็บรายการลงใน PKCS12 keystore จะต้องโหลด keystore ก่อน หมายความว่าเราต้องสร้าง keystore ขึ้นมาก่อน วิธีที่ง่ายที่สุดในการสร้าง PKCS12 keystore คือ:

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 สองค่าถูกส่งผ่านเป็นสตรีม keystore และรหัสผ่านขาเข้า เป็นเพราะเรายังไม่มี keystore หลังจากเรียกใช้โปรแกรมนี้แล้ว ควรมีไฟล์ keystore ชื่อ output.p12 ในไดเร็กทอรีการทำงานปัจจุบัน

จัดเก็บคีย์ลับ

PKCS12 อนุญาตให้จัดเก็บคีย์ลับบนฐานที่จำกัด คีย์ลับมักใช้ในการเข้ารหัส/ถอดรหัสข้อมูล เพื่อถ่ายโอนคีย์ได้อย่างสะดวก สามารถจัดเก็บไว้ใน keystore เช่น 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 keystore ไม่สามารถดึงออกมาใน Java ได้ เนื่องจาก PKCS12 เป็นมาตรฐานที่สามารถเคลื่อนย้ายได้ ไลบรารี่อื่นๆ อาจรองรับการดึงคีย์ลับ

จัดเก็บคีย์ส่วนตัว

คีย์ส่วนตัวและห่วงโซ่ใบรับรองที่เกี่ยวข้องสามารถจัดเก็บไว้ใน PKCS12 keystore ได้ keystore ที่มีคีย์ส่วนตัวและใบรับรองสามารถใช้ในการสื่อสาร 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();
}

คีย์ส่วนตัว RSA ถูกสร้างขึ้นด้วย CertAndKeyGen และใบรับรองที่เกี่ยวข้องก็ถูกสร้างขึ้นเช่นกัน จากนั้นรายการคีย์จะถูกจัดเก็บไว้ใน keyStore โดยการเรียก keyStore.setEntry() อย่าลืมบันทึก keyStore โดยการเรียก keyStore.store() มิฉะนั้นรายการจะหายไปเมื่อโปรแกรมสิ้นสุดการทำงาน

จัดเก็บใบรับรอง

PKCS12 keystore ยังอนุญาตให้จัดเก็บใบรับรองด้วยตัวเองโดยไม่ต้องจัดเก็บคีย์ส่วนตัวที่เกี่ยวข้อง ในการจัดเก็บใบรับรอง สามารถเรียกใช้ 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() พร้อมกับ alias ที่ให้มา ตัวอย่างเช่น:

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

โหลดคีย์ส่วนตัว

ความแตกต่างอย่างหนึ่งระหว่าง PKCS12 keystore และ keystore อื่นๆ เช่น 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 สามารถโหลดได้โดยการเรียก 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() ถ้า alias ที่ให้มานั้นแมปกับห่วงโซ่ใบรับรอง จะมีเพียงใบรับรองใบสุดท้ายเท่านั้นที่จะถูกส่งคืน

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 keystore สามารถใช้ในการนำเข้าและส่งออกคีย์และใบรับรองได้ เนื่องจากคีย์ส่วนตัวสามารถดึงออกมาจาก PKCS12 keystores ได้ ดังนั้นรายการจึงสามารถส่งออกจาก PKCS12 keystore และนำเข้าไปยังประเภท keystore อื่นๆ เช่น JKS ได้

โค้ดสแนปช็อตด้านล่างแสดงการส่งออกรายการคีย์ส่วนตัวจาก PKCS12 และนำเข้าไปยัง JSK keystore:

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

PKCS12 keystore เป็นประเภท keystore ที่แนะนำหากคุณต้องการประเภท keystore ที่สามารถเคลื่อนย้ายได้และคุณอาจต้องการใช้กับไลบรารี่ที่ไม่ใช่ Java ในอนาคต

สำหรับข้อมูลเพิ่มเติมเกี่ยวกับ keystore อื่นๆ โปรดดูที่ ประเภท keystore ต่างๆ ใน 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

Human's victories