Java安全——密鑰那些事


標簽(空格分隔): Java 安全

概念

密鑰是加密算法不可缺少的部分。密鑰在安全體系中至關重要,正如其名,私密的鑰匙,打開安全的大門。密鑰分兩種:對稱密鑰和非對稱密鑰。非對稱密鑰里又包含公開密鑰和私有密鑰。

與密鑰相關的還有一個概念是證書。證書主要用於鑒別密鑰,通常將公開密鑰放到證書里傳輸。

Java的安全體系里,密鑰是通過JCE算法包實現的。操作密鑰的引擎包含兩部分:密鑰生成器和密鑰工廠。密鑰生成器可以創建密鑰,而密鑰工廠將其進行包裝展示到外部。所以對於編寫程序來說,創建密鑰包括兩個步驟:1,用密鑰生成器產生密鑰;2,用密鑰工廠將其輸出為一個密鑰規范或者一組字節碼。

Java實現

Java里將密鑰封裝了一個接口——Key。非對稱密鑰有PublicKey和PrivateKey,均實現了該接口。從之前的“安全提供者框架”中的輸出結果可以看到,不同的安全提供者提供了很多密鑰生成算法,比較典型的是Sun的DSA和RSA以及JCE的Diffie-Hellman算法。

生成和表示key

密鑰的生成,Java提供了兩個生成器類——KeyPairGenerator和KeyGenerator,前者用於生成非對稱密鑰,后者用於生成對稱密鑰。對應密鑰的表示,KeyFactory類表示非對稱密鑰,SecretKeyFactory表示對稱密鑰。

我們來看一個DSA的例子:

import java.security.KeyFactory;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.NoSuchAlgorithmException;
import java.security.spec.DSAPrivateKeySpec;
import java.security.spec.InvalidKeySpecException;

import javax.crypto.KeyGenerator;

import javax.crypto.SecretKey;

import javax.crypto.SecretKeyFactory;

import javax.crypto.spec.DESKeySpec;

public class KeyTest {

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">main</span><span class="hljs-params">(String[] args)</span> </span>{</br>
    <span class="hljs-keyword">try</span> {</br>
        generateKeyPair();</br>
        generateKey();</br>
    } <span class="hljs-keyword">catch</span> (InvalidKeySpecException e) {</br>
        e.printStackTrace();</br>
    } <span class="hljs-keyword">catch</span> (NoSuchAlgorithmException e) {</br>
        e.printStackTrace();</br>
    }</br>
}</br></br>

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">generateKeyPair</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException </span>{</br>
    KeyPairGenerator kpg = KeyPairGenerator.getInstance(<span class="hljs-string">"DSA"</span>);</br>
    kpg.initialize(<span class="hljs-number">512</span>);</br>
    KeyPair kp = kpg.generateKeyPair();</br>
    System.out.println(kpg.getProvider());</br>
    System.out.println(kpg.getAlgorithm());</br>
    KeyFactory kf = KeyFactory.getInstance(<span class="hljs-string">"DSA"</span>);</br>
    DSAPrivateKeySpec dsaPKS = kf.getKeySpec(kp.getPrivate(), DSAPrivateKeySpec.class);</br>
    System.out.println(<span class="hljs-string">"\tDSA param G:"</span> + dsaPKS.getG());</br>
    System.out.println(<span class="hljs-string">"\tDSA param P:"</span> + dsaPKS.getP());</br>
    System.out.println(<span class="hljs-string">"\tDSA param Q:"</span> + dsaPKS.getQ());</br>
    System.out.println(<span class="hljs-string">"\tDSA param X:"</span> + dsaPKS.getX());</br>
}</br></br>

<span class="hljs-function"><span class="hljs-keyword">public</span> <span class="hljs-keyword">static</span> <span class="hljs-keyword">void</span> <span class="hljs-title">generateKey</span><span class="hljs-params">()</span> <span class="hljs-keyword">throws</span> NoSuchAlgorithmException, InvalidKeySpecException </span>{</br>
    KeyGenerator kg = KeyGenerator.getInstance(<span class="hljs-string">"DES"</span>);</br>
    SecretKey key = kg.generateKey();</br>
    System.out.println(kg.getProvider());</br>
    System.out.println(kg.getAlgorithm());</br>
    SecretKeyFactory skf = SecretKeyFactory.getInstance(<span class="hljs-string">"DES"</span>);</br>
    DESKeySpec desKS = (DESKeySpec) skf.getKeySpec(key, DESKeySpec.class);</br>
    System.out.println(<span class="hljs-string">"\tDES key bytes size:"</span> + desKS.getKey().length);</br>
}</br>

}

Key生成的代碼架構設計類圖如下:
%5b%26lt%3b%26lt%3bKey%26gt%3b%26gt%3b%5

KeyGenerator與KPG類似,只是KPG生成KeyPair,KG
生成SecretKey。

密鑰管理

關於證書

證書這東西,真不知道放哪里合適,就不單獨拿出來講了。考慮到證書可以驗證密鑰的合法性,就放這里說一下吧。

因為非對稱密鑰的場景,需要將公鑰傳輸給對應的需求者。那么如何傳輸能確保這個公鑰是我給你的而不是別人替換的呢?那就需要對這次傳輸加密簽名,於是又進入了這樣的循環。於是就引出了證書——證書可以保證內容和發源地是一致的,也就是說證書可以保證發給需求者的內容確實是屬於內容擁有者的。

證書不是誰都能來發的,證書是通過一個公正實體(CA,證書授權機構)來頒發並驗證合法性。證書包含三方面內容:
1,實體名,即證書持有者。
2,與主體相關的公開密鑰。
3,驗證證書信息的數字簽名。證書由證書發行人簽名。
Java中有對應的Certificate類來做證書相關的事情。但是證書不是我們這里要討論的重點,而且Java本身對證書的支持就不完備。因此證書的內容就在這里插播一下。我們還是回到密鑰的傳輸問題上來。

KeyStore

Java中KeyStore類負責密鑰的管理,KeyStore有個setKeyEntry()方法。通用的流程是KeyStore將key設置為一個key entry。然后通過store()方法保存為.keystore文件。使用方得到.keystore文件,利用load()方法讀取Key entry,然后使用。

如果是非對稱密鑰的秘密密鑰,寫入密鑰項的使用方法如下:

public static void secretKeyStore() throws KeyStoreException, NoSuchAlgorithmException, CertificateException, IOException {
char[] password = "123456".toCharArray();
String fileName = System.getProperty("user.home") + File.separator + ".keystore";
FileInputStream fis = new FileInputStream(fileName);
KeyStore ks = KeyStore.getInstance("jceks");
ks.load(fis, password);
KeyGenerator kg = KeyGenerator.getInstance("DES");
SecretKey key = kg.generateKey();

    ks.setKeyEntry(<span class="hljs-string">"myKeyEntry"</span>, key, password, <span class="hljs-literal">null</span>);</br></br>

    FileOutputStream fos = <span class="hljs-keyword">new</span> FileOutputStream(fileName);</br>
    ks.store(fos, password);</br>
    System.<span class="hljs-keyword">out</span>.println(<span class="hljs-string">"store key in "</span> + fileName);</br>
}</code></pre>

這里帶來一些概念:

  • 密鑰庫:也就是上面說的KeyStore,用來管理存放密鑰和證書的地方。Java的密鑰管理是基於密鑰庫來構建的。
  • 密鑰項:密鑰庫里存放的是一條條的密鑰項。密鑰項要么保存一個非對稱密鑰對,要么保存一個秘密密鑰。如果保存的是密鑰對,那還可能保存一個證書鏈。證書鏈的第一個證書包含公鑰。
  • 別名:每個密鑰都會可以有個別名,可以理解為密鑰項的名字。
  • 標識名:密鑰庫中的實體的標識名是其完整的X.500名的子集,比如一個DN是

    CN=Yu Jia, OU=ALI, O=ALIBABA, L=HZ, ST=ZJ, C=CN
  • 證書項:只包含一個公鑰證書,保存的是證書而不是證書鏈。
  • JKS,JCEKS,PKCS12:密鑰庫算法,Java默認是JKS,只能保存私鑰,要想保存對稱密鑰的秘密密鑰,需要使用JCEKS,這也就是上面代碼中提到的KeyStore ks = KeyStore.getInstance("jceks");。可以通過修改java.security文件中的keystore.type=JCEKS來更改默認算法。

Keytool

光是這樣,還欠點什么,因為上面的代碼放到main函數里還是無法執行,而且也有個疑問,明明是要創建keystore,干嘛還要先load?
看看KeyStore中store()方法的源碼:

public final void store(OutputStream stream, char[] password)
throws KeyStoreException, IOException, NoSuchAlgorithmException, CertificateException
{
if (!initialized) {
throw new KeyStoreException("Uninitialized keystore");
}
keyStoreSpi.engineStore(stream, password);
}

未初始化的Keystore是要拋出KeyStoreException的。而初始化動作是在load()方法里做的。那這就奇怪了,第一個keystore難道是自己隨便在系統目錄里touch的?

這就引出了keytool工具,這是一個JRE提供的管理工具,方便管理密鑰庫的。keytool是命令行接口,使用keytool命令可以管理密鑰庫,具體命令各個參數可以man keytool或者keytool -help了解。

我這里列出我的程序是如何初始化一個keystore的:
1,我先生成了一個別名叫做changedi的密鑰項,其算法是RSA非對稱算法

zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -genkey -alias changedi -keyalg RSA 輸入密鑰庫口令: 再次輸入新口令: 您的名字與姓氏是什么? [Unknown]: Yu Jia 您的組織單位名稱是什么? [Unknown]: ALI 您的組織名稱是什么? [Unknown]: ALIBABA 您所在的城市或區域名稱是什么? [Unknown]: HZ 您所在的省/市/自治區名稱是什么? [Unknown]: ZJ 該單位的雙字母國家/地區代碼是什么? [Unknown]: CN CN=Yu Jia, OU=ALI, O=ALIBABA, L=HZ, ST=ZJ, C=CN是否正確? [否]: Y 

輸入 <changedi> 的密鑰口令
(如果和密鑰庫口令相同, 按回車):
再次輸入新口令:

2,依照提示輸入完成DN后,keystore就創建好了,可以查看一下

zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -list
輸入密鑰庫口令:  

密鑰庫類型: JKS
密鑰庫提供方: SUN

您的密鑰庫包含 1 個條目

changedi, 2016-7-7, PrivateKeyEntry,
證書指紋 (SHA1): 76:C8:CE:EA:4C:29:6D:0E:FF:8C:02:BE:F4:F4:55:97:63:1F:C8:26

3,可以看到,這個庫還是JKS的,需要更改為JCEKS,於是做下面的事

zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -keypasswd -alias changedi -storetype jceks
輸入密鑰庫口令:  
輸入 <changedi> 的密鑰口令
新<changedi> 的密鑰口令: 
重新輸入新<changedi> 的密鑰口令: 

4,再list時,要選擇storetype,因為剛才雖然是修改密碼,但是其實核心目的是要更改密鑰庫類型

zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -list -storetype jceks
輸入密鑰庫口令:  

密鑰庫類型: JCEKS
密鑰庫提供方: SunJCE

您的密鑰庫包含 1 個條目

changedi, 2016-7-7, PrivateKeyEntry,
證書指紋 (SHA1): 76:C8:CE:EA:4C:29:6D:0E:FF:8C:02:BE:F4:F4:55:97:63:1F:C8:26

5,運行剛才的程序,寫一個對稱密鑰的秘密密鑰進去,作為這個keystore的一個密鑰項,再list

zunyuanjys-MacBook-Air:~ zunyuan.jy$ keytool -list -storetype jceks
輸入密鑰庫口令:  

密鑰庫類型: JCEKS
密鑰庫提供方: SunJCE

您的密鑰庫包含 2 個條目

changedi, 2016-7-7, PrivateKeyEntry,
證書指紋 (SHA1): 76:C8:CE:EA:4C:29:6D:0E:FF:8C:02:BE:F4:F4:55:97:63:1F:C8:26
mykeyentry, 2016-7-7, SecretKeyEntry,

其實上面的例子,在創建第一個密鑰項時就可以指定storetype是JCEKS,我這里只是展示一下如何切換密鑰庫類型。另外在RSA的私鑰密鑰項在未指定證書的情況下也會生成一個自簽名證書。

回到剛才的代碼里,我們看看setKeyEntry的細節:

public final void setKeyEntry(String alias, Key key, char[] password, Certificate[] chain)
throws KeyStoreException
{
if (!initialized) {
throw new KeyStoreException("Uninitialized keystore");
}
if ((key instanceof PrivateKey) &&
(chain == null || chain.length == 0)) {
throw new IllegalArgumentException("Private key must be "
+ "accompanied by certificate "
+ "chain");
}
keyStoreSpi.engineSetKeyEntry(alias, key, password, chain);
}

可以看到,如果是非對稱密鑰的生產,需要提供一個證書鏈,否則就拋異常。考慮到這樣的情況,我們一般不是做專業的企業級安全。還是keytool搞定好了。

  </div>


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM