SSL socket 通讯详解【转】

来源:互联网 发布:icmp报文 端口 编辑:程序博客网 时间:2024/05/19 18:11

来自:http://blog.csdn.net/pony_maggie/article/details/51315946

作者:小马

最近刚弄完一个ssl socket通讯,整理个笔记。

SSL原理

比如 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通讯。

openssl 与 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;}
  • 1
  • 2
  • 3
  • 4
  • 5
  • 6
  • 7
  • 8
  • 9
  • 10
  • 11
  • 12
  • 13
  • 14
  • 15
  • 16
  • 17
  • 18
  • 19
  • 20
  • 21
  • 22
  • 23
  • 24
  • 25
  • 26
  • 27
  • 28
  • 29
  • 30
  • 31
  • 32
  • 33
  • 34
  • 35
  • 36
  • 37
  • 38
  • 39
  • 40
  • 41
  • 42
  • 43
  • 44
  • 45
  • 46

如有错误,请不吝赐教。