【引言】
HTTPS是众所周知的,但要是问到"S"的含义是什么?也许能回答上来的人并不多。HTTPS其实是HTTP + SSL,S的含义也就是Secure Socket Layer(简称SSL)。前段时间看了些SSL文档的一些东西,所以本篇博客以SSL为主题,对相关知识做个总结。
【SSL】
一. SSL 协议
SSL是用于在web上实现加密最广泛使用的协议。SSL使用加密过程的组合提供了互联网通信安全。
SSL为用于Internet通信的标准TCP / IP通信协议提供安全增强。SSL添加在标准的TCP/IP协议中的传输层和应用层之间。HTTP是使用SSL协议最常见的应用,即Internet网页的协议。
其他应用程序,如网络新闻传输协议(NNTP),Telnet,轻量级目录访问协议(LDAP),交互式消息访问协议(IMAP)和文件传输协议(FTP),也可以与SSL一起使用。
二. 为什么使用SSL
就拿网上交易的场景来说,客户端需要进行一笔网上支付,会涉及一些类似身份证号码、银行卡号或信用卡号等私密信息,在使用HTTP请求的方式下,由于以下三个问题,在网络间传输一些敏感信息可能存在风险:
SSL解决了这些问题。
它通过可选地允许两个通信方中的每一方在认证的过程中确保另一方的身份来解决第一个问题。
一旦双方通过身份验证,SSL就会在双方之间提供加密连接,以实现安全的消息传输。双方通信是加密的,因此解决了第二个问题。
在SSL协议中使用的加密算法是hash函数,类似求和校验函数,这就保证了传输过程中数据不被修改,安全加密函数解决了第三个问题,保证了数据的完整性。
三. SSL使用场景
使用SSL协议最典型的例子是电子商务交易。在电子商务交易中,假使你能保证通信的服务器的身份,显然是无知的。如果只输入信用卡号码,那么有人可以很容易地创建一个假冒优质服务的虚假网站。SSL允许你,在客户端,去验证服务端身份,同样也允许服务端校验客户端,在互联网交易中,很少这么做。
一旦客户端和服务器对彼此的身份是合适的,SSL就会通过其使用的加密算法提供隐私和数据完整性。 这允许通过因特网安全地传输敏感信息,例如信用卡号。
虽然SSL提供身份验证,隐私和数据完整性,但它不提供不可否认服务。 不可否认意味着发送消息的实体以后不能否认他们发送消息。 当签名的数字等效物与消息相关联时,稍后可以证明该通信。 仅SSL不提供不可否认性。
四. SSL是如何工作
SSL影响深刻的原因之一在于使用了几种不同的加密过程。SSL使用公钥加密来提供身份验证,密钥加密和数字签名以提供隐私和数据完整性。
加密的主要目的在于让未经授权的第三方难以进入和理解双方的私密通信。然而拒绝所有未经授权的访问是不可能的,但是通过加密过程,可以使得未经授权的第三方不理解私密数据。加密是通过一些复杂的算法,将一些明文进行编码,转为密文。用于加密和解密通过网络传输的数据的算法通常分为两类:单密钥加密和公开密钥加密。
单密钥加密和公开密钥加密都取决于使用商定的加密密钥或密钥对。 密钥是在加密和解密数据的过程中由加密算法或算法使用的一串比特。加密密钥类似于锁的密钥:只有使用正确的密钥才能打开锁。
在两个通信方之间安全地传输密钥并非易事。 公钥证书允许一方安全地发送其公钥,同时确保接收者公钥的真实性。
- 单密钥加密
使用单密钥加密,通信双方对同一个密钥进行加密和解密。在通过网络发送任何数据前,双方都需要拥有密钥,并且必须就将用于加密和解密的加密算法达成一致。
单密钥使用的一个主要问题在于在不允许攻击者访问的情况下,一方如何从另一方获得密钥。假如通信双方使用单密钥加密方法保证他们的数据安全,而攻击者获得了他们的密钥,攻击者便可以理解通信双方的任意加密数据。不仅攻击者可以解密通信双方的消息,也可以假装为通信的某一方发送数据给对方,而对方并不知道该数据是由攻击者发送的。
一旦解决了密钥的分发问题,单密钥便可成为很有价值的工具。该算法提供了非常出色的安全性并且可以相对快速的加密数据,在SSL会话中发送的大多数敏感数据是使用密钥加密发送的。
单密钥加密也叫对称加密,因为双方是对同一个密钥进行加密和解密。常用的对称加密算法包括DES,3DES,RC2,RC4.
- 公开密钥加密
公开密钥加密通过拥有一个公钥和一个私钥解决了密钥的分发问题。公钥可以通过网络公开发送,而私钥由通信一方保密,公钥和私钥是彼此的加密反转,一个密钥加密,另一个密钥解密。
假设Bob使用公钥加密给Alice发送消息,Alice拥有一个公钥和一个私钥,她将自己的私钥保存在安全的地方,然后将公钥发送给Bob。Bob使用Alice的公钥将消息加密,而Alice使用自己的私钥解密。
假设Alice使用自己的私钥加密消息然后将加密后的消息发送给Bob,Bob便可以确定他收到的消息是来自Alice的;如果Bob可以使用Alice的公钥进行解密,那该消息一定是通过使用Alice的私钥进行的加密,并且只能是Alice的私钥。此问题在于任何人都能够阅读该消息,因为Alice的公钥是公开的。虽然这种情况不是安全通信,但他为数字签名提供了基础。数字签名是公钥证书的组件之一,并且使用在SSL给服务端和客户端授权。
公钥加密也称为非对称加密,因为加密和解密数据是使用不同的密钥。在SSL中,一个众所周知的公钥加密算法叫做RSA算法,另一个专门用来做密钥交换的公钥加密算法叫做DH算法。公钥加密需要一些额外的开销,速度上会有所减慢,因此,常被用来加密少量数据,如密钥,而不是大批量的数据。
- 单密钥加密与公开密钥加密的比较
两种加密方式都有各自的优缺点。在单密钥加密中,数据的加密和解密速度快,但由于通信双方共享相同的密钥信息,使得密钥的交换成为问题。在公开密钥加密中,密钥交换不是问题,因为公钥不需要保证私密,但是加密和解密数据算法需要更多的损耗,所以速度上会慢。
- 公钥证书
公钥证书为实体传递其公钥以在非对称加密中使用提供了一种安全的方式。 公钥证书避免了以下问题:如果攻击者创建了自己的公钥和私钥,则他可以声称自己是通信方Alice,并将他的公钥发送给通信方Bob。Bob可以与攻击者通信,但Bob会认为与自己的通信的是Alice。
公钥证书可以等同于护照,它由受信任组织颁发,并且为来者提供身份证明。颁发公钥证书的受信任组织结构称为证书颁发机构(CA)。CA就像是一个公证人,要从CA那获取证书,必须提供身份证明。一旦CA确信申请人代表其所代表的组织,CA就会签署证书,证明证书中包含信息的有效性。
一个公钥证书包含以下几个字段:
当Bob在公钥证书中接受Alice的有效公钥,那么当攻击者伪装Alice的情况下,Bob便不会被愚弄,从而发送加密消息给攻击者。
多个证书可被链接为一个证书链。当使用证书链时,第一个证书始终是发件人的证书,下一个是颁发发件人证书的实体的证书。若证书链中有多个证书,那么每个证书都是由前一个颁发证书所授权的,在证书链的最后一个证书便是由CA机构颁发的根证书。CA是广泛信任的公共证书颁发机构,多个根CA证书信息通常存储在客户端的浏览器中,其中包括CA的公钥,知名的CA包括VeriSign, Entrust, and GTE CyberTrust.
- 加密hash函数
当发送加密数据时,SSL是通过加密hash函数保证数据的完整性,此函数功能可以保证Alice向Bob发送的数据不被攻击者篡改。
加密hash函数类似于和校验函数,主要的区别在于和校验函数是用来防止数据被偶然替换,hash函数是用来防止数据被故意篡改。当用hash函数加密的数据传输过程中,生成一串字符,称为散列。对消息稍作修改,从hash的结果上看,也是一次很大的改动。加密hash函数中不需要密钥,常见的两种hash加密函数为MD5和SHA.
- 消息授权码
消息授权码类似于hash加密,只是它是基于密钥的基础上。当传输的数据中包括密钥信息,而且是由hash函数加密的,那么这个散列结果称为HMAC.
若Alice想要确定攻击者没有篡改她发送给Bob的消息,她可以计算出一个HMAC值,并将该值附加在原始消息后面,然后她可以使用和Bob共享的密钥加密消息和HMAC。当Bob解密消息并计算HMAC时,他可以判断出在数据传输过程中是否被篡改。使用SSL,可利用HMAC保证数据传输的安全。
- 数字签名
一旦消息使用hash函数加密,那么hash散列的值一定是通过包含发送方的私钥进行加密的,这个加密后得到的序列被称为数字签名。
五. SSL的工作流程
使用SSL的通信始于客户端和服务端之间的信息交换,这种信息交换称为SSL握手。
SSL握手的目的主要包含以下三点:
- 密码协商
SSL的会话始于客户端和服务端使用密码进行交涉,计算机可以使用包含加密算法和密钥大小的密码组加密数据。密码组包含公钥交换算法,密钥协商算法以及hash加密函数。客户端会告诉服务端哪一个是可用的,从而服务端会选择一个最佳的双方可接受的密码组。
- 服务端认证
在SSL中,认证的过程是可选的,但是在网上电子商务交易中,客户端是想要认证服务端的。服务端认证就能让客户端确定服务端所代表的身份,从而客户端能够信任该服务端。
为了能够认证服务端属于它宣称的组织机构身份,服务端会将公钥证书传给客户端,若该证书是有效的,则客户端便可以确定信任该服务端。
客户端与服务端的信息交换允许他们使用相同的密钥。例如,在RSA算法中,客户端使用从公钥证书中获得到的公钥,加密信息,并将加密后的信息传给服务端,只能通过服务端的私钥去解密该加密消息
- 发送加密数据
客户端和服务端可以有获得相同密钥的方法,在每一个消息中,他们会先选择使用hash加密函数加密消息,并且计算出一个HMAC散列追加在加密消息的后面,然后他们使用各自的密钥以及在第一步中协商好的密钥算法去解密该数据以及HMAC。因此,客户端和服务端可以通过各自的加密hash散列数据保证通信安全。
在SSL中,消息的传递按照以下顺序:
若在SSL会话中,参数被存储,这些参数是可以在SSL会话中重复使用的。保存SSL会话中的参数会使得加密通信更快一些。
六. 核心类
为了保证通信安全,双方都需要允许SSL连接。在JSSE API中,SSL连接最后的节点类是SSLSocket和SSLEngine.
一个SSL通信(SSLSocket)是由SSLSocketFactory或SSLServerSocket建立的,(依次地,SSLServerSocket是由SSLServerSocketFactory创建的)。SSLSocketFactory 和 SSLServerSocketFactory 对象是由SSLContext创建的。一个SSLEngine是直接由SSLContext创建的,并且依赖应用程序来处理所有IO.
在使用原生SSLSocket或SSLEngine的情况下,在发送数据前,你必须检查对等方的证书。SSLSocket和SSLEngine类中不会自动检测,比如,URL中的主机名是否匹配证书中的主机名。如果未验证主机名,则可以利用URL谎骗应用程序。
获得初始化SSLContext的方法有两种:
最简单的方法是直接调用SSLSocketFactory和SSLServerSocketFactory中的静态getDefault方法。这些方法中,会创建一个默认的SSLContext,并且包含默认的密钥管理(KeyManager)、信任库管理(TrustManger)以及生成一个安全的随机数。(KeyManagerFactory 和 TrustManagerFactory分别用来创建KeyManager和TrustManager)。使用的密钥材料位于默认的密钥库和信任库中,默认密钥和信任库,类型和密码也可在系统属性描述中自定义。
另一个给调用者更多控制去创建SSLContext的方法是调用SSLContext类中的getInstance()方法,然后通过调用实例的init()方法去初始化context。初始化方法包含三个参数:KeyManager对象数组,TrustManager对象数组以及生成安全随机数。KeyManager 和 TrustManager对象可通过实现适当的接口或者使用KeyManagerFactory 和 TrustManagerFactory创建实现。然后在KeyManagerFactory 和 TrustManagerFactory的初始化方法中传递一个参数包含KeyStore参数,其中包含密钥材料信息。最后,调用getKeyManagers()方法和getTrustManagers()方法可以获取到Trust和Key managers数组,分别对应信任库类型和密钥材料类型。
下面是使用第2种方法的伪代码:
//自己的 私钥
KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
KeyStore ks = KeyStore.getInstance("JKS");
ks.load(new FileInputStream(clientKs), "123456".toCharArray());
kmf.init(ks, "123456".toCharArray());
//对方的 公钥
TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
KeyStore tks = KeyStore.getInstance("JKS");
tks.load(new FileInputStream(serverKs), "123456".toCharArray());
tmf.init(tks);
SSLContext sslContext = SSLContext.getInstance("SSL", "SunJSSE");
sslContext.init(kmf.getKeyManagers(), tmf.getTrustManagers(), new java.security.SecureRandom());
SSLSocketFactory ssf = sslContext.getSocketFactory();
// 创建URL对象
URL myURL = new URL(url);
// 创建HttpsURLConnection对象,并设置其SSLSocketFactory对象
HttpsURLConnection httpsConn = (HttpsURLConnection) myURL.openConnection();
httpsConn.setSSLSocketFactory(ssf);
httpsConn.setRequestMethod("POST");
httpsConn.setRequestProperty("User-Agent", USER_AGENT);
httpsConn.setRequestProperty("Accept-Language", "UTF-8");
httpsConn.setDoOutput(true);
OutputStreamWriter outputStreamWriter = new OutputStreamWriter(httpsConn.getOutputStream());
outputStreamWriter.write(strContent);
outputStreamWriter.flush();
outputStreamWriter.close();
一旦建立SSL连接,就会创建一个SSL会话,包含各种信息,例如已建立的身份,使用的密码等等。然后SSLSession用来描述两个身份实体间的持续关系和状态信息。每个SSL连接涉及一个SSL会话,但是该会话可以同时或顺序地用在这些实体之间的多个连接。
- 核心类和接口
JSSE的核心类包含在javax.net和javax.net.ssl包中。
SocketFactory抽象类用来创建通信,它必须由其他工厂子类化,这些工厂创建特定的通信子类,从而为公共通信提供一个通用框架。
ServerSocketFactory和SocketFactory类似,但是他专门用来创建服务端通信。
SSLSocketFactory被作为一个工厂用于创建安全通信,是SocketFactory的子类。安全通信工厂封装了创建和初始配置安全通信的详细信息,其中包括认证密钥,对等方有效证书和启用的密码。
SSLServerSocketFactory和SSLSocketFactory类似,但他专门用来创建服务端通信。
主要有三种方法可以获得一个SSLSocketFactory:
默认工厂通常配置为仅支持服务端认证,这样不会泄露更多的客户端信息。
许多建立通信的类不需要关注通信的创建细节,通过参数传递该通信工厂是一种较好的方法,隔离了创建通信的细节,增加了可重用性。
也可以创建一个新的通信工厂,通过实现一个自己的通信工厂实例,或者通过其他类成为一个通信工厂,一个具体实例就是SSLContext,在JSSE中作为一个基础全局类提供。
SSLSocket类是标准的Socket类下的一个子类,他支持所有标准的通信方法,并且额外增加了安全通信的一些方法。该实例类封装了创建的SSLContext。
SSLServerSocket和SSLSocket类相似,只是用来创建服务端的通信。
为了防止对方的欺骗,你可以在SSLSocket中验证证书。
获得一个SSLSocket有两种方法:
https协议与http类似,只是https通信是建立在SSL/TLS协议上,然后在接收和传递数据前,都需要验证对方身份。net.ssl.HttpsURLConnection继承net.HttpsURLConnection类,并且添加了一些具体特征。、
在某些情况下,需要指定具体使用的SSLSocketFactory。
当HttpsURLConnection该类被加载时,会分配一个默认的SSLSocketFactory。未来实例将会默认继承当前的SSLSocketFactory,直到通过静态方法HttpsURLConnection.setDefaultSSLSocketFactory将新的默认SSLSocketFactory分配给该类。一旦该连接实例被创建,可以通过调用setSSLSocketFactory()方法将实例继承的SSLSocketFactory覆盖。
需要注意的是改变现有的默认静态SSLSocketFactory对当前连接实例是不起作用的,当需要改变现有实例时,必须调用setSSLSocketFactory()方法。
如果请求的url中的主机名与在SSL/TLS握手协议中接收到的证书的主机名不一致,那么该URL可能会发生谎骗行为。如果因为某些合理原因,不能实现主机名的匹配,SSL中可以使用回调进行进一步验证。主机名的验证可以在任何步骤确定,例如执行备用主机名匹配模式或者弹出交互框。主机名验证不成功将会关闭连接。
七. 相关术语与定义
认证
认证是一个确认各方身份的过程。
密码序列
密码序列是加密参数的组合,用于定义认证,密钥协商,加密和完整性保护的加密算法和密钥大小。
证书
证书是一种数字签名的声明,用于担保实体(个人,公司等)的身份和公钥。 证书可以是自签名的,也可以由证书颁发机构(CA)颁发。证书颁发机构是受信任为其他实体颁发有效证书的实体。 知名的CA包括VeriSign,Entrust和GTE CyberTrust。 X509是一种通用的证书格式,可以通过JDK的keytool进行管理。
加密hash函数
加密哈希函数类似于校验和。 使用算法处理数据,该算法产生相对较小的位串,称为散列。 加密散列函数有三个主要特征:它是单向函数,这意味着不可能从散列中生成原始数据; 原始数据的微小变化会在结果散列中产生很大的变化; 它不需要加密密钥。
加密服务提供商
在JCA中,各种加密算法的实现由加密服务提供者或简称“提供者”提供。 提供者本质上是为特定算法实现一个或多个引擎类的包。 引擎类以抽象方式定义加密服务,而无需具体实现。
数字签名
数字签名等同于手写签名。被用来确认在网络传输过程中,数据是由谁传递的,并且在传输过程中不被修改。例如,基于RSA的数据签名,首先会将数据进行hash加密计算,然后将加密的散列值作为发送者的私钥。
加密和解密
加密是一个利用复杂算法将原始消息或文本转换为加密文本的过程,除非被解密,否则是不能理解的。解密是将加密文本转换为明文的过程。用于加密和解密的通用算法包括两种:对称加密和非对称加密。
握手协议
双方同意使用现有会话或新会话的协商阶段。握手协议是通过记录协议交换的一系列消息,在握手结束时,基于会话中的密钥协商秘密生成新的特定于连接的加密和完整性保护密钥。
密钥协商
密钥协商是一种通过双方合作来建立一个共同的密钥的方式,每一方都会生成一些数据作为交换,将双方的数据结合生成一个通用密钥。只有当持有一些适当的私有的初始化数据才能获得最终的密钥。DH是一种通用的密钥协商算法。
密钥交换
一方生成对称密钥并使用对方的公钥(通常为RSA)对其进行加密。 然后将数据传输到对方,对方使用其对应的私钥解密密钥。
密钥管理与信任库管理
密钥管理与信任库管理是使用密钥数据库进行密钥资料的管理。一个密钥管理对应一个密钥数据库,在需要的时候,给双方提供公钥,例如,双方需要互相认证。一个信任库管理是建立在信息的基础上决定授信给谁。
密钥库与信任库
一个密钥库是密钥资料的数据库,密钥资料有多种用途,如授权或保证数据完整性。现有多种类型的密钥库可供使用,如PKCS12和Sun公司的JKS.
通常来说,密钥库信息可以分为两个不同的类别:密钥条目和可信证书条目。一个密钥条目由身份实体和他自己的私钥组成,可用于各种加密目的。相反地,除了实体身份信息,一个可信证书条目只包含一个公钥。所以,当需要私钥的时候,可信证书条目并不能被使用,例如在javax.net.ssl.KeyManager中。在JDK实现的JKS中,一个密钥库密钥条目和可信证书条目都包含。
一个信任库是一个密钥库,用来决定是否可信任。如果你收到一些你已授信的身份实体的数据,或者你可以验证该实体是它声称的实体,那么你可以假设数据确实来自该实体。
若用户确定信任该身份实体,则一个条目只能被加入一个信任库。通过生成密钥对或通过导入证书,用户已经信任该条目,因此密钥库中的任何条目都被视为可信条目。
拥有两个不同的密钥库文件,一个是密钥条目,另一个是可信证书条目,包括CA证书。前者包含一些私密信息,而后者不包括。使用两种不同的文件而不是一个单独的密钥库文件,是为了将你的证书和别人的证书区分开来。如果将私钥存储在具有受限访问权限的密钥库中,则可以为私钥提供更多保护,同时在需要时可在更公开的密钥库中提供受信任证书。
消息认证码
消息认证码在密钥的基础上,提供了一种来检查的进行传输或存储在一个不可靠的介质中的信息的完整性。通常,在共享密钥的两方之间使用MAC以验证在这些方之间传输的信息。
基于加密散列函数的MAC机制被称为HMAC。 HMAC可以与任何加密散列函数一起使用,例如消息摘要5(MD5)和安全散列算法(SHA),以及秘密共享密钥。
公钥加密
公钥加密使用加密算法,其中生成两个密钥。 一个密钥被公开,而另一个密钥被保密。 公钥和私钥是加密反转; 只有一个密钥才能加密其他密钥才能解密。 公钥加密也称为非对称加密。
记录协议
记录协议将应用程序级别或作为握手过程的一部分的所有数据打包成离散的数据记录,就像TCP流将应用程序字节流转换为网络数据包一样。 然后,各个记录受当前加密和完整性保护密钥的保护。
会话
会话是状态信息的命名集合,包括经过身份验证的对等身份,密码序列和密钥协商机密,这些机密通过安全握手协商并且可以在多个安全实例之间共享。
【总结】
在没有遇到问题前,看多少遍文档都是一个感觉,似懂非懂;当遇到问题,想要从文档中查找答案,却不知从何处着手,还是需要站在巨人的肩膀上,遇到同样的问题是如何解决的。回过头,再看文档,才有了突然明白的感觉。文档看多少遍,都不如解决一个问题实际。解决了问题,再看看文档会有更大的收获。