了解JCA类后,可以考虑如何组合这些类来实现像SSL / TLS这样的高级网络协议。 “JSSE参考指南”中的“SSL / TLS概述”部分从高层次描述了协议的工作原理。 由于不对称(公钥)密码操作比对称操作(**)慢得多,所以使用公钥密码术来建立**,然后用它来保护实际的应用数据。 非常简单,SSL / TLS握手包括交换初始化数据,执行一些公钥操作以获得**,然后使用该**来加密进一步的通信量。
注意:这里提供的细节只是简单地展示了如何使用上面的一些类。 本部分不会提供足够的信息来构建SSL / TLS实施。 有关更多详细信息,请参阅“JSSE参考指南”和RFC 2246:“TLS协议”。
假设这个SSL / TLS实现将作为JSSE提供者提供。 首先编写Provider类的具体实现,最终将在Security类的提供者列表中注册。 这个提供者主要提供从算法名称到实际实现类的映射。 (即:“SSLContext.TLS” - >“com.foo.TLSImpl”)当应用程序请求一个“TLS”实例(通过SSLContext.getInstance(“TLS”))时,将根据请求的算法查询提供者的列表, 创建一个适当的实例。
在讨论实际握手的细节之前,需要快速回顾一些JSSE的体系结构。 JSSE体系结构的核心是SSLContext。 上下文最终创建实际实现SSL / TLS协议的结束对象(SSLSocket和SSLEngine)。 SSLContexts使用两个回调类KeyManager和TrustManager进行初始化,它们允许应用程序首先选择要发送的验证资料,然后再验证对等方发送的凭证。
JSSE KeyManager负责选择向对等体显示哪些凭据。 许多算法是可能的,但是常见的策略是在由磁盘文件支持的KeyStore中维护RSA或DSA公钥/私钥对以及X509Certificate。 当从文件初始化并加载KeyStore对象时,文件的原始字节将使用KeyFactory转换为PublicKey和PrivateKey对象,并使用CertificateFactory转换证书链的字节。 当需要证书时,KeyManager简单地参考这个KeyStore对象,并确定出现哪些证书。
KeyStore的内容最初可能是使用keytool等实用程序创建的。 keytool创建一个RSA或DSA KeyPairGenerator,并使用适当的**大小进行初始化。 然后使用这个生成器来创建一个KeyPair,keytool将把这个新创建的证书和最终写入磁盘的KeyStore一起存储起来。
JSSE TrustManager负责验证从对等端收到的凭证。 验证凭证有多种方式:其中之一是创建CertPath对象,并让JDK的内置公钥基础结构(PKI)框架处理验证。 在内部,CertPath实现可能会创建一个Signature对象,并使用它来验证证书链中的每个签名。
有了这个架构的基本理解,我们可以看一下SSL / TLS握手中的一些步骤。 客户端首先发送一个ClientHello消息给服务器。 服务器选择要使用的密码组,然后将其发回到ServerHello消息中,并根据套件选择开始创建JCA对象。 我们将在下面的例子中使用服务器唯一身份验证。
在第一个示例中,服务器尝试使用基于RSA的密码组,例如TLS_RSA_WITH_AES_128_CBC_SHA。 查询服务器的KeyManager,并返回相应的RSA条目。 服务器的凭证(即:证书/公钥)将在服务器的证书消息中发送。 客户端的TrustManager验证服务器的证书,如果接受,客户端使用SecureRandom对象生成一些随机字节。 然后使用已使用在服务器证书中找到的PublicKey初始化的加密非对称RSA密码对象对其进行加密。 此加密数据在客户端**交换消息中发送。 服务器将使用其相应的PrivateKey在解密模式下使用类似的密码恢复字节。 这些字节然后用于建立实际的加***。
一旦建立了真实的加***,秘***就被用来初始化一个对称的密码对象,并且这个密码被用来保护所有传输中的数据。 为了帮助确定数据是否已被修改,创建MessageDigest并接收发往网络的数据的副本。 当数据包完成时,摘要(哈希)被附加到数据,并且整个数据包被密码加密。 如果使用诸如AES的分组密码,则必须填充数据以形成完整的块。 在另一端,这些步骤简单地颠倒过来。
再一次,这是非常简单的,但给了一个想法,这些类可能被结合起来创建一个更高层次的协议。