Different types of keystore in Java -- JKS

  Pi Ke        2014-09-05 20:21:51       47,011        3          English  简体中文  繁体中文  ภาษาไทย  Tiếng Việt 

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 執行個體的區分大小寫版本。通常建議使用不區分大小寫,因為使用者應該使用不同的別名名稱來區分不同的項目,而不是使用不同的別名名稱大小寫。有關區分大小寫的更多資訊,請參閱 此篇文章

我們將在未來的文章中介紹其他類型的密鑰庫。

DEMO  EXAMPLE  KEYSTORE  JKS 

       

  RELATED


  3 COMMENTS


lintao [Reply]@ 2015-01-12 03:43:48

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!

NightCat [Reply]@ 2015-01-12 07:11:36

Yes. You are correct. The alias name here actually doesn't matter. But to be consistent, I have updated it to mykey. Thank you for pointing this out.

Cat Mucius [Reply]@ 2020-02-21 15:52:41

If value of the private key cannot be extracted from a JKS keystore, then how can Cipher object of any provider use it?

Is there some "backdoor" function utilized?



  RANDOM FUN

Everything is under control