SSL socket 通讯详解【转】

来自: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

如有错误,请不吝赐教。


  • 0
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
/* ========================================================================================= // 利用OpenSSL库对Socket传输进行安全加密(RSA+AES) // 1. 利用RSA传输AES生成密钥所需的Seed(32字节BUF) // 2. 利用AES_encrypt/AES_decrypt对Socket上面的数据报文进行AES对称性加密 // --- // * 理论上只需要AES就能保证全部流程,但由于AES加密所需要的AES-KEY是一个结构 // * 这个一个结构,如果通过网络进行传输,就需要对它进行网络编码,OpenSSL里面没有现成的API // * 所以就引入RSA来完成首次安全的传输,保证Seed不会被窃听 // * 同样,只使用RSA也能完成全部流程,但由于RSA的处理效率比AES低, // * 所以在业务数据传输加密上还是使用AES // --- // 下面的代码包含了上述传输加密流程所需的所有步骤(OpenSSL部分) // 在实际的Socket应用开发时,需要将这些步骤插入到Client/Server网络通信的特定阶段 // --- // 为能完成代码的编译和执行,需要先安装OpenSSL执行库及开发库 // 以Debian为例,需要安装openssl 和 libssl-dev // 编译命令: g++ -o rsa-encrypt rsa-encrypt.cpp -lcrypto // --- // 所需的OpenSSL主要的API及功能描述 // 1. RSA_generate_key() 随机生成一个RSA密钥对,供RSA加密/解密使用 // 2. i2d_RSAPublicKey() 将RSA密钥对里面的公钥提出到一个BUF,用于网络传输给对方 // 3. d2i_RSAPublicKey() 将从网络传过来的公钥信息生成一个加密使用的RSA(它里面只有公钥) // 4. RSA_public_encrypt() 使用RSA的公钥对数据进行加密 // 5. RSA_private_decrypt() 使用RSA的私钥对数据进行加密 // 6. AES_set_encrypt_key() 根据Seed生成AES密钥对中的加密密钥 // 7. AES_set_decrypt_key() 根据Seed生成AES密钥对中的解密密钥 // 8. AES_encrypt() 使用AES加密密钥对数据进行加密 // 9. AES_decrypt() 使用AES解密密钥对数据进行解密 // --- // 一个典型的安全Socket的建立流程, 其实就是如何将Server随机Seed安全发给Client // -- C: Client S:Server // C: RSA_generate_key() --> RSAKey --> i2d_RSAPublicKey(RSAKey) --> RSAPublicKey // C: Send(RSAPublicKey) TO Server // S: Recv() --> RSAPublicKey --> d2i_RSAPublicKey(RSAPublicKey) --> RSAKey // S: Rand() --> Seed --> RSA_public_encrypt(RSAKey, Seed) --> EncryptedSeed // S: Send(EncryptedSeed) TO Client // C: Recv() --> EncryptedSeed --> RSA_private_decrypt(RSAKey, EncryptedSeed) --> Seed // --- 到此, Client和Server已经完成完成传输Seed的处理 // --- 后面的流程是它们怎样使用这个Seed来进行业务数据的安全传输 // C: AES_set_encrypt_key(Seed) --> AESEncryptKey // C: AES_set_decrypt_key(Seed) --> AESDecryptKey // S: AES_set_encrypt_key(Seed) --> AESEncryptKey // S: AES_set_decrypt_key(Seed) --> AESDecryptKey // --- Client传输数据给Server // C: AES_encrypt(AESEncryptKey, Data) --> EncryptedData --> Send() --> Server // S: Recv() --> EncryptedData --> AES_decrypt(AESDecryptKey, EncryptedData) --> Data // --- Server传输数据给Client // S: AES_encrypt(AESEncryptKey, Data) --> EncryptedData --> Send() --> Client // C: Recv() --> EncryptedData --> AES_decrypt(AESDecryptKey, EncryptedData) --> Data / ========================================================================================= */
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值