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 -- ภาพรวม
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?