Java Cryptography Architecture
密钥根接口Key
接口Key位于包java.security中。密钥在加解密运算中的地位至关重要,密钥丢失,安全目标中的机密性就已经失去了。
Kerckhoff原则:只有密钥保密。密码学的发展经历了漫长的试错过程,而一个更成熟的观点也在这个过程中产生:为了对密码学原语的安全性获得足够的信心,每个密码原语必须由密码专家对其进行公开分析。若做不到这一点,密码原语的可靠性只能依赖于含糊不清的安全性说明,而历史证明了其效果不尽如人意。
所有的密钥都具有三个特征
- 真正随机:密钥一定是来自一个可靠的随机源产生的随机数,所以一个好的密钥必须来自好的随机数。
- 算法相关:密钥和加解密算法是相关的,不同的算法对于密钥的长度都有要求,比如DES算法要求密钥是56位的,如果想让Java生成一个57位的DES算法密钥,就会直接运行报错。
- 特定编码:密钥数据通常具有标准的编码格式,主要是为了方便密钥管理和与其他加解密函数库进行交互和解析。比如,Java提供PEM和DER两种编码方式对这些密钥进行编码,并提供相关指令以使用户在这两种格式之间进行转换,其他加解密函数库(比如OpenSSL)也是采用的这两种编码格式。
SecretKey、PublicKey、PrivateKey三大接口继承于Key接口,定义了对称密钥和非对称密钥接口。
安全随机数SecureRandom
Java提供了两个类来为用户提供随机数功能,分别是类Random和类SecureRandom。SecureRandom是从Random派生而来的,且专门用于要求高的密码学场合。类Random一般用于随机性要求不高的场合,但是速度快。
伪随机Random
伪随机数是用确定性的算法计算出来的均匀分布的随机数序列,并不真正随机,但是具有类似于随机数的统计特征,如均匀性、独立性等。在计算伪随机数时,若使用的初值(种子)不变,那么伪随机数的数序也不变。
类Random用来创建伪随机数,如果给定一个初始的种子,产生的随机数序列是完全一样的;如果不给定种子,就使用系统当前时间戳作为种子,那么每次运行时,种子不同,得到的伪随机数序列就不同。
如果我们在创建Random实例时指定一个种子,就会得到完全确定的随机数序列,比如:
真随机SecureRandom
严格意义上的真随机可能仅存在于量子力学之中,我们当前所想要的(或者所能要的)并不是这种随机。我们其实想要一种不可预测、统计意义上、密码学安全的随机数,只要能做到这一点的随机数生成器都可以称为真随机数生成器。
想直接利用算法得到真随机数有点难度,但是也不是没有办法。计算机不能产生随机数,但是现实世界中有非常多的随机因素,将这种随机因素引入计算机就能实现真随机。Linux内核中的随机数发生器(/dev/random)理论上能产生真随机,即这个随机数的生成独立于生成函数,我们说这个随机数发生器是非确定的(不可预见的)。这种真随机并不一定非得是特殊设计的硬件。Linux操作系统内核中的随机数生成器(/dev/random)维护了一个熵池,用于搜集硬件噪声,比如键盘敲击速度、鼠标位置变化、网络信号强度变化、时钟、IO请求的响应时间、特定硬件中断的时间间隔甚至周围的电磁波等。直观地讲,按一次键盘、动一下鼠标、邻居家Wi-Fi信号强度变化、磁盘写入速度等都能够提供最大可能的随机数据熵。Linux维护着这样一个熵池,不断收集非确定性的设备事件来作为种子(种子就是输入到随机数产生器中的比特串),因此可认为是高品质的真随机数生成器。不过/dev/random是阻塞的,也就是说,如果熵池空了,对于/dev/random的读操作将被挂起,直至收集到足够的环境噪声为止。因此,在开发程序时,我们应使用/dev/urandom作为/dev/random的一个副本,它不会阻塞,但其输出的熵可能会小于/dev/random。真随机数有一个非常基本的特征就是不可预测性。
类SecureRandom提供了能满足加密要求的强随机数生成器。随机数在密码学中有着地基的地位。至关重要的密钥首先必须是一个安全的随机数。密码学意义上的安全随机数要求必须保证其不可预测性,实际上,密码学中有15项随机数检测规则,限于篇幅,这里不展开了。怎么得到安全的随机数?通常可以通过硬件随机数芯片来获得,也可以通过非物理的随机数产生器来获得。目前,三大非物理的随机数产生器有Linux操作系统的/dev/random设备接口、Windows操作系统的CryptGenRandom接口、JDK的java.security.SecureRandom类。
Java Cryptography Extension
密钥接口SecretKey
接口SecretKey用于保存对称密钥,从接口Key派生。该接口自己并没有提供成员方法,而是使用Key中的方法,比如getFormat和getEncoded,前者返回密钥的编码格式,后者得到原始密钥(没有编码过的密钥)数组。对于没有编码过的密钥,getFormat返回的结果是“RAW”。
生成密钥常用以下两种方式:
- 第一种方式是使用密钥生成器keyGenerator,定义密钥生成器keyGenerator的实例对象(密钥由密钥生成器来生成,就像米饭是由电饭锅来生成的一样),然后调用init方法进行密钥生成器的初始化,也就是为密钥生成器设置一些我们所需密钥的属性参数,比如密钥长度、随机数等(没有随机数肯定是没有办法生成密钥的,密钥通常是一个随机数)。就像我们要为电饭锅设置烧饭时间,并给电饭锅放入大米(没有大米,电饭锅这个米饭生成器是肯定无法生成米饭的)一样,最后调用密钥生成方法generateKey来生成密钥(相当于电饭锅开始烧出米饭来了),返回值存于Secretkey接口中。
- 第二种方式稍微简便些,即使用接口Secretkey的实现类SecretKeySpec(该类是密钥规范类),实例化SecretKeySpec对象产生密钥,返回结果存于Secretkey接口中。
密钥生成器KeyGenerator
1、创建密钥生成器
//得到一个AES算法的密钥生成器对象
KeyGenerator keygen=KeyGenerator.getInstance("AES");
2、初始化密钥生成器对象
比如,初始化一个DES算法的56比特的密钥:
keygen.init(56);
如果密钥生成器KeyGenerator定义了DES算法,但是初始化传入的密钥长度不是56比特,会如何呢?运行报错!因为不同的加密算法对密钥长度都是有要求的。
3、生成密钥
KeyGenerator keyGenerator = KeyGenerator.getInstance("DES");
keyGenerator.init(56);
SecretKey secretKey = keyGenerator.generateKey();
String secretKeyFormat = secretKey.getFormat();
System.out.println(secretKeyFormat);
密钥规范类SecretKeySpec
String deskey="12345678";
SecretKey secretKey = new SecretKeySpec(deskey.getBytes(), "DES");
初始向量类IvParameterSpec
在分组加密算法的CBC模式下需要一个称为初始向量(IV)的字节数组。有了初始向量iv,可以增加加密算法的强度。这个初始向量以后会作为参数传入加解密对象(Cipher)的初始化函数(init函数)。
IvParameterSpec iv = IvParameterSpec("0123456789101112".getBytes());