JKS คือ Java Keystore ซึ่งเป็นประเภท keystore ที่เป็นกรรมสิทธิ์ ออกแบบมาสำหรับ Java สามารถใช้เพื่อเก็บ private key และใบรับรองที่ใช้สำหรับการสื่อสาร SSL แต่ไม่สามารถเก็บ secret key ได้ อย่างไรก็ตาม keytool ที่มาพร้อมกับ JDKs ไม่สามารถดึง private key ที่เก็บไว้ใน JKS ออกมาได้ Keystore ประเภทนี้มักมีนามสกุลเป็น jks
ต่อไปเราจะแสดงวิธีการใช้งาน JKS keystore ด้วยโค้ด Java บริสุทธิ์
สร้าง JKS keystore
วิธีที่ง่ายที่สุดในการสร้าง JKS keystore คือการสร้าง keystore ว่างเปล่า ก่อนอื่นเราสามารถรับ instance ของ KeyStore และโหลด keystore ที่เป็น null จากนั้นหลังจากโหลด keystore ที่เป็น null แล้ว เราเพียงแค่ต้องเรียก KeyStore.store() ด้วยชื่อ keystore และรหัสผ่านของ keystore
ด้านล่างนี้เป็นตัวอย่างง่ายๆ:
try{ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(null,null); keyStore.store(new FileOutputStream("mytestkey.jks"), "password".toCharArray()); }catch(Exception ex){ ex.printStackTrace(); }
หลังจากการเรียกใช้ข้างต้น คุณจะเห็น keystore ชื่อ mytestkey.jks ในไดเร็กทอรีปัจจุบัน ตอนนี้ keystore ว่างเปล่าโดยไม่มีรายการใดๆ
เก็บ private key
ตอนนี้เรามาเก็บ private key หนึ่งตัวและ certificate chain ที่เกี่ยวข้องลงใน keystore โปรดทราบว่าเราไม่สามารถเก็บ private key โดยไม่มี certificate chain ที่เกี่ยวข้องลงใน keystore โดยใช้ JDK ได้ อย่างไรก็ตามด้วยไลบรารีอื่นหรือไลบรารีเนทีฟ คุณอาจสามารถเก็บ private key โดยไม่มี certificate chain ที่เกี่ยวข้องได้
try{ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("mytestkey.jks"),"password".toCharArray()); 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("mykey", key, "password".toCharArray(), chain); keyStore.store(new FileOutputStream("mytestkey.jks"), "password".toCharArray()); }catch(Exception ex){ ex.printStackTrace(); }
ก่อนอื่น เราจะสร้าง private key และ self signed certificate จากนั้นเรียก KeyStore.setKeyEntry() ด้วย alias ที่ระบุ key รหัสผ่านสำหรับ key และ certificate chain ที่เกี่ยวข้อง โปรดจำไว้ว่าเราต้องเรียก KeyStore.store() เพื่อเก็บ key ลงใน keystore
Alias คือป้ายกำกับของรายการเพื่อให้สามารถค้นหาได้ง่ายในภายหลัง
เก็บใบรับรอง
เราสามารถเก็บใบรับรองบน JKS keystore ได้ ใบรับรองที่จะเก็บควรเป็น X509Certificate สามารถเก็บไว้ใน keystore โดยไม่มี private key ที่เกี่ยวข้อง กระบวนการนี้คล้ายกับการเก็บ private key
try{ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("mytestkey.jks"),"password".toCharArray()); CertAndKeyGen gen = new CertAndKeyGen("RSA","SHA1WithRSA"); gen.generate(1024); X509Certificate cert=gen.getSelfCertificate(new X500Name("CN=SINGLE_CERTIFICATE"), (long)365*24*3600); keyStore.setCertificateEntry("single_cert", cert); keyStore.store(new FileOutputStream("mytestkey.jks"), "password".toCharArray()); }catch(Exception ex){ ex.printStackTrace(); }
โหลด private key
หลังจากเก็บ key แล้ว เราสามารถโหลดรายการภายใน keystore ได้ ที่นี่เรากำลังพูดถึงการโหลด private key จริงๆแล้วไม่ใช่กรณีนี้ อย่างที่เราอธิบายไว้ก่อนหน้านี้ private key ไม่สามารถดึงออกมาจาก JKS โดยใช้ Java ได้ ที่นี่เราจริงๆแล้วดึง certificate chain ของ private key
try{ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("mytestkey.jks"),"password".toCharArray()); Key key = keyStore.getKey("mykey", "password".toCharArray()); // System.out.println("Private key : "+key.toString()); //คุณจะได้รับ NullPointerException ถ้าคุณยกเลิกการแสดงความคิดเห็นบรรทัดนี้ java.security.cert.Certificate[] chain = keyStore.getCertificateChain("mykey"); for(java.security.cert.Certificate cert:chain){ System.out.println(cert.toString()); } }catch(Exception ex){ ex.printStackTrace(); }
โปรดทราบว่าบรรทัดที่แสดงความคิดเห็น key จะเป็น null ตามที่คาดไว้ เราสามารถรับ certificate chain ได้ตามปกติ
[ [ Version: V3 Subject: CN=ROOT Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5 Key: Sun RSA public key, 1024 bits modulus: 90980299845597512779139009881469177009407272139633139241921529845092210461181243924599150259446249079941561941533303439718936138867375776965995893255358889228584415558006141961051402385279285497775776996780406808976543439543789816486513982581378223575354716191394304768315366544413052547926792470794374067383 public exponent: 65537 Validity: [From: Sat Sep 06 09:57:28 CST 2014, To: Sun Sep 06 09:57:28 CST 2015] Issuer: CN=ROOT SerialNumber: [ 206b697b] ] Algorithm: [SHA1withRSA] Signature: 0000: 53 6A FD FE E6 3A 5E 6E A6 43 C4 F4 D1 56 D4 08 Sj...:^n.C...V.. 0010: 7E 3B 8B 73 68 71 56 AB 96 FE 24 E7 2D DC 04 BB .;.shqV...$.-... 0020: 14 B0 C6 71 8D F0 3E EC FE D8 5B BB 8C 0F 55 63 ...q..>...[...Uc 0030: 2B 38 8E 45 F1 2D F0 BB 8C 6D 13 A8 11 37 E1 FA +8.E.-...m...7.. 0040: 77 AF C7 73 72 2B 40 4F 74 32 F6 3C 24 E6 AB ED w..sr+@Ot2.<$... 0050: 2C 6F 19 2E DC 58 5F CB 75 62 40 2F 3E BE 59 99 ,o...X_.ub@/>.Y. 0060: C0 1F 7A 70 15 AF C3 66 B3 4F C9 11 C3 45 59 EF ..zp...f.O...EY. 0070: 36 F4 1C C9 9B FA 5E 43 A0 28 DB 07 0D F2 53 6E 6.....^C.(....Sn ]
โหลดใบรับรอง
คล้ายกับการโหลด private key เราต้องส่ง alias ของใบรับรองที่เราต้องการดึงออกมา
try{ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(new FileInputStream("mytestkey.jks"),"password".toCharArray()); java.security.cert.Certificate cert = keyStore.getCertificate("single_cert"); System.out.println(cert.toString()); }catch(Exception ex){ ex.printStackTrace(); }
ผลลัพธ์จะเป็น:
[ [ Version: V3 Subject: CN=SINGLE_CERTIFICATE Signature Algorithm: SHA1withRSA, OID = 1.2.840.113549.1.1.5 Key: Sun RSA public key, 1024 bits modulus: 99756834215197288877309915243024788596281418171661241282881476656110879586349799740269767889529808199104172091786860877280382867461569439907754755558759387462421169749111354565793974372777424046360810758009149155148290676527032833774084635148674232352006810533640038723102562578516643345287042787777951043863 public exponent: 65537 Validity: [From: Sat Sep 06 10:14:33 CST 2014, To: Sun Sep 06 10:14:33 CST 2015] Issuer: CN=SINGLE_CERTIFICATE SerialNumber: [ 6943e549] ] Algorithm: [SHA1withRSA] Signature: 0000: 35 58 70 96 F4 35 82 2A 95 9F BB 31 02 6E 7C 29 5Xp..5.*...1.n.) 0010: 4A FE AF EB 2D B5 3A A7 C7 9D 4C 9A 34 2C 5C 46 J...-.:...L.4,\F 0020: C2 82 A8 AC 1A C0 98 A5 67 21 74 7B 1E E2 E5 AC ........g!t..... 0030: DE B2 1D 87 BE 16 45 9B D0 2A D3 2B F6 E1 4B 35 ......E..*.+..K5 0040: 27 8B A7 0A EF F2 07 41 90 A6 69 07 BE 87 C5 B1 '......A..i..... 0050: 54 DE DB A2 5A 41 47 3B 3F A7 74 6F 5C C8 8D B4 T...ZAG;?.to\... 0060: C8 65 2B 0F 8E 94 A8 80 C7 8B B5 78 FA C2 9C ED .e+........x.... 0070: 8E EC 28 E4 8E 62 A1 59 6A BC 37 7B 0D FC C7 AF ..(..b.Yj.7..... ]
นำเข้า key และใบรับรอง
กระบวนการนี้ง่ายมาก ก่อนอื่นเราต้องโหลด keystore ที่มีใบรับรองที่จะนำเข้า จากนั้นเราต้องโหลด keystore อีกอันหนึ่งที่เราต้องการนำเข้าใบรับรอง ต่อไป เราต้องรับใบรับรองจาก keystore แหล่งที่มาและใส่ลงใน keystore ปลายทาง
เนื่องจากเราไม่สามารถดึง private key ออกจาก JKS ได้ ดังนั้นเราจึงสามารถนำเข้าใบรับรองไปยัง JKS ได้เท่านั้น อย่างไรก็ตามเราสามารถดึง private key ออกจาก keystore ประเภทอื่น (PKCS12) และจากนั้นเก็บไว้ใน JKS keystore
ข้อมูลชิ้นสุดท้าย Oracle ให้ keystore JKS สองเวอร์ชัน: 區分大小寫 และ ไม่區分大小寫 เมื่อเรียก KeyStore.getInstance("JKS") จะสร้าง instance JKS เวอร์ชันไม่區分大小寫 เมื่อเรียก KeyStore.getInstance("CaseExactJKS") จะสร้าง instance JKS เวอร์ชัน區分大小寫 โดยปกติแล้วแนะนำให้ใช้ไม่區分大小寫 เนื่องจากผู้ใช้ควรแยกแยะรายการต่างๆ ด้วยชื่อ alias ที่แตกต่างกัน แทนที่จะเป็นกรณีชื่อ alias ที่แตกต่างกัน สำหรับข้อมูลเพิ่มเติมเกี่ยวกับการ區分大小寫 โปรดดู โพสต์นี้
เราจะครอบคลุม keystore ประเภทอื่นในโพสต์ต่อไป
Pi Ke,
I have a comment about this code in "Loading private key" part:
Key key = keyStore.getKey(
"alias"
,
"password"
.toCharArray());
By using "alias", I think you're trying to say private key should be extract by its alias. But, in "Store Private Key" part, the private key has been stored as "mykey". So, to be consistent, I think the upper code should use "mykey" although the value is null too due to JKS type. Is my understanding right? Thanks!