zoukankan      html  css  js  c++  java
  • 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">"	DSA param G:"</span> + dsaPKS.getG());</br>
        System.out.println(<span class="hljs-string">"	DSA param P:"</span> + dsaPKS.getP());</br>
        System.out.println(<span class="hljs-string">"	DSA param Q:"</span> + dsaPKS.getQ());</br>
        System.out.println(<span class="hljs-string">"	DSA 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">"	DES 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>
  • 相关阅读:
    POJ 3259 Wormholes【BellmanFord】
    POJ 2960 SNim【SG函数的应用】
    ZOJ 3578 Matrixdp水题
    HDU 2897 邂逅明下【bash博弈】
    BellmanFord 算法及其优化【转】
    【转】几个Java的网络爬虫
    thinkphp 反字符 去标签 自动加点 去换行 截取字符串 冰糖
    php 二维数组转 json文本 (jquery datagrid 数据格式) 冰糖
    PHP 汉字转拼音(首拼音,所有拼音) 冰糖
    设为首页与加入收藏 兼容firefox 冰糖
  • 原文地址:https://www.cnblogs.com/jpfss/p/8574341.html
Copyright © 2011-2022 走看看