由于项目需求,网关需要和多个不同的平台进行tls加密通讯,在不同的开发平台使用tls方式是不同的,鄙人也爬过很多坑,查过很多资料,记录了在安卓java,ios,c++各个平台的生成方式和使用方式,错误问题的解决。等等,详情可以下载word文档,下面没法显示详细的图文数据,格式有点乱。
word文档下载:http://download.csdn.net/detail/rushroom/9088237
openssl技术原理和各平台解决方案
By:JasonsAlex(QQ531401335)
Date:15/09/07
Company:Vargo Tech.
System:CentOS6.6
注:文中的提到的cer和crt为同一格式,只是命名不同
一.openssl技术原理
你要是对 SSL 或 X.509 证书一无所知, 那我大概解释下. 对于那些打算用自签名证书(self-signed certificate)的人来说, 需要了解自签名证书跟花钱购买机构颁发的证书有什么区别.
首先我们需要了解下 SSL 证书究竟是个什么东东? 其实它就包含俩部分: 1) 一个身份标识, 一个用来识别身份的东西, 有点类似警察叔叔通过护照或驾照查你的身份; 2) 一个公共密钥, 这个用来给数据加密, 而且只有证书的持有者才能解密. 简而言之, SSL 证书就俩个功能, 身份验证跟保障通讯过程中的数据安全.
另外还有一点很重要. 那就是一个证书可以给另外一个证书“签字”. 用 layman 的话说就是 Bob 用他自己的证书在别的证书上盖上 “同意” 两个红红的大字. 如果你信任 Bob (当然还有他的证书), 那么你也可以信任由他签发的证书. 在这个例子中, Bob 摇身一变, 成了证书颁发机构(Certificate Authority). 现在主流的浏览器都自带一大堆受信任证书颁发机构(trusted Certificate Authorities)(比如:Thawte, Verisign等).
最后我们讲一讲浏览器是怎么使用证书的. 笼统的讲, 当你打开下列连接的时候 “https://www.yoursite.com” :
服务器会给浏览器发一个证书.
浏览器会对比证书中的“common name”(有时也叫 “subject”) 跟服务器的域名是否一样. 例如, 一个从“www.yoursite.com” 网站发过来的证书就应该有一个内容是 “www.yoursite.com” 的 common name, 否则浏览器就会提示该证书有问题.
浏览器验证证书真伪, 有点像门卫通过证件上的全息图辨别你的证件是不是真的. 既然在现实生活中有人伪造别人的身份. 那么在网络世界也就有人造假, 比如用你的域名“www.yoursite.com” 来伪造一个安全证书. 浏览器在验证的时候, 会检查这个证书是否是它信任机构颁发的, 如果不是, 那么浏览器就会提示这个证书可能有问题. 当然, 用户可以选择无视警告, 继续使用.
一旦证书通过验证 (或是用户无视警告, 继续使用有问题的证书), 浏览器就开始利用证书中的公开密钥加密数据并传给服务器.
(一).数字签名是什么?
1.鲍勃有两把钥匙,一把是公钥,另一把是私钥。
2.鲍勃把公钥送给他的朋友们----帕蒂、道格、苏珊----每人一把。
3.苏珊给鲍勃写信,写完后用鲍勃的公钥加密,达到保密的效果。
4.鲍勃收信后,用私钥解密,看到信件内容。
5.鲍勃给苏珊回信,写完后用Hash函数,生成信件的摘要(digest)。
6.然后,鲍勃使用私钥,对这个摘要加密,生成"数字签名"(signature)。
7.鲍勃将这个签名,附在信件下面,一起发给苏珊。
8.苏珊收信后,取下数字签名,用鲍勃的公钥解密,得到信件的摘要。由此证明,这封信确实是鲍勃发出的。
9.苏珊再对信件本身使用Hash函数,将得到的结果,与上一步得到的摘要进行对比。如果两者一致,就证明这封信未被修改过。
10.复杂的情况出现了。道格想欺骗苏珊,他偷偷使用了苏珊的电脑,用自己的公钥换走了鲍勃的公钥。因此,他就可以冒充鲍勃,写信给苏珊。
11.苏珊发现,自己无法确定公钥是否真的属于鲍勃。她想到了一个办法,要求鲍勃去找"证书中心"(certificate authority,简称CA),为公钥做认证。证书中心用自己的私钥,对鲍勃的公钥和一些相关信息一起加密,生成"数字证书 "(Digital Certificate)。
12.鲍勃拿到数字证书以后,就可以放心了。以后再给苏珊写信,只要在签名的同时,再附上数字证书就行了。
13.苏珊收信后,用CA的公钥解开数字证书,就可以拿到鲍勃真实的公钥了,然后就能证明"数字签名"是否真的是鲍勃签的。
(二).非对称加密算法如RSA的特点如下:
1, 公钥加密私钥解密, 大家都可以用我的公钥给我发加密的数据了, 因为只有我有私钥才能解密.
2, 私钥加密公钥解密叫数字签名(例如所谓的UEFI secure boot就是在主板硬件里集成一些操作系统的公钥,由主板硬件去校验操作系统是法合法,但关键是微软把持了公钥的申请,主板硬件厂商没有提供界面让用法自定义公钥,尤其在移动领域很多win8的硬件根本不提供关闭secure boot的选项这样就造成只能安装win8一种系统), 大家收到我用私钥加密后的数据, 看用公钥能不能打得开, 能打开说明这数据确实是由我所发的, 因为别人没有我的私钥不可能伪造这些数据.
非对称加密去处很费时间, 我们一般采用对称密钥算法如DES来加密, 但对称密钥的保存是一个问题.
所以我们可以采用非对称加密算法来加密先协商交换对称密钥, 这就叫SSL. 假设客户端A的公私钥对是(P1,V1), 服务端B的公私钥对是(P2,V2), A需要确认和它通信的是B, 那么SSL的过程是:
首先, A和B都持有对方的公钥.
step1, A->B: hello 是step2, B->A: 用V2加密过的P1(即用户证书,A就用P2解密出P1, 这种数字签名方式让A确定了和它通信的是B)
step3, A->B: ok
step4, B->A: 用V1加密的一段信息
step5, A->B: 用P1加密一个自动生成的对称密钥K(用之前的P1解密成功这段信息则认为B是可信的了)
step6, B->A: 用K加密的数据(之后两对密钥功能结束,由K来加解密数据)
总结一下, 这里(P2,V2)就是certificate authority (CA)用来给客户签名用的公私钥。
(P1,V1)是客户自己的公私钥,提交给CA,CA所做的事情就是上述step2用(P2,V2)来给客户的(P1,V1)签名,简单吧?
V2是CA公司要保密的,而P2就是公用CA证书;
用V2加密过(签名过)的P1,称为用户证书,一般被安装在服务器端。
X.509证书是一些标准字段的集合, 是包含有关用户或设备及其相应公钥信息的一种非常通用的证书格式, 目前版本是3. 必要字段包括:
1, 版本号
2, 由CA给每一个证书分配的序列号;
3, 证书使用的签名算法
4, 证书的认证机构
5, 证书的有效日期
6, 证书的所有人的唯一标识
7, 认证机构使用私钥的数字签名
8, 公钥信息
不同于PGP证书任何人都可以扮演认证者的角色, X.509证书的认证者只能是CA或由CA指定的人.要获得一份X.509证书,必须请求CA发给你证书。用户提供自己的公钥,证明自己拥有相应的私钥,并提供有关自己的某些特定信息。然后在这些信息上数字签名,并将整个数据包(称为证书请求)发给CA。CA做一些努力来验证用户提供的信息是正确的,然后就生成证书并返回给用户。
OpenSSL对X.509的支持如下:
(1) 证书请求管理
(2) 证书生成
(3) 证书吊销及CRL管理
(4) X509名字管理
(5) 属性管理
(6) 扩展管理
(7) 验证及信任管理
(三).握手过程:
为了了解SSL通讯可以参考文档中的SSL通讯过程,主要包括握手,对话,关闭对话,三个步骤。其中握手部分的主要内容有协商协议,相互验证,生成并交换对称密钥,其中相互验证和对称密钥的交换是由非对称加密来完成的。在对话过程中,实际的明文是由之前生成的对称密钥来加密的。当对话结束后,互相发送结束信号结束通讯。
在上述过程中不仅仅是通讯双方简单的交换数据,更重要的是要根据SSL协议的要求,在特定的状态下发送或接受特定的数据,并且这些数据是经过处理的数据,也就是在tcp头和通讯正文之间还要包括一些ssl的信息,并且正文是由特定形式加密的。SSLEngine正是完成了管理状态,封装应用程序数据发往网络,解析网络数据并传递给应用程序的角色。
先看看SSLEngine有哪些状态,有哪些工作要做,假设从一个ssl server看。首先在握手阶段,需要和client程序多次握手,进行身份验证,对称密钥生成等工作,在这段时间里,并没有实际的应用层数据交换,而只有SSL协议数据的交换。且不看实际传输的内容和意义,在握手过程中,也就是上图中step1 到step13。Server的SSLEngine初始化后总是等待client的请求(等待接收数据),此时它的状态是NEED_UNWRAP,unwrap是解包的意思,这意味着,SSLEngine等待解析一个SSL的数据包,当server收到数据包后,在nio中数据包总是放在一个buffer里而不再是读stream,我们把这个buffer交给SSLEngine,调用它的unwrap方法,SSLEngine会解析这个数据包,把其中关于SSL握手的信息提取出来,并改变自己的状态,此处它将变成NEED_WRAP状态,意味着打包,它需要把对应的SSL回复内容写到数据包中返回到客户端,也就是step2-6中所作的事情。以此类推,SSLEngine多数时间总是在解包和打包两个状态间切换,尤其是在实际通讯时,注意到在unwrap和wrap函数中都有一个源buffer和一个目的buffer,因为SSLEngine不仅提取SSL协议相关的内容还要解密网络数据并把明文传递给应用程序,这其实才是这两个函数名字的来源,只不过在握手过程中,并没有实际的数据,而只有SSL协议信息,所以那个目的buffer总是没有东西。可以把SSL通讯看做交换礼物,SSLEngine把包裹拆了把礼物给你,或者他把礼物包起来送走,只是在SSL握手时,那个包裹里没有礼物,SSLEngine只是拆了个空包裹或是寄了个空包裹。那么还有没有其它状态,有一个FINISHED 状态那是在server端处于step13时所处的状态,表示这次handshake完成了;而当进入实际交换数据的时候,也就是step14的状态,这个状态是NOT_HANDSHAKE,表示当前不在握手,一般这个时候只需要在socket可读时,调用unwrap函数解密来自网络的SSL数据包,在socket可写的时候调用wrap函数把明文数据加密发送出去。还有一个状态NEED_TASK,首先要知道一点SSLEngine是异步的,wrap和unwrap函数调用都会立刻返回,比如在server收到client第一次请求后,会调用unwrap,但实际上SSLEngine还会做很多工作,比如访问Keystore文件,这些操作是费时的,但是实际上函数却立刻返回了,这时候SSLEngine会进入NEED_TASK状态,而不是立刻进入NEED_WRAP状态,所以必须让SSLEngine完成手头的工作,才能进入下一步工作,这时可以调用SSLEngine的getDelegatedTask()方法获得那个尚未完成的工作,它是一个Runnable的对象,可以调用它的run方法等待他完成,如果你是个高并发的server,也可以在这个时候做其他事情,等待这个工作完成,再接下去做wrap工作。另外还有一个非常容易出错的地方,一个NEED_UNWRAP状态的下一个状态然有可能是NEED_UNWRAP,并且一次调用unwrap方法并不一定把buffer中的所有内容都解包出来,可能还有内容需要在一次unwrap才能把所有东西都解析完,我遇到的这种情况发生在用nio的server和老的SSLSocket通讯时,在step7-11的过程中,client只向server一次性发送了这些数据,而server端需要连续两次unwrap才能把client的数据完整处理掉。
除了上述4个状态描述了SSLEngine的状态,还有4个状态用于描述每次调用wrap和unwrap后的结果状态。它们分别是BUFFER_OVERFLOW表示目标buffer没有足够的空间来存放解包的内容,这往往是因为你的目的buffer太小,或者在buffer在写入前没有clear;BUFFER_UNDERFLOW表示源buffer没足够内容让SSLEngine来解包,这往往是因为,可能还有数据尚未到达,或者在buffer读取前没有flip;CLOSED表示通讯的某一段正试图结束这个SSL通讯;OK,你懂的。
了解了SSLEngine的状态以及wrap和unwrap的原理,完成一个基于NIO的SSLsocket也就不会没想法了。
首先NIO的socket基本都通过Selector来实现,把socket 的accept,read,write事件都注册到selector上,不断的循环select()就可以,只是对于一个SSL Server Socket而言,它只是个普通的ServerSocket,首先只关心accept事件,所以首先这在selector上注册一个事件。
当serversocket接收到一个SSL client的请求后,就要开始进行握手,这个过程是同步的,所以先不要吧read和write事件也注册到selector上,当完成握手后,才注册这两个事件,并把socket设置成非阻塞。当select到socket可读时先调用unwrap方法,可写时先调用wrap方法。
每个socket都有两组buffer,分别是appIn,netIn和appOut,netOut,其中netXX都代表从socket中读取或写入的东西,他们都是加了密的,而appXX代表应用程序可理解的数据内容,它们都通过SSLEngine的wrap和unwrap方法才能与netXX相互转换。
二. c++服务器和客户端证书生成
* 如果服务器证书和客户端证书创建时信息太相似,可能用ca文件sign第二个证书时的时候提示error,把index.txt删除新建一个再sign就OK了
* 在填写证书创建信息时,有个common name,这个cn在客户端调用connectToHostEncrypted(,,third param)时,第三个参数要用,不然程序报错
“The issuer certificate of a locally looked up certificate could not be found”
1.首先要生成服务器端的私钥(key文件):
openssl genrsa -des3 -out server.key 1024
运行时会提示输入密码,此密码用于加密key文件(参数des3便是指加密算法,当然也可以选用其他你认为安全的算法.),以后每当需读取此文件(通过openssl提供的命令或API)都需输入口令.如果觉得不方便,也可以去除这个口令,但一定要采取其他的保护措施!
去除key文件口令的命令:
openssl rsa -in server.key -out server.key
2.openssl req -new -key server.key -out server.csr
生成Certificate Signing Request(CSR),生成的csr文件交给CA签名后形成服务端自己的证书.屏幕上将有提示,依照其指示一步一步输入要求的个人信息即可.
3.对客户端也作同样的命令生成key及csr文件:
openssl genrsa -des3 -out client.key 1024
openssl req -new -key client.key -out client.csr
4.CSR文件必须有CA的签名才可形成证书.可将此文件发送到verisign等地方由它验证,要交一大笔钱,自签名证书生成如下:
openssl req -new -x509 -keyout ca.key -out ca.crt
5.用生成的CA的证书为刚才生成的server.csr,client.csr文件签名:
openssl ca -in server.csr -out server.crt -cert ca.crt -keyfile ca.key
openssl ca -in client.csr -out client.crt -cert ca.crt -keyfile ca.key
现在我们所需的全部文件便生成了.
另:
client使用的文件有:ca.crt,client.crt,client.key
server使用的文件有:ca.crt,server.crt,server.key
.crt文件和.key可以合到一个文件里面,本人把2个文件合成了一个.pem文件(直接拷贝过去就行了)
三. JAVA服务端和客户端证书生成(JKS)
(一)使用keytool创建密钥库
1).使用双向认证的SSL/TLS协议通信,客户端和服务器端都要设置用于证实自己身份的安全证书,并且还要设置信任对方的哪些安全证书。
理论上一共需要准备四个文件,两个keystore文件和两个truststore文件。
通信双方分别拥有一个keystore和一个truststore,keystore用于存放自己的密钥和公钥,truststore用于存放所有需要信任方的公钥。
首先使用JDK自带的keytool工具来生成keystore和truststore。这里使用的Java版本是1.7。
2).也可以选择使用单向认证,这种情况下client侧不需要提供证书。所以,
server侧只需要自己的keystore文件,不需要truststore文件
client侧不需要自己的keystore文件,只需要truststore文件(其中包含server的公钥)。
此外server侧需要在创建SSLServerSocket之后设定不需要客户端证书:setNeedClientAuth(false)
(二)证书生成过程
如果需要对证书进行签名,可以使用keytool生成一个csr(certificate sign request), 然后提交到一个CA进行签名。这里使用openssl提供的CA功能。
1)[openssl]生成CA的RSA私钥。
openssl genrsa -out cakey.pem 1024
2)[openssl]使用CA.sh脚本初始化CA。脚本会要求输入刚才生成的私钥文件。
CA.sh -newca
CA certificate filename (or enter to create)
cakey.pem
3)[openssl]利用CA私钥生成自签名的CA根证书。指定x509参数表示要生成自签名的证书。(我们知道证书里面只包含公钥,为什么从私钥可以生成证书呢?这是因为私钥里面包含RSA算法所需的参数,其实可以算出公钥,后面会详细解释)
openssl req -new -x509 -key cakey.pem -out ca.cer
4)[keytool]生成server的keystore文件。
keytool -genkey -alias catserver -keyalg rsa -keysize 1024 -sigalg sha256withrsa -keypass catserver -keystore catserver.keystore -storepass catserverks
5)[keytool]生成server的csr.
keytool -certreq -alias catserver -file catserver.csr -keypass catserver -keystore catserver.keystore -storepass catserverks
6) [openssl]对server的csr进行签名得到server证书
openssl ca -in catserver.csr -out catserver.cer -cert ca.cer -keyfile cakey.pem
7)[keytool]生成client的keystore文件。
keytool -genkey -alias foxclient -keyalg dsa -keysize 512 -sigalg sha1withdsa -keypass foxclient -keystore foxclient.keystore -storepass foxclientks
8)[keytool]生成client的csr.
keytool -certreq -alias foxclient -file foxclient.csr -keypass foxclient -keystore foxclient.keystore -storepass foxclientks
9) [openssl]对client的csr进行签名得到client证书
openssl ca -in foxclient.csr -out foxclient.cer -cert ca.cer -keyfile cakey.pem
现在我们有如下文件:
catserver.keystore -- server的keystore文件
catserver.cer -- server证书文件,已经由CA签名。
foxclient.keystore -- client的keystore文件
foxclient.cer -- client证书文件,已经由CA签名。
ca.cer -- CA的证书文件。作为根证书。
接下来我们还需要
10)[keytool]导入ca.cer和catserver.cer到catserver.keystore
keytool -import -trustcacerts -alias ca -keystore catserver.keystore -storepass catserverks -file ca.cer
keytool -import -alias catserver -keypass catserver -keystore catserver.keystore -storepass catserverks -file catserver.cer
11)[keytool]导入ca.cer和foxclient.cer到foxclient.keystore
keytool -import -trustcacerts -alias ca -keystore foxclient.keystore -storepass foxclientks -file ca.cer
keytool -import -alias foxclient -keypass foxclient -keystore foxclient.keystore -storepass foxclientks -file foxclient.cer
12)[keytool]创建server的truststore文件并导入ca.cer和foxclient.cer.
keytool -import -trustcacerts -alias ca -keystore catservertrust.keystore -storepass catservertrustks -file ca.cer
keytool -import -alias foxclient -keystore catservertrust.keystore -storepass catservertrustks -file foxclient.cer
13)[keytool]创建client的truststore文件并导入ca.cer和catserver.cer.
keytool -import -trustcacerts -alias ca -keystore foxclienttrust.keystore -storepass foxclienttrustks -file ca.cer
keytool -import -alias catserver -keystore foxclienttrust.keystore -storepass foxclienttrustks -file catserver.cer
*前面提到可以从CA的私钥文件生成CA的根证书。证书里面其实包含的是公钥。
其实openssl也提供命令可以直接从私钥cakey.pem生成公钥。
openssl rsa -in cakey.pem -pubout -out caputkey.pem
为什么能够从私钥推导出公钥呢?
原因很简单,因为私钥中也包含了计算公钥所需的参数n和e。
*可以使用openssl来生成server的证书,步骤如下
openssl genrsa -out catserverkey.pem 1024
openssl req -new -key catserverkey.pem -out catserver.csr
openssl ca -in catserver.csr -out catserver.cer -cert ca.cer -keyfile cakey.pem
四.安卓客户端证书生成(BKS)(双向)
(一).生成客户端和服务端pfx
1.openssl pkcs12 -export -inkey server.key -in server.crt -CAfile ca.pem -chain -out server.pfx
2.openssl pkcs12 -export -inkey client.key -in client.crt -CAfile ca.pem -chain -out client.pfx
(二).使用portecle工具生成bks (客户端和服务端相同)
步骤1:File->New Keystore Type->(选择)BKS
步骤2:Tools->Import key pair->(选择pfx)的文件
步骤3:File->Save Keystore(保存为.BKS的格式的文件)
(三).导入证书到安卓:
1.将client.bks导入到KeyStore
2.将server.bks导入到TrustStore
(四).错误信息:
1.Q:error:1408A10B:SSL routines:SSL3_GET_CLIENT_HELLO:wrong version number
A:安卓客户端协议错误,请使用和服务器相同的tls版本协议.,请务必检查socket是否设置了tls协议:setEnabledProtocols(new String[] {"TLSv1.2"})
五.IOS客户端证书生成(P12)
1.将client.crt和client.key转换为pkcs12:
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12
六. 错误信息的汇总和解决方案
(一).安卓客户端问题:
1.Q:error:1408A10B:SSL routines:SSL3_GET_CLIENT_HELLO:wrong version number
A:安卓客户端协议错误,请使用和服务器相同的tls版本协议.,请务必检查socket是否设置了tls协议:setEnabledProtocols(new String[] {"TLSv1.2"})
(二).portecle错误:
Q:找不到密钥库,无法生成密钥
A:复制到JDK_HOME\jre\lib\ext
配置bcprov,在 JDK_HOME\jre\lib\security\目录中找到 java.security 在内容增加一行
security.provider.11=org.bouncycastle.jce.provider.BouncyCastleProvider
(三).tls生成错误:
Q:
或
A:此类错误,请修改/pki/tls/openssl.cnf
添加:
[policy_customized]
countryName = match
stateOrProvinceName = supplied
organizationName = supplied
organizationalUnitName = optional
commonName = supplied
emailAddress = optional
修改为policy = policy_customized
注:optional表示可选的意思