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?