查看文章 |
JCA形成了安全API的核心。在设计之初,有2个重要的原则。
第一、JCA是实现无关的且可互操作的。实现无关是通过加密服务提供者(cryptography service providers)来实现的。 一个provider实现了一种加密服务(例如生成随机数字或者创建一个电子签名)。 互操作性保证不同的provider可以一起工作。例如,不同的provider使用相同的算法,被一个provider加密可以被另外一个provider解密。
第二、JCA是算法无关性和可扩展性的。 算法无关性是通过引擎类(engine classes:提供特定的加密服务,如密钥生成器或者信息摘要服务)的规范来实现的。 算法可扩展性确保这些引擎类可以很容易的用新的算法更新。
JDK包含一些默认的加密服务提供者,在名称为SUN的包底下,有如下provider: ❑ Implementation of DSA (Digital Signature Algorithm) ❑ Implementation of MD5 and SHA-1 message digest algorithms ❑ Key pair generator to generate public and private key pairs for the DSA algorithm ❑ DSA algorithm parameter generator ❑ DSA algorithm parameter manager ❑ DSA key factory that supports converting public keys to and from private keys ❑ SHA1PRNG pseudo-random number generator ❑ X.509 Certificate path builder and validator for PKIX ❑ A certificate store using the PKIX LDAP V2 Schema ❑ Certificate factory for X.509 certificates and Certificate Revocation Lists (CRLs) ❑ A keystore
所有的provider将会做详细的讨论。所有的例子都是使用SUN包的默认的provider的实现。如果你使用第三方的provider包,请参考第三方文档。
引擎类(Engine Classes)
一个引擎类提供一个特定加密服务的接口。这个接口规定程序员如何使用特定的服务。一个特定的引擎类可以有许多不同的实现,例如电子签名的实现使用SHA-1或者MD5算法。 每一个引擎类都有对应的服务提供接口(SPI),SPI是一个被引擎类封装的抽象类。 作为引擎类,SPI类必须被继承。每一个引擎类都有一个工厂类可以用来创建引擎类的实例,使用getInstance工厂方法。 JDK定义了12个引擎类,有3个(certificate path classes 和the certificate store)是在jdk1.4引入的。 这些引擎类的描述如下:
SPI类的命名规则是将Spi加到engine class名称后面,例如SecureRadom 的SPI是SecureRadomSpi。 每一个engine class都有一个getInstance方法用来请求一个特定的算法并请求一个特定的provider。 安装一个不同的provider包 通过放置JAR文件到你的classpath或者部署JAR文件作为你JRE的扩展。Provider一定要放置在java.security文件的列表里。这个文件可以再你JDK或者JRE安装目录的lib/security目录里找到。文件里的属性以以下的形式出现: security.provider.n=masterClassName “n”用数字替代,例如1或2。当没有特定provider的时候,会从上向下搜索获得engine class的getInstance方法。 “masterClassName ”用provider包里的有资格的类全名去代替。
JDK 6.0文件里指定provider如下: security.provider.1=sun.security.provider.Sun security.provider.2=sun.security.rsa.SunRsaSign security.provider.3=com.sun.net.ssl.internal.ssl.Provider security.provider.4=com.sun.crypto.provider.SunJCE security.provider.5=sun.security.jgss.SunProvider security.provider.6=com.sun.security.sasl.Provider security.provider.7=org.jcp.xml.dsig.internal.dom.XMLDSigRI security.provider.8=sun.security.smartcardio.SunPCSC security.provider.9=sun.security.mscapi.SunMSCAPI
下一步,对每一个engine class进行讲解。例子的实现使用的SUN提供的包。
Calculating and Verifying Message Digests(计算和验证信息摘要)
MessageDigest引擎类接受随意长度的byte数组作为输入并计算出定长的hash值,被叫做消息摘要。这是一个单向的操作。有一个消息摘要不能得到原始的输入信息。如果这样可以的话,这个世界上肯定有一个最好的压缩算法存在。因此,你可以将消息摘要看做数据的“指纹”,因为每一个输入集成为唯一的hash值。
现在看一看工厂建立方法。所有的引擎类都是这样创建,所以在这里详细描述。每一个引擎类都有三个静态方法:
static [engine class name] getInstance(String algorithm) static [engine class name] getInstance(String algorithm,String provider) static [engine class name] getInstance(String algorithm,Provider provider)
后2种形式的getInstance方法可以指定特定的provider。最后一种允许你传入一个provider的实例,第二种形式允许你使用provider的名字。 所有的字符串,包括算法都是大小写敏感的。[engine class name] 引擎类的类名代替。
SUN提供的包里有2种消息摘要算法:MD5和SHA-1。 MD5算法接受参数并生成128位的消息摘要。SHA-1是Secure Hash Algorithm的缩写,提供160位的消息摘要。
在调用getInstance工厂方法之后,一个已初始化的MessageDigest 就存在了。下一步是提供带有输入的MessageDigest并计算出消息摘要。有三个方法传递输入数据到MessageDigest: void update(byte input) void update(byte[] input) void update(byte[] input, int offset, int len) 第一种形式接受简单的字节输入。第二种接受字节数组,最后一种接受字节数组,并允许计算子数组的消息摘要(开始位置offset,输入的大小是len)。
有三种方法计算消息摘要,返回字节数组。 byte[] digest() byte[] digest(byte[] input) int digest(byte[] buf, int offset, int len)
第一个digest方法基于update方法中已输入参数来计算消息摘要。第二个方法基于输入参数到方法中并返回消息摘要。第三种基于基于update方法然后存储消息摘要到buf字节数组并传递作为参数。Len参数指定消息摘要的可用最大长度。Offset指定在数组里,消息摘要从哪里开始返回值是buf里存的字节的个数。
你可以使用MessageDigest引擎类来保证数据的完整性。假设你在编写一个全局性的数据安全性和完整性的系统组件。你要确认数据没有没更改过。一种方法就是:在传输的时候,存储这些和敏感数据对应的消息摘要。这些信息摘要值被存在基础系统里,然后每一个数据在到达目的地后,这些信息摘要可被重新计算。可以开发一个组件从基础系统里查找消息摘要并和新计算的信息摘要做比较。下面这个例子就是计算消息摘要并和已经查找到的消息摘要比较:
import java.security.MessageDigest; public class MessageDigestExample {
输出入下: --- Message Digest --- -97 103 -17 -58 -81 -87 95 26 -17 -101 51 81 -42 -80 29 126 5 -111 -73 72 这个数字数组可以重新计算并和接收端做比较来保证传递是否完整。
Digital Signing and Verification of Data(数字签名和数据验证) 使用密钥来实现电子签名数据,使用公钥来实现签名是否被验证完成。这能保证数据来源自特定的人,这个人使用密钥来签名,就像信用卡发票签名一样。密钥用来签名一组字节,并生成一个短的、定长的签名。签名可以通过公钥验证。这个过程在下图中显示。 这里主要是从编程的角度来看DSA算法。实际上,DSA算法与信息摘要算法MD5或者SHA-1一起使用。信息摘要和密钥一起作为DSA算法的输入。另一方面,数据被再一次编码成信息摘要,同时和公钥作为DSA的输入来验证数据的完整性。
和信息摘要一样,电子签名算法也有2个重要的原则。 第一个原则是和密钥对应的公钥可以用来验证数据的完整性。 第二个原则是数字签名和公钥不暴露任何关于密钥的东西。 实际的签名对象可以有三种状态。如下:
签名状态 描述 UNINITIALIZED 假定是创立之后立即的状态 SIGN 意味着对象正在为签名初始化。在initSign之后设置。 VERIFY 意味着对象已被初始化正在为验证签名。在initVerify之后设置
SUN提供的DSA算法的实现是Digital Signature Standard (DSS)的一部分。SHA-1和MD5都能用于DSA算法。引擎类的值是显而易见的。所以将消息摘要功能和数字签名功能结合是很简单的。和MessageDigest引擎类一样,Signature引擎类也有三个相同的getInstance方法。 一个Signature类必须被初始化,然后使用下面方法来准备数字签名数据: final void initSign(PrivateKey privateKey) 在这个方法被调用之后,Signature类的状态是SIGN。下一步将数据发送到Signature对象并实际上对数据进行签名。通过update和sign方法实现:
final void update(byte b) final void update(byte[] data) final void update(byte[] data, int offset, int len)
////////////////////////////////////////////////////////////////////// final byte[] sign() final int sign(byte[] outbuf, int offset, int len) 当sign方法返回之后,Signature对象留在SIGN状态并且有了密钥,如果想有不同的密钥调用initSign方法。 另外一个操作,Signature引擎类可以支持验证数据。Signature对象必须使用initVerify来验证数据: final void initVerify(PublicKey publicKey) final void initVerify(Certificate certificate)
Public key 和 certificate 都能验证数字签名。在initVerify调用后,Signature为VERIFY状态。 Update方法用来发送数据到Signature对象来做验证。用法和发送数据来签名相同。 验证的方法被调用,然后决定数据的签名和公钥与密钥是否匹配:
final boolean verify(byte[] signature) final boolean verify(byte[] signature, int offset, int length)
verify返回后,Signature对象留在了VERIFY状态。如果想使用不同的公钥调用initVerify方法。
一个经常使用公钥和密钥的方式是:签名然后验证交流的源头。 例如:假定你为一个政府承包商工作,任务是构建一个安全交流系统,本质上是安全的电子邮件系统。安全的电子邮件的客户端一定要有数字签名信息发出并且有验证到达的信息的能力。产生和管理key的细节后面讨论。假设key都存在。你将开发一个有签名和验证的安全沟通: |

