一、JCA/ JCE
JCA(Java Cryptography Architecture) 是Java体系结构,提供了基本Java加密框架,比如证书、数字签名、消息摘要、秘钥对生成器等,在java.security包中实现。
JCE(Java Cryptography Extension)是JCA的扩展,主要负责提供DES、AES、RSA、DSA这样的加密算法,因为加密算法是会不断进步的,会有新的算法诞生,所以整个安全体系结构的可扩展性必须要得到保证。
JCE包因为其加密算法的安全限制,受美国出口限制,我们平时使用的是oracle提供的“阉割版”,比如默认不允许256位密钥的AES,我们通过oracle官网,可以下载“完整版”。
下面是“完整版的加密扩展包”
http://www.oracle.com/technetwork/java/javase/downloads/jce8-download-2133166.html-
解压缩以后是这个样子:
根据readme中的提示,放在
-
JCE并不是只有oracle提供的,有多家厂商可以提供JCE的扩展包,在我们jdk的安装目录下的java.security文件中可以看到,支持的服务提供者Provider。
JCA的实现是在java.security包下,这个包只能实现消息摘要算法,其他都要依赖JCE扩展包,在javax.crypto包中实现,也可以添加自己的provider,根据上图的格式继续填写第11个即可。使用Security.addProvider("xxx")可以完成Provider的添加,使用insertProviderAt("xxx", position)可以在指定位置完成添加,这里涉及到一个优先级的概念,如上图Provider所示,数字越小优先级越高。
借用oracle官方的一个例子,如图privider example,我们可以使用如下代码获得摘要实例,比如我们要获取SHA-256算法生成摘要的实例,第一句代码会走左侧的图,遍历所有provider,获取到支持该算法的Provider就立刻返回,左侧会返回ProviderB,而生成摘要是提供重载方法的,可以指定Provider,第二行代码指定了ProviderC,那么就会直接返回ProviderC,可以看到使用方法简单灵活。
md = MessageDigest.getInstance("SHA-256");
md = MessageDigest.getInstance("SHA-256", "ProviderC");
官方是不推荐使用Sun, SunJSSE,SunJCE,SunRsaSign这些厂商提供的Provider的,据说是因为历史原因导致这些厂商提供的功能和加密强度都不怎么样 。
MessageDigest.getInstance("SHA-256");这一行代码,的执行流程会是什么样子的呢?我们找到MessageDigest在包中位置,发现旁边还有一个和他的名字很像的SPI类,SPI全称是Service Provider Interface,是软件设计可插拔的体现,经常被用在插件的设计上。
-
类似MessageDigest这样的类,我们通常叫做“engine”类,可以翻译叫做引擎类,每一个这样的引擎类都有一个对应的SPI类,引擎类继承SPI类,引擎类负责被用户调用,引擎类调用SPI类,我们发现所有的SPI类都是abstract的,也就是说SPI类提供模板,其他Provider实现SPI类中的方法就可以了,这些对于用户来说都是透明的,用户只需要在java.security中进行配置就可以了,是可插拔的一种体现,后面将会对这些引擎类进行简单介绍。
对于某些provider,可能会提供加密算法的别名,官方文档不推荐使用别名这种行为,因为其他厂商不一定叫这个别名。
-
如果想知道自己可以使用那些Provider,Security的getProvider()方法可以获取到,获取到key有很多,笔者使用的是1.8版本,有600+条,如下面这段代码。
public static void main(String[] args) { for (Provider p : Security.getProviders()) { System.out.println(p); for (Map.Entry entry :p.entrySet()) { System.out.println("\t"+entry.getKey()); } } }
二、java.security包类库
- 类似MessageDigest这样的类,我们通常叫做引擎类,java.包中的引擎类都是为其继承的SPI类服务,这样的可插拔的机制方便了整体的扩展,下面将介绍java.security包下的引擎类。
1、MessageDigest类
-
可以使用MessageDigest生成指定加密算法的摘要,代码如下,首先根据MessageDigest的静态方法获取对应算法的实例,然后调用update()方法更新摘要,最后使用digest()方法生成摘要。
MessageDigest digest = MessageDigest.getInstance("SHA"); digest.update(input); byte[] output = digest.digest();
2、Key接口
-
Key是一个接口,是秘钥的抽象概念,所有与秘钥相关的类都要实现这个接口,通过查看源代码我们可以发现这个接口继承了Serializable接口,这是因为秘钥多数情况都是在系统间进行传输,这个接口很简单只有三个方法,如下:
public String getAlgorithm();//获取算法名称
public String getFormat();//获取秘钥格式化的编码格式
public byte[] getEncoded();//返回二进制格式的秘钥 有三个接口继承了它:分别是SecretKey、PublicKey、PrivateKey,SecretKey是对称秘钥的抽象,对称加密算法的秘钥由SecretKey提供,PublicKey和PrivateKey对应非对称件加密算法中的公钥和私钥。
3、KeyPair接口
- KeyPair是非对称加密算法秘钥对的抽象,如下图所示,这个类的构造方法参数只有两个,类型为PublicKey和PrivateKey,这个类是不提供公钥和私钥的setter方法的,只可以通过构造方法的方式去构造秘钥对。
4、KeyPairGenerator类
-
秘钥对的生成就是由KeyPairGenerator来完成的,这是一个引擎类,同样也继承了SPI类,通过工厂方法getInstance()来完成秘钥对的构建,下面通过调用KeyPairGenerator的getInstance()方法获得实例,初始化秘钥长度,然后获得秘钥对。
KeyPairGenerator generator = KeyPairGenerator.getInstance("DSA"); generator.initialize(1024); KeyPair keys = generator.generateKeyPair();
5、KeyFactory类
-
KeyFactory最大的作用就是根据指定的规范生成秘钥,如下面这段代码,使用的是PKCS8规范,这样的规范还有很多,如下图所示:
PKCS8EncodedKeySpec pkcs8KeySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory keyFactory = KeyFactory.getInstance("RSA"); Key privateKey = keyFactory.generatePrivate(pkcs8KeySpec);
6、Signature类
-
Signature类主要用于生成和验证数字签名,生成数字签名这一步我们通常称为“加签”,验证数字签名这一步,我们通常称为“验签”,首先我们根据KeyPairGenerator的工厂方法getInstance()获取实例秘钥对实例,初始化秘钥长度1024,生成秘钥对,根据指定算法构建Signature签名实例,根据私钥初始化,调用sign()方法完成加签,下面这一段代码展示了加签的过程:
byte[] data = "Signature Data".getBytes(); KeyPairGenerator generator = KeyPairGenerator.getInstance("DSA"); generator.initialize(1024); KeyPair keyPair = generator.generateKeyPair(); Signature signature = Signature.getInstance(generator.getAlgorithm()); signature.initSign(keyPair.getPrivate()); signature.update(data); byte[] signData = signature.sign();
-
验签过程和加签相似,使用公钥初始化,调用verify()方法完成验签,如下面代码所示:
signature.initVerify(keyPair.getPublic()); signature.update(data); boolean status = signature.verify(signData);
7、KeyStore类
-
KeyStore是秘钥库的抽象,用于管理秘钥和证书,这也是一个引擎类,如下代码,我们从本地加载秘钥库配合密码加载秘钥库,这里我们可以看到通过keyStore.aliases();获取了全部的别名列表,原因是keystore是通过别名去加载秘钥的,原理类似前面提到的Provider,获取到第一个别名返回。
String keyPassword = "123456"; inputStream = new FileInputStream(new File(keyPath)); KeyStore keyStore = KeyStore.getInstance(keyType); keyStore.load(inputStream, keyPassword.toCharArray()); Enumeration enum2 = keyStore.aliases(); String keyAlias = null; if (enum2 == null) { return null; } else { if (enum2.hasMoreElements()) { keyAlias = (String) enum2.nextElement(); } else { return null; } } PrivateKey priviteKey = keyStore.getKey(keyAlias, keyPassword.toCharArray());
参考:
1、oralce JCA参考指南:https://docs.oracle.com/javase/7/docs/technotes/guides/security/crypto/CryptoSpec.html#ProviderArch