分类: C/C++
2017-07-12 13:57:51
一、加密和算法
1.1 散列(hash)
Hash,一般翻译做"",也有直接音译为""的,就是把任意长度的输入,通过,变换成固定长度的输出,该输出就是。常用的散列函数是SHA1和MD5。
哈希是单向的,不可通过散列值得到原文;
不同的内容做散列计算,计算出的散列值为相同的概率几乎等于0;
哈希主要用在:文件校验、数字签名、快速查找等。
例如,文件校验:
1.2 对称加密(Symmetric Cryptography)
需要对加密和解密使用相同的。由于其速度快,对称性加密通常在消息发送方需要加密大量数据时使用。但是管理密钥不方便,要求共享密钥。如果N个人相互之间都要用对称加密进行通讯,则每个人维护的密钥是N-1个。
目前流行的对称加密算法有AES、、、、RC2、RC4、等等。
1.3 非对称加密(Asymmetric Cryptography)
与对称加密不同,需要两个:(publickey)和私有密钥(privatekey)。与私有密钥是一对,如果用公开密钥对数据进行加密,只有用对应的私有密钥才能解密;如果用私有密钥对数据进行加密,那么只有用对应的公开密钥才能解密。因为加密和解密使用的是两个不同的,所以这种算法叫作。非对称加密算法的优点是密钥管理很方便,缺点是速度慢。
通常来讲,公钥是每个人都能得到的,私钥是只有自己持有;
A与B之间进行通信,安全的方式:
1. A给B发送消息,使用B的公钥进行加密,然后发给B,只有B用私钥才能解密成明文;
2. B给A发送消息,使用A的公钥进行加密,然后发给A,只有A用私钥才能解密成明文;
A获取B的公钥,必须是安全的方式;B获取A的公钥,也必须是安全的方式;
1.4 数字签名(Digital Signature)
现实生活中,我们用签名来证明某个东西是与签名者相关的,是不可否认的,不可伪造的;
在虚拟世界里,我们有数字签名来帮助证明某个文档是你创建的,或者是你认可的。数字签名所用的技术是散列和非对称加密。
基于前面非对称加密的介绍,对签名的信息进行散列计算,用私钥对这个散列值进行加密;这样就得到一个签名。
当用户B比对散列值3与散列值2是否相同,如果相同则认为是A签名的,否则不是。
二、数字证书
如果现实生活中是用身份证来证明身份,那么在虚拟世界中,则会使用数字证书来证明身份。并不是每个人都需要用数字证书来证明身份,那么什么情况下,会使用数字证书来证明身份呢?普通用户一般情况下,不需要证明自己的身份,大部分网站不关心是谁访问了网站,现在的网站只关心流量啊~反过来,网站就需要证明自己的身份了。比如你想要提交信用卡信息给预定航班的网站,那么你如何确定你正在访问的网站就是你所想要访问的那个呢?现在很多的。比如你想访问的是“”,但其实你访问的是“”,所以在提交自己的信息之前你需要验证一下网站的身份,要求网站出示数字证书。一般正常的网站都会主动出示自己的数字证书。由于证书在网页浏览中最为常见,所以我下面举的例子都是基于浏览器的。
2.1 数字证书的构成
我们的身份证是由公安机关颁发的,并加有很多防伪技术,不能伪造(或者说很难)。同样的,数字证书也有专门的发证机关(Certificate Authority,简称CA,其实是一些商业公司啦)。比较常见的发证机关是。数字证书的发证机关会对自己发放的证书加上自己的数字签名,以保证证书不能被伪造。那数字证书到底包含了些什么呢?
1. 持有者姓名(Common Name)
2. 发证机关(Issuer)
3. 有效日期(Validity)
4. 证书持有人的公钥(Subject’s Public Key Info)
5. 扩展信息 (Extension)
6. 用发证机关对该证书的数字签名(Certificate Signature)
基本信息就这些了(这些信息会在后面的章节有所解释),为了更清晰的说明问题,来几张截图(火狐->工具->选项->高级->证书->查看证书):
从图4我们可以看到,Certificate(证书)和Signature(签名)是分开的,但其实这个Signature也是证书的一部分。可以这么理解,数字证书包含证书主体和数字签名。证书中的签名是对证书主体的签名。
2.2 如何验证数字证书
当浏览器拿到一个数字证书:
1. 先看发证机关(图5),主要是看以下三个:Organization Name(O),Organizational Unit Name(OU),Common Name(CN)
2. 找到相应的发证机关的证书,获得发证机关的公钥,用此公钥解密被加密的SHA1,这样就获得了此证书的SHA1值,我们称它为Hash1(图6、图7)。
3. 浏览器用SHA1算法对此证书重新计算一遍SHA1,获得Hash2。
4. 然后比较Hash1和Hash2是否相等。如果相等就证明这张证书是由发证机关颁发的,并且没有被篡改过。
5. 验证证书持有者
经过上述的证书验证后,可以认为证书没问题,是可靠的。但是还需要做一步,需要查看下发送者是否是证书的持有者(也就是通常意义上的身份证上的照片是否是本人,看完证件,还要比对下脸)。
通过证书里的公钥加密一段信息发送给证书持有者,如果对方能发送回来(加密的或者明文),说明对方是证书的持有者(也即,对方有对应的私钥)。
6. 核对名字
通常对于用证书来做web服务器的应用来说,证书中的名字(Common name)很重要,一般与根域名相等。如果不相等,则认为证书存在问题。
2.3 证书的级联
所有的证书都是基于另一张证书(可信任证书)进行认证的。换句话说,用一张已知合法的证书来证明另一张未知的证书。发证机关的证书默认是可信任的,这些证书称之为“根证书”。
由于申请证书的人数众多,发证机关忙不过来,这时候需要一些代理来帮忙签发证书,代理可能也需要代理来帮忙签发证书。这样就存在了层级关系:
这里的”USERTrust Legacy Secure Server CA”是由根证书”AddTrust External CA Root”签发的。验证这张证书的时候,需要从下往上递归验证。
是否是CA证书,可以看下扩展信息中的”基本约束”,如下图:
三、SSL的基本原理
现在回到我们最原始的问题,由于Internet的架构问题,信息在网络上传输是很容易被别人获取的,那如何建立一个安全的传输网络呢?前面我们讨论了很多保证信息安全的技术,而SSL就是建立在这些技术的基础上的一套协议,用来保证通信的安全。SSL全称是 Secure Sockets Layer,它是一种间于传输层(比如TCP/IP)和应用层(比如HTTP)的协议。具体的SSL协议很复杂,我这里只讲一个大概。
最简单的方法来保证通信安全是用非对称加密。我们前面讲过数字证书的认证,如果双方都认证了对方的数字证书,那么每次传输信息的时候都用对方的公钥加密,这样就只有对方能解密,从而保证了信息的安全。但是对于日常应用(比如网页浏览)有两个问题:
1.非对称加密速度缓慢,消耗资源
如果客户端和服务器之间传输文件用非对称加密的话,速度一定慢的忍无可忍。
2.不可能要求每个用户都去申请数字证书
申请数字证书是一个相当麻烦的过程,要求每个上网的用户都拥有证书是不可能的事情。
SSL通过“握手协议”和“传输协议”来解决上述问题。握手协议是基于非对称加密的,而传输协议是基于对称加密的。根据不同的应用,SSL对证书的要求也是不一样的,可以是单方认证(比如HTTP, FTP),也可以是双方认证(比如网上银行)。通常情况下,服务器端的证书是一定要具备的,客户端的证书不是必须的。
图10 握手过程
Phase1:
client_hello:包括一个随机数和一系列的可支持的加密组件(以性能高到低的方式排列)
server_hello: 包含一个随机数和一个从客户端发来的组件中选择的加密组件
加密组件:每个加密组件包含一个密钥交换算法、加密算法、MAC算法,例如:TLS_ RSA_WITH_AES_256_CBC_SHA256
密钥交换算法:DH算法、RSA(公钥/私钥)
Phase2(例如使用DH):
server_key_exchange中包括 p、a、S1;
client_key_exchange中包括C1;
最后生成K是共用密钥。
Phase3 :
客户端的认证:服务器或许会请求客户端发送证书,如果客户端有,则发送证书,如果没有则发送一个no_certificate的警告信息。
如果发送了证书才会有certificate_verify,包括了客户端的认证,包括了一个之前握手消息的散列值。
Phase4:
客户端发送Change Cipher Spec消息,通知SSL服务器后续报文将采用协商好的密钥和加密套件进行加密和MAC计算。
SSL客户端计算已交互的握手消息(除Change Cipher Spec消息外所有已交互的消息)的Hash值,利用协商好的密钥和加密套件处理Hash值(计算并添加MAC值、加密等),并通过Finished消息发送给SSL服务器。
SSL服务器利用同样的方法计算已交互的握手消息的Hash值,并与Finished消息的解密结果比较,如果二者相同,且MAC值验证成功,则证明密钥和加密套件协商成功。
同样地,SSL服务器发送Change Cipher Spec消息,通知SSL客户端后续报文将采用协商好的密钥和加密套件进行加密和MAC计算。
SSL服务器计算已交互的握手消息的Hash值,利用协商好的密钥和加密套件处理Hash值(计算并添加MAC值、加密等),并通过Finished消息发送给SSL客户端。SSL客户端利用同样的方法计算已交互的握手消息的Hash值,并与Finished消息的解密结果比较,如果二者相同,且MAC值验证成功,则证明密钥和加密套件协商成功。
四、openssl开源库的使用(客户端)
openssl开源库的网址,包括源码、文档等内容。这一节,我们主要使用的是ssl/tls库。
1. 初始化
作为一个客户端,首先要初始化一个Context 对象(一个SSL_CTX),这个对象是用来创建一个新的连接对象。Context初始化,包括四个主要的部分,如下:
SSL_METHOD *meth;
SSL_CTX *ctx;
SSL_library_init(); //初始化整个库,加载了一些openssl的算法等内容,只需要在程序启动的时候,初始化一次就可以了。
meth=SSLv23_method(); //用SSL v2 或者V3兼容的方式
ctx=SSL_CTX_new(meth); //创建一个Context对象
/* Load our keys and certificates*/
if(!(SSL_CTX_use_certificate_chain_file(ctx, keyfile))) //加载证书
berr_exit("Can’t read certificate file");
SSL_CTX_set_default_passwd_cb(ctx, password_cb);//如果密码是加密过的,password_cb是个回调函数,可以还原密码
if(!(SSL_CTX_use_PrivateKey_file(ctx,keyfile,SSL_FILETYPE_PEM)))//加载私有密钥
berr_exit("Can’t read key file");
/* Load the CAs we trust*/
if(!(SSL_CTX_load_verify_locations(ctx,CA_LIST,0))) //加载可信任的CA证书
berr_exit("Ca’t read CA list");
如果一个client要去执行认证,则需要加载自己的public/private key 对儿和相关证书。
SSL_CTX_use_certificate_chain_file用来加载CA证书;
SSL_CTX_use_PrivateKey_file用来加载私钥,通常密码是加密的,可以通过SSL_CTX_set_default_passwd_cb回调函数来获取密码。
加载根CA列表,如果你要认证对端,则需要知道CA,加载函数:SSL_CTX_load_verify_locations
2. 握手处理
SSL连接的第一步就是要执行SSL握手过程。握手认证server(可选的认证client),并且建立关键材料用来保护后续的数据。SSL_connect()函数用来执行SSL的握手协议。如果使用阻塞协议,SSL_connect会一直阻塞,直到握手完成,或者返回一个错误。SSL_connect返回1表示成功,0或者负数表示一个错误。代码如下:
sock=tcp_connect(host,port); //假设tcp_connect 是用来建立一个tcp连接的函数
ssl = SSL_new(ctx); //初始化TLS/SSL
sbio = BIO_new_socket(sock, BIO_NOCLOSE); //创建一个BIO对象,关联到TCP socket
SSL_set_bio(ssl,sbio,sbio); //将ssl关联到BIO对象上
if(SSL_connect(ssl)<=0) //这里执行握手
berr_exit("SSL connect error");
3. 检查server的证书
X509 *peer;
char peer_CN[256];
if(SSL_get_verify_result(ssl)!=X509_V_OK) //对端发来的证书验证,如果不是X509
berr_exit("Certificate doesn’t verify");
/*Check the common name*/
peer=SSL_get_peer_certificate(ssl); //提取server的证书
X509_NAME_get_text_by_NID(X509_get_subject_name(peer),NID_commonName, peer_CN, 256); //获取Common Name
if(strcasecmp(peer_CN,host))
err_exit ("Common name doesn’t match host name");
4. 读写数据
SSL_write()函数用来发送数据到对端,像write()一样使用。只是传的参数是SSL对象而不是文件描述符。在阻塞的模式,SSL_write()不会返回,直到数据都发送完,或者一个错误返回,然而write可能只写部分数据。注意: SSL_MODE_ENABLE_PARTIAL_WRITE标记使能部分写的功能。代码如下:
r=SSL_write(ssl,request,request_len); //写数据,数据在request中,长度是request_len
switch(SSL_get_error(ssl,r)){
case SSL_ERROR_NONE:
if(request_len!=r)
err_exit("Incomplete write!");
break;
default:
berr_exit("SSL write problem");
break;
}
SSL_read() 用来读取数据,就像read一样,我们选择合适的参数传给SSL_read()。SSL_read返回读到的数据长度。如果不存在数据可读,则SSL _read是阻塞的,会一直等待。
r=SSL_read(ssl,buf,BUFSIZZ); //读取数据
switch(SSL_get_error(ssl,r)){
case SSL_ERROR_NONE:
len=r;
break;
case SSL_ERROR_ZERO_RETURN:
goto shutdown;
case SSL_ERROR_SYSCALL:
fprintf(stderr,"SSL Error: Premature close0);
goto done;
default:
berr_exit("SSL read problem");
}
//这里对数据进行处理
例子中使用函数SSL_get_error()来替代errno,SSL_get_error()检查返回值,并且指出是否错误发生了,并且错误是什么。
5. 关闭Closure/shutdown/cleanup
TCP使用FIN标记来说明发送者已经发完所有的数据。SSLv2简单的允许任何一方使用一个TCP FIN来终止SSL连接。这会导致“截断攻击”:攻击者通过简单地伪造TCP FIN来使看起来消息是短的。除非受害者有某种方式知道消息的长度。
为了避免这种情况,SSLv3引进了一个close_notify警告。close_notify是一个SSL消息(因此是安全的),但是不是数据流本身的一部分,不背应用所看到。当发送完close_notify后,不会再有数据会被发送。
因此,SSL_read返回0,说明socket被关闭了,已经收到了close_notify消息。如果client先收到一个FIN,则认为是一个“过早关闭”。
如果我们读到SSL_read返回0,没有任何错,则我们需要发送close_notify到server端(通过SSL_shutdown()来发送)。
SSL_shutdown()返回1表示完全关闭,0表示不完全关闭,-1是出现一个错误。当收到服务器发来的close_notify,唯一可能出错的是发送我们的close_notify,否则SSL_shutdown总是会成功。
代码片段(client):
while(1)
{
r=SSL_read(ssl,buf,BUFSIZZ);
switch(SSL_get_error(ssl,r))
{
case
SSL_ERROR_NONE: //未出错
len=r;
break;
case
SSL_ERROR_ZERO_RETURN: //未出错,我们已经收到close_notify
goto shutdown; //这里去发送我们的close_notify
case
SSL_ERROR_SYSCALL:
fprintf(stderr, "SSL Error: Premature
close\n"); //收到一个“过早关闭”的信息
goto
done;
default:
berr_exit("SSL read problem");/*错误处理,这里是退出程序*/
}
/*数据处理*/
}
shutdown:
r=SSL_shutdown(ssl); //发送给server端一个close_nofity
switch(r)
{
case 1:
break; /* Success */
case 0:
case -1:
default:
berr_exit("Shutdown
failed");
}
done:
SSL_free(ssl); /*释放资源*/
/*释放相关变量*/
五、openssl证书常用命令
x509证书一般会用到三类文,key,csr,crt。
Key 是私用密钥openssl格,通常是rsa算法。
Csr 是证书请求文件,用于申请证书。在制作csr文件的时,必须使用自己的私钥来签署申,还可以设定一个密钥。
crt是CA认证后的证书文,(windows下面的,其实是crt),签署人用自己的key给你签署的凭证。
pfx会同时包含公钥和私钥。
1.key的生成
openssl
genrsa -des3 -out
server.key 2048
这样是生成rsa私钥,des3算法,openssl格式,2048位强度。server.key是密钥文件名。为了生成这样的密钥,需要一个至少四位的密码。
可以通过以下方法生成没有密码的key:
openssl
rsa -in server.key
-out server.key
server.key就是没有密码的版本了。这种没密码的私钥这种用法挺多,比如webserver(lighttpd)这种方式,不可能每次启动server的时候,都要求用户输入密码,太过麻烦。
2. 生成CA的crt
openssl
req -new -x509
-key server.key -out ca.crt
-days 3650
生成的ca.crt文件是用来签署下面的server.csr文件。需要输入一堆信息,有一个Common name 通常是CA机构的域名(这里,我们可以自己取一个名字)。
3. csr的生成方法
openssl
req -new -key
server.key -out server.csr
需要依次输入国家,地区,组织,email。
最重要的是?有一个common
name,可以写你的名字或者域名。
如果为了https申请,这个必须和域名吻合,否则会引发浏览器警报。
生成的csr文件交给CA签名后形成服务端自己的证书。
注意Common name 与CA的Common name 要不一样,否则会校验失败。
4. crt生成方法
CSR文件必须有CA的签名才可形成证书,可将此文件发送到verisign等地方由它验证,要交一大笔钱,何不自己做CA呢。
openssl
x509 -req -days 3650 -in server.csr -CA ca.crt -CAkey
server.key -CAcreateserial -out server.crt
输入key的密钥后,完成证书生成。
-CA选项指明用于被签名的csr证书,
-CAkey选项指明用于签名的密钥,
-CAserial指明序列号文件
-CAcreateserial指明文件不存在时自动生成。
最后生成了私用密钥:server.key和自己认证的SSL证书:server.crt
证书合并:
cat server.key
server.crt > server.pem
6. 常用命令
校验证书,是否正确: openssl
verify -CAfile ca.crt server.crt
此命令的意思是 用CA证书ca.crt 来验证server的公钥,例如:
1. X509转换pfx
openssl
pkcs12 -export -out server.pfx -inkey server.key -in server.crt
2.
查看证书:
openssl
x509 -in ca.crt
-text -noout
openssl
x509 -in server.crt
-text –noout
注意看证书的有效期时间:
用openssl命令开一个server:
openssl s_server -accept 4433 -cert
server.crt -key server.key -CAfile ca.crt
-ssl3
上述的例子是:
-accept 4433 表示在本地4433 端口上开启ssl服务器
-cert server.crt 表示是服务器的公钥
-key server.key 表示是服务器的私钥
-CAfile ca.crt 表示是ca证书,服务器和客户端都有的
-ssl3 表示用sslv3
用openssl命令做客户端,连接服务器测试:
openssl
s_client -connect localhost:443 -verify 1
-CAfile ca.crt
-connect localhost:4433 表示连接到本地端口4433
-CAfile ca.crt 表示CA证书是ca.crt
-ssl3 表示使用sslv3
-verify 1 表示要认证对端
CA的生成:
openssl
genrsa -des3 -out
ca.key 2048
openssl
rsa -in ca.key
-out ca.key
openssl
req -new -x509
-key ca.key -out
ca.crt -days 3650
server端的生成:
openssl
genrsa -des3 -out
server.key 2048
openssl
rsa -in server.key
-out server.key
openssl
req -new -key
server.key -out server.csr
openssl x509 -req -days 3650 -in server.csr
-CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt
openssl pkcs12 -export -out server.pfx
-inkey server.key -in server.crt
client端的生成:
openssl
genrsa -des3 -out
client.key 2048
openssl
rsa -in client.key
-out client.key
openssl
req -new -key
client.key -out client.csr
openssl x509 -req -days 3650 -in client.csr
-CA ca.crt -CAkey ca.key -CAcreateserial -out client.crt
openssl pkcs12 -export -out client.pfx
-inkey client.key -in client.crt
验证:
openssl verify -CAfile ca.crt server.crt
openssl verify -CAfile ca.crt client.crt