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 {

    public static void main(String[] args) {
        try {
            generateKeyPair();
            generateKey();
        } catch (InvalidKeySpecException e) {
            e.printStackTrace();
        } catch (NoSuchAlgorithmException e) {
            e.printStackTrace();
        }
    }

    public static void generateKeyPair() throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyPairGenerator kpg = KeyPairGenerator.getInstance("DSA");
        kpg.initialize(512);
        KeyPair kp = kpg.generateKeyPair();
        System.out.println(kpg.getProvider());
        System.out.println(kpg.getAlgorithm());
        KeyFactory kf = KeyFactory.getInstance("DSA");
        DSAPrivateKeySpec dsaPKS = kf.getKeySpec(kp.getPrivate(), DSAPrivateKeySpec.class);
        System.out.println("\tDSA param G:" + dsaPKS.getG());
        System.out.println("\tDSA param P:" + dsaPKS.getP());
        System.out.println("\tDSA param Q:" + dsaPKS.getQ());
        System.out.println("\tDSA param X:" + dsaPKS.getX());
    }

    public static void generateKey() throws NoSuchAlgorithmException, InvalidKeySpecException {
        KeyGenerator kg = KeyGenerator.getInstance("DES");
        SecretKey key = kg.generateKey();
        System.out.println(kg.getProvider());
        System.out.println(kg.getAlgorithm());
        SecretKeyFactory skf = SecretKeyFactory.getInstance("DES");
        DESKeySpec desKS = (DESKeySpec) skf.getKeySpec(key, DESKeySpec.class);
        System.out.println("\tDES key bytes size:" + desKS.getKey().length);
    }

}

Key生成的代码架构设计类图如下:

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("myKeyEntry", key, password, null);

        FileOutputStream fos = new FileOutputStream(fileName);
        ks.store(fos, password);
        System.out.println("store key in " + fileName);
    }

这里带来一些概念:

  • 密钥库:也就是上面说的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搞定好了。

时间: 2017-03-14

Java安全——密钥那些事的相关文章

Java日志性能那些事

在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索.绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?今天就让我们来聊聊Java日志性能那些事. DEBUG级别的日志在生产环境中不会输出到文件中,也可能带来不小的开销.我们撇开判断和方法调用的开销,在Log4J 2.x的性能文档中 有这样一组对比:logger.debug("Entry number: " + i + " is &qu

Java日志性能那些事(转)

在任何系统中,日志都是非常重要的组成部分,它是反映系统运行情况的重要依据,也是排查问题时的必要线索.绝大多数人都认可日志的重要性,但是又有多少人仔细想过该怎么打日志,日志对性能的影响究竟有多大呢?今天就让我们来聊聊Java日志性能那些事. 说到Java日志,大家肯定都会说要选择合理的日志级别.合理控制日志内容,但是这仅是万里长征第一步--哪怕一些DEBUG级别的日志在生产环境中不会输出到文件中,也可能带来不小的开销.我们撇开判断和方法调用的开销,在Log4J 2.x的性能文档中有这样一组对比:

关于Java你不知道的十件事

作为 Java 书呆子,比起实用技能,我们会对介绍 Java 和 JVM 的概念细节更感兴趣.因此我想推荐 Lukas Eder 在 jooq.org 发表的原创作品给大家. 你是从很早开始就一直使用 Java 吗?那你还记得它的过去吗?那时,Java 还叫 Oak,OO 还是一个热门话题,C++ 的 folk 者认为 Java 是不可能火起来,Java 开发的小应用程序 Applets 还受到关注. 我敢打赌,下面我要介绍的这些事,有一半你都不知道.下面让我们来深入探索 Java 的神秘之处.

Java调试那点事

该文章来自于阿里巴巴技术协会(ATA)精选文章. Java调试概述 程序猿都调式或者debug过Java代码吧?都体会过被PM,PD,测试,业务同学们围观debug吧?说调试,先看看调试严格定义是什么.引用Wikipedia定义: 调试(De-bug),又称除错,是发现和减少计算机程序或电子仪器设备中程序错误的一个过程.调试的基本步骤: 1. 发现程序错误的存在 2. 以隔离.消除的方式对错误进行定位 3. 确定错误产生的原因 4. 提出纠正错误的解决办法 5. 对程序错误予以改正,重新测试 用

Java代码规范那些事

Java开发中所要遵守的编码规范大体上有如下7点.命名规范.注释规范.缩进排版规范.文件名规范.声明规范.语句规范以及编程规范. 1.命名规范 (1)所有的标示符都只能用ASCⅡ字母(A-Z或a-z).数字(0-9)和下划线"_". (2)一个唯一包名的前缀总是全部小写的字母.例如:www.tonysun.cc (3)类名是一个名词,采用大小写混合的方式,每个单词的首字母大写.例如:Tony. (4)接口的大小写规则与类名相似:例如:Tony. (5)方法名是一个动词或动词词组,采用大

java 学习笔记

笔记 JAVA的多线程 一.线程基本概念 将1个程序转换成多个独立运行的子任务.每个子任务都叫做一个线程. "进程"是指一种"自包容"的运行程序.有自己的地址空间.一个进程可以容纳多个同时执行的线程. 事实上,多线程最主要的一个用途就构建1个"反应灵敏"的用户界面. 二.线程的使用 1. 创建一个线程 最简单的方法就是从Thread类继承这个类,包含了创建和运行线程所需的一切东西. Thread最重要的是run方法,继承类必须对之进行重载,使其按

AES加密改造 c#return 改成java

问题描述 AES加密改造 c#return 改成java /// /// 获取密钥 /// private static string Key { get { return @")O[NB]6,YF}+efcaj{+oESb9d8>Z'e9M"; } } 不懂c#上面这个方法改成java的咋改啊? 解决方案 一眼望去,字符串里也没有需要转义的特殊字符,所以上面这个对应java代码: private static String key = ")O[NB]6,YF}+efc

java中如何使用MD5进行加密_java

在各种应用系统的开发中,经常需要存储用户信息,很多地方都要存储用户密码,而将用户密码直接存储在服务器上显然是不安全的,本文简要介绍工作中常用的 MD5加密算法,希望能抛砖引玉. (一)消息摘要简介 一个消息摘要就是一个数据块的数字指纹.即对一个任意长度的一个数据块进行计算,产生一个唯一指印(对于SHA1是产生一个20字节的二进制数组).消息摘要是一种与消息认证码结合使用以确保消息完整性的技术.主要使用单向散列函数算法,可用于检验消息的完整性,和通过散列密码直接以文本形式保存等,目前广泛使用的算法

各大公司Java后端开发面试题总结

ThreadLocal(线程变量副本) Synchronized实现内存共享,ThreadLocal为每个线程维护一个本地变量. 采用空间换时间,它用于线程间的数据隔离,为每一个使用该变量的线程提供一个副本,每个线程都可以独立地改变自己的副本,而不会和其他线程的副本冲突. ThreadLocal类中维护一个Map,用于存储每一个线程的变量副本,Map中元素的键为线程对象,而值为对应线程的变量副本. ThreadLocal在Spring中发挥着巨大的作用,在管理Request作用域中的Bean.事