JKS 是 Java 密钥库,一种专为 Java 设计的专有密钥库类型。它可以用来存储用于 SSL 通信的私钥和证书,但是它不能存储密钥。JDK 附带的 keytool 无法提取存储在 JKS 中的私钥。这种类型的密钥库通常扩展名为 jks。
接下来我们将展示如何使用纯 Java 代码操作 JKS 密钥库。
创建 JKS 密钥库
创建 JKS 密钥库最简单的方法是创建一个空的密钥库。我们可以首先获取 KeyStore 的实例,然后加载一个空密钥库。加载空密钥库后,我们只需要调用 KeyStore.store() 方法,并指定密钥库名称和密钥库密码即可。
下面是一个简单的演示:
try{ KeyStore keyStore = KeyStore.getInstance("JKS"); keyStore.load(null,null); keyStore.store(new FileOutputStream("mytestkey.jks"), "password".toCharArray()); }catch(Exception ex){ ex.printStackTrace(); }
执行上述调用后,您将在当前工作目录中看到一个名为 mytestkey.jks 的密钥库。现在密钥库是空的,没有任何条目。
存储私钥
现在让我们将一个私钥及其关联的证书链存储到密钥库中。请注意,我们不能使用 JDK 将私钥与其关联的证书链一起存储到密钥库中。使用其他库或本机库,您也许能够存储没有关联证书链的私钥。
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(); }
首先,我们将创建一个私钥和一个自签名证书,然后调用 KeyStore.setKeyEntry() 方法,并指定别名、密钥、密钥密码及其关联的证书链。请记住,我们需要调用 KeyStore.store() 方法才能将密钥存储到密钥库中。
别名是条目的标签,以便以后可以轻松找到它。
存储证书
我们可以将证书存储在 JKS 密钥库中。要存储的证书应该是 X509Certificate。它可以存储在密钥库中,而无需关联私钥。此过程类似于存储私钥。
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(); }
加载私钥
存储密钥后,我们还可以加载密钥库中的条目。在这里,我们说要加载私钥,实际上并非如此,正如我们前面所述,使用 Java 无法从 JKS 中提取私钥。在这里,我们实际上提取私钥的证书链。
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(); }
请注意注释的行,密钥将为 null,正如预期的那样。但是,我们可以正常获取证书链。
[ [ 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 ]
加载证书
这类似于加载私钥,我们需要传递要提取的证书的别名。
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..... ]
导入密钥和证书
此过程实际上非常简单,我们首先需要加载要导入证书的密钥库。然后,我们还需要加载另一个密钥库,我们需要将证书导入到该密钥库中。接下来,我们需要从源密钥库获取证书并将其放入目标密钥库。
由于我们无法从 JKS 中提取私钥,因此我们只能将证书导入 JKS。但是,我们可以从其他类型的密钥库 (PKCS12) 中提取私钥,然后将它们存储在 JKS 密钥库中。
最后一条信息。Oracle 提供了两个版本的 JKS 密钥库:区分大小写和不区分大小写。调用 KeyStore.getInstance("JKS") 时,将创建一个不区分大小写的 JKS 实例,调用 KeyStore.getInstance("CaseExactJKS") 时,将创建一个区分大小写的 JKS 实例。通常建议使用不区分大小写,因为用户应该使用不同的别名来区分不同的条目,而不是使用不同的别名大小写。有关区分大小写的更多信息,请参阅 此文章。
我们将在以后的文章中介绍其他类型的密钥库。
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!