转载请注明出处
http://blog.csdn.net/pony_maggie/article/details/51315824
作者:小马
最近刚弄完一个ssl socket通讯,整理个笔记。
比如 A要和B互相通讯,为了安全他们希望双方发送的数据都是经过加密的。这就要求双方有一个共同的加解密密钥(一般加密都是基于对称加密算法)。如何才能让双方都拥有同一个密钥呢?有人说由一方生成发给另一方不就行了。这样就带来另一个问题,如何保证这个传送的密钥是安全的呢?
ssl实现了通过非对称的RSA加解密原理实现密钥的交换。(这里就不细讲RSA的原理了,不明白的可以先补充一下这部分知识)。我们假设A是服务器端,B是客户端。然后还有一个角色,是一个可信任的第三方CA。A首先生成一个RSA公私钥对,然后再由这个可信的第三方用自己的私钥(为了便于描述, 后面叫CA_PRIVATE_KEY)把这个DES密钥连同RSA公钥一起签发一个客户端证书(后面叫CLIENT_CERT),这个证书中同时也包含一段签名。
A会把CA证书(证书中包含CA_PRIVATE_KEY 对应的公钥, 后面叫CA_CERT)连同CLIENT_CERT一起发给客户端,当然客户端也可以由其它途径获取这两个证书(比如由专门的安全设备中导入到本地)。同时RSA私钥(后面叫CLIENT_PRIVATE_KEY)也会一起下发到客户端。
客户端首先用CA_CERT对CLIENT_CERT做验签,以确保数据确实是由A发出来的。如果验签能过,其实就已经说明B已经认证了A的身份。前面说到CLIENT_CERT包含服务器的公钥,B用这个公钥对一个对称的DES密钥加密,然后可以公开发出去,他不用担心这个密钥会被截取,因为只有A才有CLIENT_PRIVATE_KEY,也只有A才能解密出来这个DES密钥。这样就完成了对称密钥的交换。
盗个图,流程基本是下面这样的,
大部分时候到这里,SSL通讯前的握手已经完成了,可以进行安全的数据通讯了。不过有时候会有双向认证的需求,也就是A也想认证B。这个时候CLIENT_PRIVATE_KEY就发生作用了,B会用这个私钥自己生成签名,然后发给A来认证。
这些基本就是SSL的原理了。
上面讲到的这些流程,如果用程序实现还是有点复杂的。幸运的是,开源库openssl已经帮我们做了大部分的事情(openssl的实现机制更复杂也更灵活,但基本原理跟上面是一致的)。我们只需要调用一些基本的接口就可以完成SSL socket通讯。
基于ssl的socket通讯一般分为几个步骤。
第一步, 初始化
这一步主要是初始化openssl库,创建会话上下文等,
SSL_METHOD *method = NULL;
SSL_library_init ();
SSL_load_error_strings();
OpenSSL_add_ssl_algorithms();
method = SSLv3_client_method();
g_ctx = SSL_CTX_new(method); /* Create new context */
SSLv3_client_method 是指定ssl要使用的协议。SSL协议由美国 NetScape公司开发的,V1.0版本从没有公开发表过;V2.0版本于1995年2月发布。但是,由于V2.0版本有许多安全漏洞,所以,1996年紧接着就发布了V3.0版本。微软从IE 7开始就已经把浏览器的缺省设置不支持SSL 2.0,但可能是考虑到有些网站还只支持SSL 2.0,所以IE浏览器留了一个可以由用户设置支持SSL 2.0的选项,以便能正常访问只支持SSL 2.0的网站。IE7/IE8支持SSL 3.0和TLS1.0,而IE9还支持TLS1.1和1.2。
在openssl里指定协议很简单,每个协议都有对应的函数,一行代码就可以搞定。
SSL_METHOD* TLSv1_client_method(void); TLSv1.0 协议
SSL_METHOD* SSLv2_client_method(void); SSLv2 协议
SSL_METHOD* SSLv3_client_method(void); SSLv3 协议
SSL_METHOD* SSLv23_client_method(void); SSLv2/v3 协议
SSL_CTX_new创建ssl上下文,这里面很多全局变量要被各个阶段共享。
SSL_load_error_strings(void );
如果想打印出一些方便阅读的调试信息的话,便要在一开始调用此函数.
第二步,加载证书和私钥
前面提到过,证书有CA证书,还是客户端的证书,私钥是客户端私钥。也是几个接口就搞定的事情,
int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,const char *CApath);
此函数用来便是加载CA证书文件的.
int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
加载客户端自己的证书文件.
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
加载自己的私钥,以用于签名.
void SSL_CTX_set_default_passwd_cb_userdata(SSL_CTX *ctx, void *u);
加载私钥时一般要一个验证密码,这个密码是由生成私钥的一方来设定的,客户端得到这个密码后要通过一个接口传入验证:
void SSL_CTX_set_verify(SSL_CTX ctx,int mode,int (*callback)(int, X509_STORE_CTX ));
缺省mode是SSL_VERIFY_NONE,如果想要验证对方的话,便要将此项变成SSL_VERIFY_PEER.SSL/TLS中缺省只验证server,如果没有设置 SSL_VERIFY_PEER的话,客户端连证书都不会发过来.
第三步,建立ssl socket连接
首先要建立普通的socket连接,这个就不多说了,连接成功后返回一个socket描述符,假设名称为socket_fd。然后与ssl进行关联,三个接口就可以完成:
g_ssl = SSL_new(g_ctx);
SSL_set_fd(g_ssl, socket_fd);
SSL_connect(g_ssl);
这样后面所以的发送,接收过程都是基于g_ssl这个ssl的全局字段了。
第四步,发送和接收
收发数据就更简单了,
发送,
SSL_write(g_ssl, pData, bytes_left);
接收,
SSL_read(g_ssl, pData, bytes_left);
当然,一般我们在普通的socket收发都会加一些超时处理机制,对于ssl的收发也是一样的,后面我的示例中会给出来一个较完善的处理过程。
第五步,释放资源
如果不使用了,就要把所有占用的资源都释放掉。
当然首先要把socket关闭,
close(socket_fd);
然后是和ssl相关的资源释放
SSL_CTX_free(g_ctx);
SSL_shutdown(g_ssl);
SSL_free(g_ssl);
ERR_free_strings();
初始化,连接等操作都很简单,这里只给出一个ssl发送函数的实现方法,带超时处理的,供大家参考。
int socket_send_ssl(const char *data,
unsigned int data_len,int time_out)
{
int iRet = -1;
struct timeval tm;
tm.tv_sec = time_out;
tm.tv_usec = 0;
char *pData = data;
int bytes_left = data_len;
int written_bytes = 0;
if((data == NULL) || (data_len <= 0))
{
return -2;
}
iRet = setsockopt(socket_fd,SOL_SOCKET,SO_SNDTIMEO,&tm,sizeof(tm));//设置发送超时
while(bytes_left > 0)
{
written_bytes = SSL_write(g_ssl, pData, bytes_left);
if(written_bytes <= 0)
{
if(errno == EINTR)
{
written_bytes = 0;
}
else
{
return -1;
}
}
bytes_left -= written_bytes;
pData += written_bytes;
}
return 0;
}
如有错误,请不吝赐教。