建立SSL安全通信的一般流程,笔记整理
分类:微服架构

使用OpenSSL API 建立SSL安全通信的一般流程

本文由CSDN-蚍蜉撼青松【主页:http://blog.csdn.net/howeverpf】整理编辑,转载请注明出处!

    OpenSSL是一套开放源代码的SSL套件,其函数库是以C语言所写成,实现了基本的传输层数据加密功能。此软件是以两个加拿大人Eric A. Young 和Tim J. Hudson所写的SSLeay为基础所发展的,SSLeay随着两人前往RSA公司任职而停止开发。1998年,OpenSSL项目组接管了OpenSSL的开发工作,并推出了OpenSSL的0.9.1版,到目前为止,OpenSSL的算法已经非常完善,对SSL2.0、SSL3.0以及TLS1.0都支持。

    OpenSSL同时实现了客户端与服务器的开发接口,使用OpenSSL进行安全通信的大致流程如下图所示。

图片 1

图1 使用OpenSSL API建立SSL通信的流程

  HTTPS是以安全为目标的HTTP通道,简单讲是HTTP的安全版。即HTTP下加入SSL层,HTTPS的安全基础是SSL,因此加密的详细内容就需要SSL。Nebula是一个为开发者提供一个快速开发高并发网络服务程序或搭建高并发分布式服务集群的高性能事件驱动网络框架。Nebula作为通用网络框架提供HTTPS支持十分重要,Nebula既可用作https服务器,又可用作https客户端。本文将结合Nebula框架的https实现详细讲述基于openssl的SSL编程。如果觉得本文对你有用,帮忙到Nebula的Github或码云给个star,谢谢。Nebula不仅是一个框架,还提供了一系列基于这个框架的应用,目标是打造一个高性能分布式服务集群解决方案。Nebula的主要应用领域:即时通讯(成功应用于一款IM)、消息推送平台、数据实时分析计算等,Bwar还计划基于Nebula开发爬虫应用。

使用SSL前,先有 基本的TCP套接字连接。见demo代码

error: openssl 的所有解决方案 (2013/6/22 17:39:00)

一、OpenSSL API

【本小节内容改自:秀秀的新浪博文《SSL协议的分析及实现-2》】

    SSL通信模型采用标准的C/S结构,因此基于OpenSSL的程序可以被分为两个部分:Client和Server。上图1是建立SSL通信的流程简图,说明了基于OpenSSL的程序所要遵循的以下几个重要步骤:

1. SSL加密通信

  HTTPS通信是在TCP通信层与HTTP应用层之间增加了SSL层,如果应用层不是HTTP协议也是可以使用SSL加密通信的,比如WebSocket协议WS的加上SSL层之后的WSS。Nebula框架可以通过更换Codec达到不修改代码变更通讯协议目的,Nebula增加SSL支持后,所有Nebula支持的通讯协议都有了SSL加密通讯支持,基于Nebula的业务代码无须做任何修改。

图片 2

  Socket连接建立后的SSL连接建立过程:

图片 3

SSL_library_init();//在使用OpenSSL 之前,必须进行相应的协议初始化工作

error: openssl/crypto.h: No such file or directory 解决方案 (2013/6/22 17:39:00)

error: openssl/crypto.h: No such file or directory

 

(1)OpenSSL初始化

    OpenSSL在使用之前,必须进行相应的初始化工作。完成初始化功能的函数原型为:

void SSL_load_error_strings(void); // 错误信息的初始化   
int SSL_library_int(void); // 初始化SSL算法库函数( 加载要用到的算法 ),调用SSL函数之前必须调用此函数

    在建立SSL连接之前,要为Client和Server分别指定本次连接采用的协议及其版本,目前能够使用的协议版本包括SSLv2、SSLv3、SSLv2/v3和TLSv1.0。SSL连接若要正常建立,则要求Client和Server必须使用相互兼容的协议。

2. OpenSSL API

  OpenSSL的API很多,但并不是都会被使用到,如果需要查看某个API的详细使用方法可以阅读API文档。

OpenSSL_add_all_algorithms();///* 载入所有 SSL 算法 */

error: openssl/md5.h: No such file or directory 解决方案

libssl-dev 没有安装,只要

sudo apt-get install libssl-dev 就可以了

 

libssl-dev 没有安装,只要

sudo apt-get install libssl-dev 就可以了


(2)创建CTX

    在OpenSSL中,CTX是指SSL会话环境。建立连接时使用不同的协议,其CTX也不一样。创建CTX的过程中会依次用到以下OpenSSL函数:

//客户端、服务端都需要调用的   
SSL_CTX_new()         //申请SSL会话环境   
//若有验证对方证书的需求,则需调用   
SSL_CTX_set_verify()            //指定证书验证方式   
SSL_CTX_load_verify_location()  //为SSL会话环境加载本应用所信任的CA证书列表   
//若有加载证书的需求,则需调用   
SSL_CTX_use_certificate_file()       //为SSL会话加载本应用的证书   
SSL_CTX_use_certificate_chain_file() //为SSL会话加载本应用的证书所属的证书链   
SSL_CTX_use_PrivateKey_file()        //为SSL会话加载本应用的私钥   
SSL_CTX_check_private_key()          //验证所加载的私钥和证书是否相匹配  

2.1 初始化OpenSSL

  OpenSSL在使用之前,必须进行相应的初始化工作。在建立SSL连接之前,要为Client和Server分别指定本次连接采用的协议及其版本,目前能够使用的协议版本包括SSLv2、SSLv3、SSLv2/v3和TLSv1.0。SSL连接若要正常建立,则要求Client和Server必须使用相互兼容的协议。   下面是Nebula框架SocketChannelSslImpl::SslInit()函数初始化OpenSSL的代码,根据OpenSSL的不同版本调用了不同的API进行初始化。

#if OPENSSL_VERSION_NUMBER >= 0x10100003L    if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0)    {        pLogger->WriteLog(neb::Logger::ERROR, __FILE__, __LINE__, __FUNCTION__, "OPENSSL_init_ssl() failed!");        return(ERR_SSL_INIT);    }    /*     * OPENSSL_init_ssl() may leave errors in the error queue     * while returning success     */    ERR_clear_error();#else    OPENSSL_config(NULL);    SSL_library_init();         // 初始化SSL算法库函数( 加载要用到的算法 ),调用SSL函数之前必须调用此函数    SSL_load_error_strings();   // 错误信息的初始化    OpenSSL_add_all_algorithms();#endif

SSL_load_error_strings();/* 载入所有 SSL 错误消息 */

undefined reference to `MD5'_fengzi_lu的空间_百度空间 - Google Chrome (2013/8/8 15:23:26)

(3)建立SSL套接字

    在此之前要先创建普通的流套接字,完成TCP三次握手,建立普通的TCP连接。然后创建SSL套接字,并将之与流套接字绑定。这一过程中会使用以下几个函数:

SSL *SSl_new(SSL_CTX *ctx);  //申请一个SSL套接字
int SSL_set_fd(SSL *ssl,int fd);   //绑定读写套接字
int SSL_set_rfd(SSL *ssl,int fd);  //绑定只读套接字
int SSL_set_wfd(SSL *ssl,int fd);  //绑定只写套接字

2.2 创建CTX

  CTX是SSL会话环境,建立连接时使用不同的协议,其CTX也不一样。创建CTX的相关OpenSSL函数:

//客户端、服务端都需要调用SSL_CTX_new();                       //申请SSL会话环境//若有验证对方证书的需求,则需调用SSL_CTX_set_verify();                //指定证书验证方式SSL_CTX_load_verify_location();      //为SSL会话环境加载本应用所信任的CA证书列表//若有加载证书的需求,则需调用int SSL_CTX_use_certificate_file();      //为SSL会话加载本应用的证书int SSL_CTX_use_certificate_chain_file();//为SSL会话加载本应用的证书所属的证书链int SSL_CTX_use_PrivateKey_file();       //为SSL会话加载本应用的私钥int SSL_CTX_check_private_key();         //验证所加载的私钥和证书是否相匹配 

const SSL_METHOD *meth = SSLv23_client_method();创建本次会话连接所使用的协议,如果是客户端可以使用

undefined reference to `MD5'

这是因为包含md5函数的库为/usr/lib/libcrypto.a(.so),所以编译时使用   -lcrypto  就OK了。

(4)完成SSL握手

    在这一步,我们需要在普通TCP连接的基础上,建立SSL连接。与普通流套接字建立连接的过程类似:Client使用函数SSL_connect()【类似于流套接字中用的connect()】发起握手,而Server使用函数SSL_
accept()【类似于流套接字中用的accept()】对握手进行响应,从而完成握手过程。两函数原型如下:

int SSL_connect(SSL *ssl);
int SSL_accept(SSL *ssl);

    握手过程完成之后,Client通常会要求Server发送证书信息,以便对Server进行鉴别。其实现会用到以下两个函数:

X509 *SSL_get_peer_certificate(SSL *ssl);  //从SSL套接字中获取对方的证书信息
X509_NAME *X509_get_subject_name(X509 *a); //得到证书所用者的名字

2.3 创建SSL套接字

  在创建SSL套接字之前要先创建Socket套接字,建立TCP连接。创建SSL套接字相关函数:

SSL *SSl_new(SSL_CTX *ctx);          //创建一个SSL套接字int SSL_set_fd(SSL *ssl, int fd);     //以读写模式绑定流套接字int SSL_set_rfd(SSL *ssl, int fd);    //以只读模式绑定流套接字int SSL_set_wfd(SSL *ssl, int fd);    //以只写模式绑定流套接字

pSockInfo->pSslCtx = SSL_CTX_new(meth);//申请SSL 会话环境

基于X.509证书和SSL协议的身份认证过程实现 - 菜鸟浮出水 - 51CTO技术博客 - Google Chrome (2013/6/7 17:14:52)

客户端程序代码:

//client  #include <winsock2.h>  #include <conio.h>  #include <stdio.h>  #include "openssl/x509.h"  #include "openssl/ssl.h"  #include "openssl/err.h"  #include "openssl/rand.h"  #define PORT       1111  #define SERVER     "127.0.0.1"  #define CACERT     "ca.crt"  #define MYCERTF    "yuliding.crt"  #define MYKEYF     "yuliding.key"  #define MSGLENGTH  1024  int main()  {      WSADATA wsadata;      WSAStartup(MAKEWORD(2,2), &wsadata);      sockaddr_in sin;      int seed_int[100]; /*存放随机序列*/      SSL*ssl;      SSL_METHOD *meth;      SSL_CTX *ctx;      //SSL初始化      OpenSSL_add_ssl_algorithms();      //SSL错误信息初始化      SSL_load_error_strings();      //创建本次会话所使用的协议      meth = TLSv1_client_method();      //申请SSL会话的环境      ctx = SSL_CTX_new(meth);      if (NULL == ctx)          exit(1);      //设置会话的握手方式并加载CA证书      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);      SSL_CTX_load_verify_locations(ctx, CACERT, NULL);      //加载自己的证书      if (0 &gt;= SSL_CTX_use_certificate_file(ctx, MYCERTF, SSL_FILETYPE_PEM)) {          ERR_print_errors_fp(stderr);          exit(1);      }      //加载自己的私钥      if (0 &gt;= SSL_CTX_use_PrivateKey_file(ctx, MYKEYF, SSL_FILETYPE_PEM)) {          ERR_print_errors_fp(stderr);          exit(1);      }      //检查自己的证书和私钥是否匹配      if (!SSL_CTX_check_private_key(ctx)) {          printf("Private key does not match the certificate public keyn");          exit(1);      }      /*构建随机数生成机制,WIN32平台必需*/      srand((unsigned)time(NULL));      for (int i = 0; i < 100; i++)          seed_int[i] = rand();      RAND_seed(seed_int, sizeof(seed_int));      //加密方式      SSL_CTX_set_cipher_list(ctx, "RC4-MD5");      //处理握手多次      SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);      /*以下是正常的TCP socket建立过程 .............................. */      SOCKET sock;      printf("Begin tcp socket...n");      sock = socket(AF_INET, SOCK_STREAM, 0);      if (sock == INVALID_SOCKET) {          printf("SOCKET有问题. n");      }      memset(&sin, '', sizeof(sin));      sin.sin_family = AF_INET;      sin.sin_addr.s_addr = inet_addr(SERVER); /* Server IP */      sin.sin_port = htons(PORT); /* Server Port number */      int icnn = connect(sock, (sockaddr *)&sin, sizeof(sin));      if (icnn == SOCKET_ERROR) {          printf("连不上服务器n", GetLastError());          exit(1);      }      /* TCP 链接已建立.开始 SSL 握手过程.......................... */      //绑定套接字      ssl = SSL_new(ctx);      if (NULL == ssl)          exit(1);      if (0 >= SSL_set_fd(ssl, sock)) {          printf("Attach to Line fail!n");          exit(1);      }      //SSL握手      //SSL_connect(ssl);      int k = SSL_connect(ssl);      if (0 &gt;= k) {          printf("%dn", k);          printf("SSL connect fail!n");          exit(1);      }      printf("连接服务器成功n");      char sendmsg[MSGLENGTH] = "";      char revmsg[MSGLENGTH] = "";      int err = SSL_read(ssl, revmsg, sizeof(revmsg));      revmsg[err] = '';      printf("%sn", revmsg);      while (1) {          printf("请输入所要发送的数据:n");          scanf("%s", sendmsg);          SSL_write(ssl, sendmsg, strlen(sendmsg));          printf("发送消息“ %s ”成功!n", sendmsg);      }      //关闭套接字      SSL_shutdown(ssl);      SSL_free(ssl);      SSL_CTX_free(ctx);      closesocket(sock);      WSACleanup();      getch();      return 0;  } 

服务端程序代码:

//server  #include <winsock2.h>  #include <conio.h>  #include <stdio.h>  #include <winsock.h>  #include "openssl/x509.h"  #include "openssl/ssl.h"  #include "openssl/err.h"  #define MSGLENGTH      1024  #define PORT           1111  #define CACERT         "ca.crt"  #define SVRCERTF       "server.crt"  #define SVRKEYF        "server.key"  int main()  {      WSADATA wsaData;      WSAStartup(MAKEWORD(2,2), &wsaData);      SOCKET sock;      SSL_METHOD *meth;      SSL_CTX* ctx;      SSL* ssl;      //SSL初始化      OpenSSL_add_ssl_algorithms();      //SSL错误信息初始化      SSL_load_error_strings();      //创建本次会话所使用的协议      meth = TLSv1_server_method();      //申请SSL会话的环境      ctx = SSL_CTX_new(meth);      if (NULL == ctx)          exit(1);      //设置会话的握手方式并加载CA证书      SSL_CTX_set_verify(ctx, SSL_VERIFY_PEER, NULL);      SSL_CTX_load_verify_locations(ctx, CACERT, NULL);      //加载服务器端的证书      if (0 &gt;= SSL_CTX_use_certificate_file(ctx, SVRCERTF, SSL_FILETYPE_PEM)) {          ERR_print_errors_fp(stderr);          exit(1);      }      //加载服务器端的私钥      if (0 &gt;= SSL_CTX_use_PrivateKey_file(ctx, SVRKEYF, SSL_FILETYPE_PEM)) {          ERR_print_errors_fp(stderr);          exit(1);      }      //检查服务器端的证书和私钥是否匹配      if (!SSL_CTX_check_private_key(ctx)) {          printf("Private key does not match the certificate public keyn");          exit(1);      }      //加密方式      SSL_CTX_set_cipher_list(ctx, "RC4-MD5");      //处理握手多次      SSL_CTX_set_mode(ctx, SSL_MODE_AUTO_RETRY);      /*以下是正常的TCP socket建立过程 .............................. */      printf("Begin tcp socket...n");      sock = socket(AF_INET, SOCK_STREAM, 0);      if (sock == INVALID_SOCKET) {          printf("SOCKET有问题. n");          return 0;      }      sockaddr_in addr;      memset(&addr, '', sizeof(addr));      addr.sin_family = AF_INET;      addr.sin_port = htons(PORT); /* Server Port number */      addr.sin_addr.s_addr = INADDR_ANY;      //绑定sock      int nResult = bind(sock, (sockaddr *)&addr, sizeof(addr));      if (nResult == SOCKET_ERROR) {          printf("绑定SOCKET有问题. n");          return 0;      }      printf("服务器启动成功,端口:%dn正在等待连接n", PORT);      /*接受TCP链接*/      sockaddr_in sa_cli;      int err = listen(sock, 5);      if (-1 == err)          exit(1);      int client_len = sizeof(sa_cli);      int ss = accept(sock, (struct sockaddr *) &sa_cli, &client_len);      if (ss == -1) {          exit(1);      }      closesocket(sock);      printf("Connection from %d, port %dn", sa_cli.sin_addr.s_addr, sa_cli.sin_port);      /* TCP 链接已建立.开始 SSL 握手过程.......................... */      //绑定套接字      ssl = SSL_new(ctx);      if (NULL == ssl)          exit(1);      if (0 &gt;= SSL_set_fd(ssl, ss)) {          printf("Attach to Line fail!n");          exit(1);      }      //SSL握手      //SSL_accept(ssl);      int k = SSL_accept(ssl);      if (0 &gt;= k) {          printf("%dn", k);          printf("SSL connect fail!n");          exit(1);      }      //进行信息验证      X509 *client_cert;      client_cert = SSL_get_peer_certificate(ssl);      printf("发现客户端尝试连接n");      if (client_cert != NULL) {          printf ("Client certificate:n");          //读取证书subject名并显示          char *str = X509_NAME_oneline(X509_get_subject_name(client_cert), 0, 0);          if (NULL == str) {              printf("认证出错!n");              exit(1);          }          printf("subject: %sn", str);          //读取证书的issuer名并显示          str = X509_NAME_oneline(X509_get_issuer_name(client_cert), 0, 0);          if (NULL == str) {              printf("证书名为空n");              exit(1);          }          printf("issuer: %sn", str);          printf("连接成功n");          X509_free (client_cert);/*如不再需要,需将证书释放 */          OPENSSL_free(str);      }      else {          printf("找不到客户端的认证证书n");          exit(1);      }      char buf[MSGLENGTH];      SSL_write(ssl, "Server is connect to you!n", strlen("Server is connect to you!n"));      printf("Listen to the client: n");      while (1) {          err = SSL_read(ssl, buf, sizeof(buf));          buf[err] = '';          printf("%sn", buf);      }      //关闭套接字      SSL_shutdown(ssl);      SSL_free(ssl);      SSL_CTX_free(ctx);      WSACleanup();      getch();      return 0;  } 

(6)进行数据传输

    经过前面的一系列过程后,就可以进行安全的数据传输了。在数据传输阶段,需要使用SSL_read( )和SSL_write( )来代替普通流套接字所使用的read( )和write( )函数,以此完成对SSL套接字的读写操作,两个新函数的原型分别如下:

int SSL_read(SSL *ssl,void *buf,int num);            //从SSL套接字读取数据
int SSL_write(SSL *ssl,const void *buf,int num);     //向SSL套接字写入数据

2.4 完成SSL握手

  在这一步,我们需要在普通TCP连接的基础上,建立SSL连接。与普通流套接字建立连接的过程类似:Client使用函数SSL_connect()【类似于流套接字中用的connect发起握手,而Server使用函数SSL_ accept()【类似于流套接字中用的accept对握手进行响应,从而完成握手过程。两函数原型如下:

int SSL_connect;int SSL_accept;

  握手过程完成之后,Client通常会要求Server发送证书信息,以便对Server进行鉴别。其实现会用到以下两个函数:

X509 *SSL_get_peer_certificate;  //从SSL套接字中获取对方的证书信息X509_NAME *X509_get_subject_name; //得到证书所用者的名字

/* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */

用openssl编写SSL,TLS程序 - bfcyyb - 51CTO技术博客 - Google Chrome (2013/5/31 11:29:25)

(7)会话结束

    当Client和Server之间的通信过程完成后,就使用以下函数来释放前面过程中申请的SSL资源:

int SSL_shutdown(SSL *ssl);       //关闭SSL套接字
void SSl_free(SSL *ssl);          //释放SSL套接字
void SSL_CTX_free(SSL_CTX *ctx);  //释放SSL会话环境

 

2.5 数据传输

  经过前面的一系列过程后,就可以进行安全的数据传输了。在数据传输阶段,需要使用SSL_read和SSL_write来代替普通流套接字所使用的read和write函数,以此完成对SSL套接字的读写操作,两个新函数的原型分别如下:

int SSL_read(SSL *ssl,void *buf,int num);            //从SSL套接字读取数据int SSL_write(SSL *ssl,const void *buf,int num);     //向SSL套接字写入数据

 pSockInfo->pSSL = SSL_new(pSockInfo->pSslCtx);申请一个SSL 套节字;

用openssl编写SSL,TLS程序 - bfcyyb - 51CTO技术博客 - Google Chrome (2013/5/31 11:29:25)

用openssl编写SSL,TLS程序

2006-05-24 11:15:41

标签:openssl SSL TLS程序 休闲 职场

SSL(Secure Socket Layer)是netscape公司提出的主要用于web的安全通信标准,分为2.0版和3.0版.TLS(Transport Layer Security)是IETF的TLS 工作组在SSL3.0基础之上提出的安全通信标准,目前版本是1.0,即RFC2246.SSL/TLS提供的安全机制可以保证应用层数据在互联网络传输不 被监听,伪造和窜改.
一:简介:

SSL(Secure Socket Layer)是netscape公司提出的主要用于web的安全通信标准,分为2.0版和3.0版.TLS(Transport Layer Security)是IETF的TLS 工作组在SSL3.0基础之上提出的安全通信标准,目前版本是1.0,即RFC2246.SSL/TLS提供的安全机制可以保证应用层数据在互联网络传输不 被监听,伪造和窜改.

openssl(www.openssl.org)是sslv2,sslv3,tlsv1的一份完整实现,内部包含了大量加密算法程序.其命令行提供了丰富的加密,验证,证书生成等功 能,甚至可以用其建立一个完整的CA.与其同时,它也提供了一套完整的库函数,可用开发用SSL/TLS的通信程序. Apache的https两种版本 mod_ssl和apachessl均基于它实现的.openssl继承于ssleay,并做了一定的扩展,当前的版本是0.9.5a.

openssl的缺点是文档太少,连一份完整的函数说明都没有,man page也至今没做完整:-(,如果想用它编程序,除了熟悉已有的文档(包括 ssleay,mod_ssl,apachessl的文档)外,可以到它的maillist上找相关的帖子,许多问题可以在以前的文章中找到答案.

编程:
程序分为两部分,客户端和服务器端,我们的目的是利用SSL/TLS的特性保证通信双方能够互相验证对方身份(真实性),并保证数据的完整性, 私密性.

1.客户端程序的框架为:

/*生成一个SSL结构*/
meth = SSLv23_client_method();
ctx = SSL_CTX_new (meth); 
ssl = SSL_new(ctx);

/*下面是正常的socket过程*/
fd = socket();
connect();

/*把建立好的socket和SSL结构联系起来*/
SSL_set_fd(ssl,fd);

/*SSL的握手过程*/
SSL_connect(ssl);

/*接下来用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_write(ssl,"Hello world",strlen("Hello World!"));

2.服务端程序的框架为:

/*生成一个SSL结构*/
meth = SSLv23_server_method();
ctx = SSL_CTX_new (meth); 
ssl = SSL_new(ctx);

/*下面是正常的socket过程*/
fd = socket();
bind();
listen();
accept();

/*把建立好的socket和SSL结构联系起来*/
SSL_set_fd(ssl,fd);

/*SSL的握手过程*/
SSL_connect(ssl);

/*接下来用SSL_write(), SSL_read()代替原有的write(),read()即可*/
SSL_read (ssl, buf, sizeof(buf));

根据RFC2246(TLS1.0)整个TLS(SSL)的流程如下:

Client Server

ClientHello -------->
ServerHello
Certificate*
ServerKeyExchange*
CertificateRequest*
<-------- ServerHelloDone
Certificate*
ClientKeyExchange
CertificateVerify*
[ChangeCipherSpec]
Finished -------->
[ChangeCipherSpec]
<-------- Finished
Application Data <-------> Application Data

对程序来说,openssl将整个握手过程用一对函数体现,即客户端的SSL_connect和服务端的SSL_accept.而后的应用层数据交换则用SSL_read和 SSL_write来完成.

二:证书文件生成

除将程序编译成功外,还需生成必要的证书和私钥文件使双方能够成功验证对方,步骤如下:

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等地方由它验证,要交一大笔钱,何不自己做CA呢.
首先生成CA的key文件:
openssl -des3 -out ca.key 1024
在生成CA自签名的证书:
openssl req -new -x509 -key ca.key -out ca.crt
如果想让此证书有个期限,如一年,则加上"-days 365".
("如果非要为这个证书加上一个期限,我情愿是..一万年")

5.用生成的CA的证书为刚才生成的server.csr,client.csr文件签名:
可以用openssl中CA系列命令,但不是很好用(也不是多难,唉,一言难尽),一篇文章中推荐用mod_ssl中的sign.sh脚本,试了一下,确实方便了不 少,如果ca.csr存在的话,只需:
./sigh.sh server.csr
./sign.sh client.csr
相应的证书便生成了(后缀.crt).

现在我们所需的全部文件便生成了.

其实openssl中还附带了一个叫CA.pl的文件(在安装目录中的misc子目录下),可用其生成以上的文件,使用也比较方便,但此处就不作介绍了.

三:需要了解的一些函数:

1.int SSL_CTX_set_cipher_list(SSL_CTX *,const char *str);
根据SSL/TLS规范,在ClientHello中,客户端会提交一份自己能够支持的加密方法的列表,由服务端选择一种方法后在ServerHello中通知服务端, 从而完成加密算法的协商.

可用的算法为:
EDH-RSA-DES-CBC3-SHA
EDH-DSS-DES-CBC3-SHA
DES-CBC3-SHA
DHE-DSS-RC4-SHA
IDEA-CBC-SHA
RC4-SHA
RC4-MD5
EXP1024-DHE-DSS-RC4-SHA
EXP1024-RC4-SHA
EXP1024-DHE-DSS-DES-CBC-SHA
EXP1024-DES-CBC-SHA
EXP1024-RC2-CBC-MD5
EXP1024-RC4-MD5
EDH-RSA-DES-CBC-SHA
EDH-DSS-DES-CBC-SHA
DES-CBC-SHA
EXP-EDH-RSA-DES-CBC-SHA
EXP-EDH-DSS-DES-CBC-SHA
EXP-DES-CBC-SHA
EXP-RC2-CBC-MD5
EXP-RC4-MD5
这些算法按一定优先级排列,如果不作任何指定,将选用DES-CBC3-SHA.用SSL_CTX_set_cipher_list可以指定自己希望用的算法(实际上只是 提高其优先级,是否能使用还要看对方是否支持).

我们在程序中选用了RC4做加密,MD5做消息摘要(先进行MD5运算,后进行RC4加密).即
SSL_CTX_set_cipher_list(ctx,"RC4-MD5");

在消息传输过程中采用对称加密(比公钥加密在速度上有极大的提高),其所用秘钥(shared secret)在握手过程中中协商(每次对话过程均不同, 在一次对话中都有可能有几次改变),并通过公钥加密的手段由客户端提交服务端.

2.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的话,客户端连证书都不会发过来.

3.int SSL_CTX_load_verify_locations(SSL_CTX *ctx, const char *CAfile,const char *CApath);
要验证对方的话,当然装要有CA的证书了,此函数用来便是加载CA的证书文件的.

4.int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);
加载自己的证书文件.

5.int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);
加载自己的私钥,以用于签名.

6.int SSL_CTX_check_private_key(SSL_CTX *ctx);
调用了以上两个函数后,自己检验一下证书与私钥是否配对.

7.void RAND_seed(const void *buf,int num);
在win32的环境中client程序运行时出错(SSL_connect返回-1)的一个主要机制便是与UNIX平台下的随机数生成机制不同(握手的时候用的到). 具体描述可见mod_ssl的FAQ.解决办法就是调用此函数,其中buf应该为一随机的字符串,作为"seed".
还可以采用一下两个函数:
void RAND_screen(void);
int RAND_event(UINT, WPARAM, LPARAM);
其中RAND_screen()以屏幕内容作为"seed"产生随机数,RAND_event可以捕获windows中的事件(event),以此为基础产生随机数.如果一直有 用户干预的话,用这种办法产生的随机数能够"更加随机",但如果机器一直没人理(如总停在登录画面),则每次都将产生同样的数字.

这几个函数都只在WIN32环境下编译时有用,各种UNIX下就不必调了.
大量其他的相关函数原型,见cryptorandrand.h.

8.OpenSSL_add_ssl_algorithms()或SSLeay_add_ssl_algorithms()
其实都是调用int SSL_library_init(void)
进行一些必要的初始化工作,用openssl编写SSL/TLS程序的话第一句便应是它.

9.void SSL_load_error_strings(void );
如果想打印出一些方便阅读的调试信息的话,便要在一开始调用此函数.

10.void ERR_print_errors_fp(FILE *fp);
如果调用了SSL_load_error_strings()后,便可以随时用ERR_print_errors_fp()来打印错误信息了.

11.X509 *SSL_get_peer_certificate(SSL *s);
握手完成后,便可以用此函数从SSL结构中提取出对方的证书(此时证书得到且已经验证过了)整理成X509结构.

12.X509_NAME *X509_get_subject_name(X509 *a);
得到证书所有者的名字,参数可用通过SSL_get_peer_certificate()得到的X509对象.

13.X509_NAME *X509_get_issuer_name(X509 *a)
得到证书签署者(往往是CA)的名字,参数可用通过SSL_get_peer_certificate()得到的X509对象.

14.char *X509_NAME_oneline(X509_NAME *a,char *buf,int size);
将以上两个函数得到的对象变成字符型,以便打印出来.

15.SSL_METHOD的构造函数,包括
SSL_METHOD *TLSv1_server_method(void); /* TLSv1.0 */
SSL_METHOD *TLSv1_client_method(void); /* TLSv1.0 */

SSL_METHOD *SSLv2_server_method(void); /* SSLv2 */
SSL_METHOD *SSLv2_client_method(void); /* SSLv2 */

SSL_METHOD *SSLv3_server_method(void); /* SSLv3 */
SSL_METHOD *SSLv3_client_method(void); /* SSLv3 */

SSL_METHOD *SSLv23_server_method(void); /* SSLv3 but can rollback to v2 */
SSL_METHOD *SSLv23_client_method(void); /* SSLv3 but can rollback to v2 */
在程序中究竟采用哪一种协议(TLSv1/SSLv2/SSLv3),就看调哪一组构造函数了.

四:程序源代码(WIN32版本):

基本上是改造的openssl自带的demos目录下的cli.cpp,serv.cpp文件,做了一些修改,并增加了一些功能.

/******************************************************************************************
*SSL/TLS客户端程序WIN32版(以demos/cli.cpp为基础)
*需要用到动态连接库libeay32.dll,ssleay.dll,
*同时在setting中加入ws2_32.lib libeay32.lib ssleay32.lib,
*以上库文件在编译openssl后可在out32dll目录下找到,
*所需证书文件请参照文章自行生成*/
******************************************************************************************/
#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>

#include <winsock2.h>

#include "openssl/rsa.h" 
#include "openssl/crypto.h"
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
#include "openssl/rand.h"

/*所有需要的参数信息都在此处以#define的形式提供*/
#define CERTF "client.crt" /*客户端的证书(需经CA签名)*/
#define KEYF "client.key" /*客户端的私钥(建议加密存储)*/
#define CACERT "ca.crt" /*CA 的证书*/
#define PORT 1111 /*服务端的端口*/
#define SERVER_ADDR "127.0.0.1" /*服务段的IP地址*/

#define CHK_NULL(x) if ((x)==NULL) exit (-1)
#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(-2); }
#define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(-3); }

int main ()
{
int err;
int sd;
struct sockaddr_in sa;
SSL_CTX* ctx;
SSL* ssl;
X509* server_cert;
char* str;
char buf [4096];
SSL_METHOD *meth;
int seed_int[100]; /*存放随机序列*/

WSADATA wsaData;

if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0){
printf("WSAStartup()fail:%dn",GetLastError());
return -1;

OpenSSL_add_ssl_algorithms(); /*初始化*/
SSL_load_error_strings(); /*为打印调试信息作准备*/

meth = TLSv1_client_method(); /*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/
ctx = SSL_CTX_new (meth); 
CHK_NULL(ctx);

SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); /*验证与否*/
SSL_CTX_load_verify_locations(ctx,CACERT,NULL); /*若验证,则放置CA证书*/

if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(-2);
}
if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(-3);
}

if (!SSL_CTX_check_private_key(ctx)) {
printf("Private key does not match the certificate public keyn");
exit(-4);

/*构建随机数生成机制,WIN32平台必需*/
srand( (unsigned)time( NULL ) );
for( int i = 0; i < 100;i++ )
seed_int[i] = rand();
RAND_seed(seed_int, sizeof(seed_int));

/*以下是正常的TCP socket建立过程 .............................. */
printf("Begin tcp socket...n");

sd = socket (AF_INET, SOCK_STREAM, 0); CHK_ERR(sd, "socket");

memset (&sa, '', sizeof(sa));
sa.sin_family = AF_INET;
sa.sin_addr.s_addr = inet_addr (SERVER_ADDR); /* Server IP */
sa.sin_port = htons (PORT); /* Server Port number */

err = connect(sd, (struct sockaddr*) &sa,
sizeof(sa)); 
CHK_ERR(err, "connect");

/* TCP 链接已建立.开始 SSL 握手过程.......................... */
printf("Begin SSL negotiation n");

ssl = SSL_new (ctx); 
CHK_NULL(ssl);

SSL_set_fd (ssl, sd);
err = SSL_connect (ssl);
CHK_SSL(err);

/*打印所有加密算法的信息(可选)*/
printf ("SSL connection using %sn", SSL_get_cipher (ssl));

/*得到服务端的证书并打印些信息(可选) */
server_cert = SSL_get_peer_certificate (ssl); 
CHK_NULL(server_cert);
printf ("Server certificate:n");

str = X509_NAME_oneline (X509_get_subject_name (server_cert),0,0);
CHK_NULL(str);
printf ("t subject: %sn", str);
Free (str);

str = X509_NAME_oneline (X509_get_issuer_name (server_cert),0,0);
CHK_NULL(str);
printf ("t issuer: %sn", str);
Free (str);

X509_free (server_cert); /*如不再需要,需将证书释放 */

/* 数据交换开始,用SSL_write,SSL_read代替write,read */
printf("Begin SSL data exchangen");

err = SSL_write (ssl, "Hello World!", strlen("Hello World!")); 
CHK_SSL(err);

err = SSL_read (ssl, buf, sizeof(buf) - 1); 
CHK_SSL(err);

buf[err] = '';
printf ("Got %d chars:'%s'n", err, buf);
SSL_shutdown (ssl); /* send SSL/TLS close_notify */

/* 收尾工作 */
shutdown (sd,2);
SSL_free (ssl);
SSL_CTX_free (ctx);

return 0;
}
/***************************************************************************************
* EOF - cli.cpp
***************************************************************************************/

/***************************************************************************************
*SSL/TLS服务端程序WIN32版(以demos/server.cpp为基础)
*需要用到动态连接库libeay32.dll,ssleay.dll,
*同时在setting中加入ws2_32.lib libeay32.lib ssleay32.lib,
*以上库文件在编译openssl后可在out32dll目录下找到,
*所需证书文件请参照文章自行生成.
***************************************************************************************/

#include <stdio.h>
#include <stdlib.h>
#include <memory.h>
#include <errno.h>
#include <sys/types.h>

#include <winsock2.h>

#include "openssl/rsa.h" 
#include "openssl/crypto.h"
#include "openssl/x509.h"
#include "openssl/pem.h"
#include "openssl/ssl.h"
#include "openssl/err.h"

/*所有需要的参数信息都在此处以#define的形式提供*/
#define CERTF "server.crt" /*服务端的证书(需经CA签名)*/
#define KEYF "server.key" /*服务端的私钥(建议加密存储)*/
#define CACERT "ca.crt" /*CA 的证书*/
#define PORT 1111 /*准备绑定的端口*/

#define CHK_NULL(x) if ((x)==NULL) exit (1)
#define CHK_ERR(err,s) if ((err)==-1) { perror(s); exit(1); }
#define CHK_SSL(err) if ((err)==-1) { ERR_print_errors_fp(stderr); exit(2); }

int main ()
{
int err;
int listen_sd;
int sd;
struct sockaddr_in sa_serv;
struct sockaddr_in sa_cli;
int client_len;
SSL_CTX* ctx;
SSL* ssl;
X509* client_cert;
char* str;
char buf [4096];
SSL_METHOD *meth;
WSADATA wsaData;

if(WSAStartup(MAKEWORD(2,2),&wsaData) != 0){
printf("WSAStartup()fail:%dn",GetLastError());
return -1;
}

SSL_load_error_strings(); /*为打印调试信息作准备*/
OpenSSL_add_ssl_algorithms(); /*初始化*/
meth = TLSv1_server_method(); /*采用什么协议(SSLv2/SSLv3/TLSv1)在此指定*/

ctx = SSL_CTX_new (meth); 
CHK_NULL(ctx);

SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER,NULL); /*验证与否*/
SSL_CTX_load_verify_locations(ctx,CACERT,NULL); /*若验证,则放置CA证书*/

if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(3);
}
if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0) {
ERR_print_errors_fp(stderr);
exit(4);
}

if (!SSL_CTX_check_private_key(ctx)) {
printf("Private key does not match the certificate public keyn");
exit(5);
}

SSL_CTX_set_cipher_list(ctx,"RC4-MD5"); 

/*开始正常的TCP socket过程.................................*/
printf("Begin TCP socket...n");

listen_sd = socket (AF_INET, SOCK_STREAM, 0); 
CHK_ERR(listen_sd, "socket");

memset (&sa_serv, '', sizeof(sa_serv));
sa_serv.sin_family = AF_INET;
sa_serv.sin_addr.s_addr = INADDR_ANY;
sa_serv.sin_port = htons (PORT); 

err = bind(listen_sd, (struct sockaddr*) &sa_serv,
sizeof (sa_serv)); 
CHK_ERR(err, "bind");

/*接受TCP链接*/
err = listen (listen_sd, 5); 
CHK_ERR(err, "listen");

client_len = sizeof(sa_cli);
sd = accept (listen_sd, (struct sockaddr*) &sa_cli, &client_len);
CHK_ERR(sd, "accept");
closesocket (listen_sd);

printf ("Connection from %lx, port %xn",
sa_cli.sin_addr.s_addr, sa_cli.sin_port);

/*TCP连接已建立,进行服务端的SSL过程. */
printf("Begin server side SSLn");

ssl = SSL_new (ctx); 
CHK_NULL(ssl);
SSL_set_fd (ssl, sd);
err = SSL_accept (ssl);
printf("SSL_accept finishedn");
CHK_SSL(err);

/*打印所有加密算法的信息(可选)*/
printf ("SSL connection using %sn", SSL_get_cipher (ssl));

/*得到服务端的证书并打印些信息(可选) */
client_cert = SSL_get_peer_certificate (ssl);
if (client_cert != NULL) {
printf ("Client certificate:n");

str = X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0);
CHK_NULL(str);
printf ("t subject: %sn", str);
Free (str);

str = X509_NAME_oneline (X509_get_issuer_name (client_cert), 0, 0);
CHK_NULL(str);
printf ("t issuer: %sn", str);
Free (str);

X509_free (client_cert);/*如不再需要,需将证书释放 */
}
else
printf ("Client does not have certificate.n");

/* 数据交换开始,用SSL_write,SSL_read代替write,read */
err = SSL_read (ssl, buf, sizeof(buf) - 1); 
CHK_SSL(err);
buf[err] = '';
printf ("Got %d chars:'%s'n", err, buf);

err = SSL_write (ssl, "I hear you.", strlen("I hear you.")); 
CHK_SSL(err);

/* 收尾工作*/
shutdown (sd,2);
SSL_free (ssl);
SSL_CTX_free (ctx);

return 0;
}
/*****************************************************************
* EOF - serv.cpp
*****************************************************************/

五.参考文献

1.SSL规范(draft302)
2.TLS标准(rfc2246)
3.openssl源程序及文档
4.SSLeay Programmer Reference
5.Introducing SSL and Certificates using SSLeay


二、简介与概述

    OpenSSL是一个开源的代码库,它所实现的所有SSL相关功能都可以在C、C++语言中直接使用。OpenSSL具有以下优点[16]:

  • 采用C语言开发,支持多种操作系统,可移植性好;
  • 功能全面,支持大部分主流密码算法、相关标准协议和SSL协议;
  • 开放源代码,应用者能很好地了解算法实现过程;
  • 具备应用程序(即OpenSSL指令),既可以直接使用,也可以进行二次开发;
  • 免费使用,能够用于商业和非商业。

    OpenSSL整个软件包主要分为三个部分:密码算法库、SSL协议库以及应用程序。其中,密码算法库是OpenSSL的核心,SSL协议库是在密码算法库的基础上对SSL协议的实现,应用程序是直接使用OpenSSL各种功能的接口,包括密码算法相关功能与SSL协议相关功能。这三个部分的关系如图2.1所示。

图片 4

图2.1 OpenSSL套件各组成部分的关系

密码算法库

    OpenSSL的密码算法库实现了大部分主流密码算法,包括对称密码算法(AES算法、DES算法、Blowfish算法、CAST算法、IDEA算法、RC2算法、RC5算法与RC4算法)、非对称密码算法(DH算法、RSA算法、DSA算法与EC算法)和信息摘要算法(MD2算法、MD5算法、MDC2算法、SHA算法、SHA1算法和RIPEMD算法),并提供了对数字证书和密钥的管理接口。

SSL协议库

    OpenSSL的SSL协议库实现了SSL 2.0、SSL 3.0和TLS 1.0。该库提供了丰富的API函数,这些API函数将SSL协议的处理细节进行了完美的封装。当使用SSL协议库进行SSL应用程序开发时,不需要关心如何交换SSL握手消息,如何使用会话密钥加解密通信数据等细节问题,而只需考虑如何实现程序的实际功能。

应用程序

    OpenSSL的应用程序是基于OpenSSL的密码算法库和SSL协议库写成的,它已经成为了OpenSSL重要的一个组成部分。通过调用OpenSSL的相应指令,可以使用密钥生成、证书管理、格式转换、数据加密和签名、SSL测试以及其它辅助配置功能。

 

------本文由CSDN-蚍蜉撼青松【主页:http://blog.csdn.net/howeverpf】整理编辑,转载请注明出处!------

 

2.6 会话结束

  当Client和Server之间的通信过程完成后,就使用以下函数来释放前面过程中申请的SSL资源:

int SSL_shutdown;       //关闭SSL套接字void SSl_free;          //释放SSL套接字void SSL_CTX_free(SSL_CTX *ctx);  //释放SSL会话环境

SSL_set_fd(pSockInfo->pSSL, pSockInfo->sockFd);把SSL attach 到已经连接的套接字上了

openssl编译问题 - C/C++ - ChinaUnix.net - - Google Chrome (2013/5/31 14:44:39)

#include <stdio.h>
#include <string.h>
#include <errno.h>
#include <sys/socket.h>
#include <resolv.h>
#include <stdlib.h>
#include <netinet/in.h>
#include <arpa/inet.h>
#include <unistd.h>
#include <openssl/ssl.h>
#include <openssl/err.h>

#define MAXBUF 1024

void ShowCerts(SSL * ssl)
{
    X509 *cert;
    char *line;

    cert = SSL_get_peer_certificate(ssl);
    if (cert != NULL) {
        printf("数字证书信息:n");
        line = X509_NAME_oneline(X509_get_subject_name(cert), 0, 0);
        printf("证书: %sn", line);
        free(line);
        line = X509_NAME_oneline(X509_get_issuer_name(cert), 0, 0);
        printf("颁发者: %sn", line);
        free(line);
        X509_free(cert);
    } else
        printf("无证书信息!n");
}
/************关于本文档********************************************
*filename: ssl-client.c
*purpose: 演示利用 OpenSSL 库进行基于 IP层的 SSL 加密通讯的方法,这是客户端例子
*wrote by: zhoulifa(zhoulifa@163.com) 周立发(http://zhoulifa.bokee.com)
Linux爱好者 Linux知识传播者 SOHO族 开发者 最擅长C语言
*date time:2007-02-02 20:10
*Note: 任何人可以任意复制代码并运用这些文档,当然包括你的商业用途
* 但请遵循GPL
*Thanks to:Google
*Hope:希望越来越多的人贡献自己的力量,为科学技术发展出力
* 科技站在巨人的肩膀上进步更快!感谢有开源前辈的贡献!
*********************************************************************/
int main(int argc, char **argv)
{
    int sockfd, len;
    struct sockaddr_in dest;
    char buffer[MAXBUF + 1];
    SSL_CTX *ctx;
    SSL *ssl;

    if (argc != 3) {
        printf
            ("参数格式错误!正确用法如下:ntt%s IP地址 端口nt比如:t%s 127.0.0.1 80n此程序用来从某个 IP 地址的服务器某个端口接收最多 MAXBUF 个字节的消息",
             argv[0], argv[0]);
        exit(0);
    }

    /* SSL 库初始化,参看 ssl-server.c 代码 */
    SSL_library_init();
    OpenSSL_add_all_algorithms();
    SSL_load_error_strings();
    ctx = SSL_CTX_new(SSLv23_client_method());
    if (ctx == NULL) {
        ERR_print_errors_fp(stdout);
        exit(1);
    }

    /* 创建一个 socket 用于 tcp 通信 */
    if ((sockfd = socket(AF_INET, SOCK_STREAM, 0)) < 0) {
        perror("Socket");
        exit(errno);
    }
    printf("socket createdn");

    /* 初始化服务器端(对方)的地址和端口信息 */
    bzero(&dest, sizeof(dest));
    dest.sin_family = AF_INET;
    dest.sin_port = htons(atoi(argv[2]));
    if (inet_aton(argv[1], (struct in_addr *) &dest.sin_addr.s_addr) == 0) {
        perror(argv[1]);
        exit(errno);
    }
    printf("address createdn");

    /* 连接服务器 */
    if (connect(sockfd, (struct sockaddr *) &dest, sizeof(dest)) != 0) {
        perror("Connect ");
        exit(errno);
    }
    printf("server connectedn");

    /* 基于 ctx 产生一个新的 SSL */
    ssl = SSL_new(ctx);
    SSL_set_fd(ssl, sockfd);
    /* 建立 SSL 连接 */
    if (SSL_connect(ssl) == -1)
        ERR_print_errors_fp(stderr);
    else {
        printf("Connected with %s encryptionn", SSL_get_cipher(ssl));
        ShowCerts(ssl);
    }

   /* 发消息给服务器 */

    sprintf(buffer, "GET %s HTTP/1.1rnrn", "/accounts/ClientAuth?Email=jinhui1231&Passwd=hao1234567&PersistentCookie=false&source=googletalk");
    len = SSL_write(ssl, buffer, strlen(buffer));
    if (len < 0)
        printf
            ("消息'%s'发送失败!错误代码是%d,错误信息是'%s'n",
             buffer, errno, strerror(errno));
    else
        printf("消息'%s'发送成功,共发送了%d个字节!n",
               buffer, len);

#if 1
    /* 接收对方发过来的消息,最多接收 MAXBUF 个字节 */
    bzero(buffer, MAXBUF + 1);
    /* 接收服务器来的消息 */
    len = SSL_read(ssl, buffer, MAXBUF);
    if (len > 0)
        printf("接收消息成功:'%s',共%d个字节的数据n",
               buffer, len);
    else {
        printf("消息接收失败!错误代码是%d,错误信息是'%s'n",
             errno, strerror(errno));
        goto finish;
    }
  //  bzero(buffer, MAXBUF + 1);
   // strcpy(buffer, "from client->server");
#endif

  finish:
    /* 关闭连接 */
    SSL_shutdown(ssl);
    SSL_free(ssl);
    close(sockfd);
    SSL_CTX_free(ctx);
    return 0;
}

3. SSL 和 TLS

  HTTPS 使用 SSL(Secure Socket Layer) 和 TLS(Transport LayerSecurity)这两个协议。 SSL 技术最初是由浏览器开发商网景通信公司率先倡导的,开发过 SSL3.0之前的版本。目前主导权已转移到 IETF(Internet Engineering Task Force,Internet 工程任务组)的手中。

  IETF 以 SSL3.0 为基准,后又制定了 TLS1.0、TLS1.1 和 TLS1.2。TSL 是以SSL 为原型开发的协议,有时会统一称该协议为 SSL。当前主流的版本是SSL3.0 和 TLS1.0。

  由于 SSL1.0 协议在设计之初被发现出了问题,就没有实际投入使用。SSL2.0 也被发现存在问题,所以很多浏览器直接废除了该协议版本。

ret = SSL_connect(pSockInfo->pSSL);//成功创建SSL 套接字后,客户端应使用函数SSL_connect( )替代传统的函数connect( )

使用 OpenSSL API 进行安全编程 - JORDANSG的个人空间 - 开源中国社区 - Google Chrome (2013/5/30 17:45:09)

4. Nebula中的SSL通讯实现

  Nebula框架同时支持SSL服务端应用和SSL客户端应用,对openssl的初始化只需要初始化一次即可只需调用一次)。Nebula框架的SSL相关代码(包括客户端和服务端的实现)都封装在SocketChannelSslImpl这个类中。Nebula的SSL通信是基于异步非阻塞的socket通信,并且不使用openssl的BIO(因为没有必要,代码还更复杂了)。

  SocketChannelSslImpl是SocketChannelImpl的派生类,在SocketChannelImpl常规TCP通信之上增加了SSL通信层,两个类的调用几乎没有差异。SocketChannelSslImpl类声明如下:

class SocketChannelSslImpl : public SocketChannelImpl{public:    SocketChannelSslImpl(SocketChannel* pSocketChannel, std::shared_ptr<NetLogger> pLogger, int iFd, uint32 ulSeq, ev_tstamp dKeepAlive = 0.0);    virtual ~SocketChannelSslImpl();    static int SslInit(std::shared_ptr<NetLogger> pLogger);    static int SslServerCtxCreate(std::shared_ptr<NetLogger> pLogger);    static int SslServerCertificate(std::shared_ptr<NetLogger> pLogger,                const std::string& strCertFile, const std::string& strKeyFile);    static void SslFree();    int SslClientCtxCreate();    int SslCreateConnection();    int SslHandshake();    int SslShutdown();    virtual bool Init(E_CODEC_TYPE eCodecType, bool bIsClient = false) override;    // 覆盖基类的Send()方法,实现非阻塞socket连接建立后继续建立SSL连接,并收发数据    virtual E_CODEC_STATUS Send() override;          virtual E_CODEC_STATUS Send(int32 iCmd, uint32 uiSeq, const MsgBody& oMsgBody) override;    virtual E_CODEC_STATUS Send(const HttpMsg& oHttpMsg, uint32 ulStepSeq) override;    virtual E_CODEC_STATUS Recv(MsgHead& oMsgHead, MsgBody& oMsgBody) override;    virtual E_CODEC_STATUS Recv(HttpMsg& oHttpMsg) override;    virtual E_CODEC_STATUS Recv(MsgHead& oMsgHead, MsgBody& oMsgBody, HttpMsg& oHttpMsg) override;    virtual bool Close() override;protected:    virtual int Write(CBuffer* pBuff, int& iErrno) override;    virtual int Read(CBuffer* pBuff, int& iErrno) override;private:    E_SSL_CHANNEL_STATUS m_eSslChannelStatus;   //在基类m_ucChannelStatus通道状态基础上增加SSL通道状态    bool m_bIsClientConnection;    SSL* m_pSslConnection;    static SSL_CTX* m_pServerSslCtx;    //当打开ssl选项编译,启动Nebula服务则自动创建    static SSL_CTX* m_pClientSslCtx;    //默认为空,当打开ssl选项编译并且第一次发起了对其他SSL服务的连接时(比如访问一个https地址)创建};

  SocketChannelSslImpl类中带override关键字的方法都是覆盖基类SocketChannelImpl的同名方法,也是实现SSL通信与非SSL通信调用透明的关键。不带override关键字的方法都是SSL通信相关方法,这些方法里有openssl的函数调用。不带override的方法中有静态和非静态之分,静态方法在进程中只会被调用一次,与具体Channel对象无关。SocketChannel外部不需要调用非静态的ssl相关方法。

  因为是非阻塞的socket,SSL_do_handshake()和SSL_write()、SSL_read()返回值并不完全能判断是否出错,还需要SSL_get_error()获取错误码。SSL_ERROR_WANT_READ和SSL_ERROR_WANT_WRITE都是正常的。

  网上的大部分openssl例子程序是按顺序调用openssl函数简单实现同步ssl通信,在非阻塞IO应用中,ssl通信要复杂许多。SocketChannelSslImpl实现的是非阻塞的ssl通信,从该类的实现上看整个通信过程并非完全线性的。下面的SSL通信图更清晰地说明了Nebula框架中SSL通信是如何实现的:

图片 5

  SocketChannelSslImpl中的静态方法在进程生命期内只需调用一次,也可以理解成SSL_CTX_new()、SSL_CTX_free()等方法只需调用一次。更进一步理解SSL_CTX结构体在进程内只需要创建一次(在Nebula中分别为Server和Client各创建一个)就可以为所有SSL连接所用;当然,为每个SSL连接创建独立的SSL_CTX也没问题(Nebula 0.4中实测过为每个Client创建独立的SSL_CTX),但一般不这么做,因为这样会消耗更多的内存资源,并且效率也会更低。

  建立SSL连接时,客户端调用SSL_connect(),服务端调用SSL_accept(),许多openssl的demo都是这么用的。Nebula中用的是SSL_do_handshake(),这个方法同时适用于客户端和服务端,在兼具client和server功能的服务更适合用SSL_do_handshake()。注意调用SSL_do_handshake()前,如果是client端需要先调用SSL_set_connect_state(),如果是server端则需要先调用SSL_set_accept_state()。非阻塞IO中,SSL_do_handshake()可能需要调用多次才能完成握手,具体调用时机需根据SSL_get_error()获取错误码SSL_ERROR_WANT_READ和SSL_ERROR_WANT_WRITE判断需监听读事件还是写事件,在对应事件触发时再次调用SSL_do_handshake()。详细实现请参考SocketChannelSslImpl的Send和Recv方法。

  关闭SSL连接时先调用SSL_shutdown()正常关闭SSL层连接(非阻塞IO中SSL_shutdown()亦可能需要调用多次)再调用SSL_free()释放SSL连接资源,最后关闭socket连接。SSL_CTX无须释放。整个SSL通信顺利完成,Nebula 0.4在开多个终端用shell脚本死循环调用curl简单压测中SSL client和SSL server功能一切正常:

while :do      curl -v -k -H "Content-Type:application/json" -X POST -d '{"hello":"nebula ssl test"}' https://192.168.157.168:16003/test_ssl done

  测试方法如下图:

图片 6

  查看资源使用情况,SSL Server端的内存使用一直在增长,疑似有内存泄漏,不过pmap -d查看某一项anon内存达到近18MB时不再增长,说明可能不是内存泄漏,只是部分内存被openssl当作cache使用了。这个问题网上没找到解决办法。从struct ssl_ctx_st结构体定义发现端倪,再从nginx源码中发现了SSL_CTX_remove_session(),于是在SSL_free()之前加上SSL_CTX_remove_session()。session复用可以提高SSL通信效率,不过Nebula暂时不需要。

  这种测试方法把NebulaInterface作为SSL服务端,NebulaLogic作为SSL客户端,同时完成了Nebula框架SSL服务端和客户端功能测试,简单的压力测试。Nebula框架的SSL通信测试通过,也可以投入生产应用,在后续应用中肯定还会继续完善。openssl真的难用,难怪被吐槽那么多,或许不久之后的Nebula版本将用其他ssl库替换掉openssl。

//来完成握手过程

使用 OpenSSL API 进行安全编程

1人收藏此文章, 我要收藏发表于9个月前(2012-08-27 22:21) , 已有52次阅读 ,共0个评论

OpenSSL API 的文档有些含糊不清。因为还没有多少关于 OpenSSL 使用的教程,所以对初学者来说,在 应用程序中使用它可能会有一些困难。那么怎样才能使用 OpenSSL 实现一个基本的安全连接呢? 本教程将帮助您解决这个问题。

学习如何实现 OpenSSL 的困难部分在于其文档的不完全。不完全的 API 文档通常会妨碍开发人员 使用该 API,而这通常意味着它注定要失败。但 OpenSSL 仍然很活跃,而且正逐渐变得强大。这是为什么?

OpenSSL 是用于安全通信的最著名的开放库。在 google 中搜索“SSL library”得到的返回结果中, 列表最上方就是 OpenSSL。它诞生于 1998 年,源自 Eric Young 和 Tim Hudson 开发的 SSLeay 库。其他 SSL 工具包包括遵循 GNU General Public License 发行的 GNU TLS,以及 Mozilla Network Security Services(NSS)(请参阅本文后面的 参考资料 ,以获得 其他信息)。

那么,是什么使得 OpenSSL 比 GNU TLS、Mozilla NSS 或其他所有的库都优越呢?许可是一方面因素 (请参阅 参考资料)。此外,GNS TLS(迄今为止)只支持 TLS v1.0 和 SSL v3.0 协议,仅此而已。

Mozilla NSS 的发行既遵循 Mozilla Public License 又遵循 GNU GPL,它允许开发人员进行选择。 不过,Mozilla NSS 比 OpenSSL 大,并且需要其他外部库来对库进行编译,而 OpenSSL 是完全 自包含的。与 OpenSSL 相同,大部分 NSS API 也没有文档资料。Mozilla NSS 获得了 PKCS #11 支持,该支持可以用于诸如智能卡这样的加密标志。OpenSSL 就不具备这一支持。

先决条件

要充分理解并利用本文,您应该:

  • 精通 C 编程。
  • 熟悉 Internet 通信和支持 Internet 的应用程序的编写。

并不绝对要求您熟悉 SSL ,因为稍后将给出对 SLL 的简短说明;不过,如果您希望得到详细论述 SSL 的文章的链接,请参阅 参考资料部分。拥有密码学方面的知识固然好,但这 并不是必需的。

回页首

什么是 SSL?

SSL 是一个缩写,代表的是 Secure Sockets Layer。它是支持在 Internet 上进行安全通信的 标准,并且将数据密码术集成到了协议之中。数据在离开您的计算机之前就已经被加密,然后只有 到达它预定的目标后才被解密。证书和密码学算法支持了这一切的运转,使用 OpenSSL,您将 有机会切身体会它们。

理论上,如果加密的数据在到达目标之前被截取或窃听,那些数据是不可能被破解的。不过, 由于计算机的变化一年比一年快,而且密码翻译方法有了新的发展,因此,SSL 中使用的加密协议 被破解的可能性也在增大。

可以将 SSL 和安全连接用于 Internet 上任何类型的协议,不管是 HTTP、POP3,还是 FTP。还可以用 SSL 来保护 Telnet 会话。虽然可以用 SSL 保护任何连接,但是不必对每一类连接都使用 SSL。 如果连接传输敏感信息,则应使用 SSL。

回页首

什么是 OpenSSL?

OpenSSL 不仅仅是 SSL。它可以实现消息摘要、文件的加密和解密、数字证书、数字签名 和随机数字。关于 OpenSSL 库的内容非常多,远不是一篇文章可以容纳的。

OpenSSL 不只是 API,它还是一个命令行工具。命令行工具可以完成与 API 同样的工作, 而且更进一步,可以测试 SSL 服务器和客户机。它还让开发人员对 OpenSSL 的能力有一个 认识。要获得关于如何使用 OpenSSL 命令行工具的资料,请参阅 参考资料部分。

回页首

您需要什么

首先需要的是最新版本的 OpenSSL。查阅参考资料部分,以确定从哪里可以获得最新的可以自己编译的源代码, 或者最新版本的二进制文件(如果您不希望花费时间来编译的话)。不过,为了安全起见, 我建议您下载最新的源代码并自己编译它。二进制版本通常是由第三方而不是由 OpenSSL 的开发人员来编译和发行的。

一些 Linux 的发行版本附带了 OpenSSL 的二进制版本,对于学习如何使用 OpenSSL 库来说,这足够了;不过, 如果您打算去做一些实际的事情,那么一定要得到最新的版本,并保持该版本一直是最新的。

对于以 RPM 形式安装的 Linux 发行版本(Red Hat、Mandrake 等),建议您通过从发行版本制造商那里获得 RPM 程序包来更新您的 OpenSSL 发行版本。出于安全方面的原因,建议您使用 最新版本的发行版本。如果您的发行版本不能使用最新版本的 OpenSSL,那么建议您只覆盖库文件,不要覆盖 可执行文件。OpenSSL 附带的 FAQ 文档中包含了有关这方面的细节。

还要注意的是,OpenSSL 并没有在所有的平台上都获得官方支持。虽然制造商已经尽力使其能够跨平台兼容, 但仍然存在 OpenSSL 不能用于您的计算机 和/或 操作系统的可能。请参阅 OpenSSL 的 Web 站点( 参考资料 中 的链接),以获得关于哪些平台可以得到支持的信息。

如果想使用 OpenSSL 来生成证书请求和数字证书,那么必须创建一个配置文件。在 OpenSSL 程序包 的 apps 文件夹中,有一个名为 openssl.cnf 的 可用模板文件。我不会对该文件进行讨论,因为这不在本文要求范围之内。不过,该模板文件有一些非常好的注释,而且如果 在 Internet 上搜索,您可以找到很多讨论修改该文件的教程。

回页首

头文件和初始化

本教程所使用的头文件只有三个:ssl.h、bio.h 和 err.h。它们都位于 openssl 子目录中,而且都是开发您的项目 所必需的。要初始化 OpenSSL 库,只需要三个代码行即可。清单 1 中列出了所有内容。其他的头文件 和/或 初始化函数可能 是其他一些功能所必需的。

清单 1. 必需的头文件 

/* OpenSSL headers */
#include "openssl/bio.h"
#include "openssl/ssl.h"
#include "openssl/err.h"
/* Initializing OpenSSL */
SSL_load_error_strings();
ERR_load_BIO_strings();
OpenSSL_add_all_algorithms();

回页首

建立非安全连接

不管连接是 安全的还是不安全的,OpenSSL 都使用了一个名为 BIO 的抽象库来处理包括文件和套接字在内的各种类型的通信。您还可以将 OpenSSL 设置成为一个过滤器,比如用于 UU 或 Base64 编码的过滤器。

在这里对 BIO 库进行全面说明有点麻烦,所以我将根据需要一点一点地介绍它。首先, 我将向您展示如何建立一个标准的套接字连接。相对于使用 BSD 套接字库,该操作需要 的代码行更少一些。

在建立连接(无论安全与否)之前,要创建一个指向 BIO 对象的指针。这类似于在标准 C 中 为文件流创建 FILE 指针。

清单 2. 指针 

BIO * bio;

打开连接

创建新的连接需要调用 BIO_new_connect 。您可以在同一个调用中同时 指定主机名和端口号。也可以将其拆分为两个单独的调用:一个是创建连接并设置主机名的 BIO_new_connect 调用,另一个是设置端口号的 BIO_set_conn_port (或者BIO_set_conn_int_port )调用。

不管怎样,一旦 BIO 的主机名和端口号都已指定,该指针会尝试打开连接。没有什么可以影响它。如果创建 BIO 对象时遇到问题,指针将会是 NULL。为了确保连接成功,必须执行 BIO_do_connect 调用。

清单 3. 创建并打开连接 

bio = BIO_new_connect("hostname:port");
if(bio == NULL)
{
    /* Handle the failure */
}
if(BIO_do_connect(bio) <= 0)
{
    /* Handle failed connection */
}

在这里,第一行代码使用指定的主机名和端口创建了一个新的 BIO 对象,并以所示风格对该对象进行 格式化。例如, 如果您要连接到 www.ibm.com 的 80 端口,那么该字符串将是 www.ibm.com:80 。调用 BIO_do_connect 检查连接是否成功。如果出错,则返回 0 或 -1。

与服务器进行通信

不管 BIO 对象是套接字还是文件,对其进行的读和写操作都是通过以下两个函数来完成的: BIO_read 和 BIO_write 。 很简单,对吧?精彩之处就在于它始终如此。

BIO_read 将尝试从服务器读取一定数目的字节。它返回读取的字节数、 0 或者 -1。在受阻塞的连接中,该函数返回 0,表示连接已经关闭,而 -1 则表示连接出现错误。在非阻塞连接的情况下,返回 0 表示没有可以获得的数据,返回 -1 表示连接出错。可以调用BIO_should_retry 来确定是否可能重复出现该错误。

清单 4. 从连接读取 

int x = BIO_read(bio, buf, len);
if(x == 0)
{
    /* Handle closed connection */
}
else if(x < 0)
{
   if(! BIO_should_retry(bio))
    {
        /* Handle failed read here */
    }
    /* Do something to handle the retry */
}

BIO_write 会试着将字节写入套接字。它将返回实际写入的 字节数、0 或者 -1。同 BIO_read ,0 或 -1 不一定表示错误。BIO_should_retry 是找出问题的途径。如果需要重试写操作,它必须 使用和前一次完全相同的参数。

清单 5. 写入到连接 

if(BIO_write(bio, buf, len) <= 0)
{
    if(! BIO_should_retry(bio))
    {
        /* Handle failed write here */
    }
    /* Do something to handle the retry */
}

关闭连接

关闭连接也很简单。您可以使用以下两种方式之一来关闭连接: BIO_reset 或 BIO_free_all 。如果您还需要重新使用对象,那么请使用第一种方式。 如果您不再重新使用它,则可以使用第二种方式。

BIO_reset 关闭连接并重新设置 BIO 对象的内部状态,以便可以重新使用连接。如果要在整个应用程序中使用同一对象,比如使用一台安全的聊天 客户机,那么这样做是有益的。该函数没有返回值。

BIO_free_all 所做正如其所言:它释放内部结构体,并释放 所有相关联的内存,其中包括关闭相关联的套接字。如果将 BIO 嵌入于一个类中,那么应该在类的 析构函数中使用这个调用。

清单 6. 关闭连接 

/* To reuse the connection, use this line */
BIO_reset(bio);
/* To free it from memory, use this line */
BIO_free_all(bio);

回页首

建立安全连接

现在需要给出建立安全连接需要做哪些事情。惟一要改变的地方就是建立并进行连接。其他所有内容都是相同的。

安全连接要求在连接建立后进行握手。在握手过程中,服务器向客户机发送一个证书, 然后,客户机根据一组可信任证书来核实该证书。它还将检查证书,以确保它没有过期。要 检验证书是可信任的,需要在连接建立之前提前加载一个可信任证书库。

只有在服务器发出请求时,客户机才会向服务器发送一个证书。该过程叫做客户机认证。使用证书, 在客户机和服务器之间传递密码参数,以建立安全连接。尽管握手是在建立连接之后才进行的,但是客户机或服务器可以在任何时刻请求进行一次新的握手。

参考资料 部分中列出的 Netscasp 文章 和 RFC 2246 ,对握手以及建立安全连接的其他方面的知识进行了更详尽的论述。

为安全连接进行设置

为安全连接进行设置要多几行代码。同时需要有另一个类型为 SSL_CTX 的指针。该结构保存了一些 SSL 信息。您也可以利用它通过 BIO 库建立 SSL 连接。可以通过使用 SSL 方法函数调用 SSL_CTX_new 来创建这个结构,该方法函数通常是SSLv23_client_method 。

还需要另一个 SSL 类型的指针来保持 SSL 连接结构(这是短时间就能完成的一些连接所必需的)。以后还可以用该 SSL 指针来检查连接信息或设置其他 SSL 参数。

清单 7. 设置 SSL 指针 

SSL_CTX * ctx = SSL_CTX_new(SSLv23_client_method());
SSL * ssl;

加载可信任证书库

在创建上下文结构之后,必须加载一个可信任证书库。这是成功验证每个证书所必需的。如果 不能确认证书是可信任的,那么 OpenSSL 会将证书标记为无效(但连接仍可以继续)。

OpenSSL 附带了一组可信任证书。它们位于源文件树的 certs 目录中。 不过,每个证书都是一个独立的文件 —— 也就是说,需要单独加载每一个证书。在 certs 目录下,还有一个存放过期证书的子目录。试图加载这些证书将会出错。

如果您愿意,可以分别加载每一个文件,但为了简便起见,最新的 OpenSSL 发行版本的可信任证书 通常存放在源代码档案文件中,这些档案文件位于名为“TrustStore.pem”的单个文件中。如果已经有了一个可信任证书库, 并打算将它用于特定的项目中,那么只需使用您的文件替换清单 8 中的“TrustStore.pem”(或者使用 单独的函数调用将它们全部加载)即可。

可以调用 SSL_CTX_load_verify_locations 来加载可信任证书库文件。这里要用到 三个参数:上下文指针、可信任库文件的路径 和文件名,以及证书所在目录的路径。必须指定可信任库文件或证书的目录。 如果指定成功,则返回 1,如果遇到问题,则返回 0。

清单 8. 加载信任库 

if(! SSL_CTX_load_verify_locations(ctx, "/path/to/TrustStore.pem", NULL))
{
    /* Handle failed load here */
}

如果打算使用目录存储可信任库,那么必须要以特定的方式命名文件。OpenSSL 文档清楚 地说明了应该如何去做,不过,OpenSSL 附带了一个名为 c_rehash 的工具, 它可以将文件夹配置为可用于 SSL_CTX_load_verify_locations 的 路径参数。

清单 9. 配置证书文件夹并使用它 

/* Use this at the command line */
c_rehash /path/to/certfolder
/* then call this from within the application */
if(! SSL_CTX_load_verify_locations(ctx, NULL, "/path/to/certfolder"))
{
    /* Handle error here */
}

为了指定所有需要的验证证书,您可以根据需要命名任意数量的单独文件或文件夹。您还可以同时指定 文件和文件夹。

创建连接

将指向 SSL 上下文的指针作为惟一参数,使用 BIO_new_ssl_connect 创建 BIO 对象。还需要获得指向 SSL 结构的指针。在本文中,只将该指针用于 SSL_set_mode 函数。而这个函数是用来设置 SSL_MODE_AUTO_RETRY 标记的。使用这个选项进行设置,如果服务器突然希望进行 一次新的握手,那么 OpenSSL 可以在后台处理它。如果没有这个选项,当服务器希望进行一次新的握手时, 进行读或写操作都将返回一个错误,同时还会在该过程中设置 retry 标记。

清单 10. 设置 BIO 对象 

bio = BIO_new_ssl_connect(ctx);
BIO_get_ssl(bio, & ssl);
SSL_set_mode(ssl, SSL_MODE_AUTO_RETRY);

设置 SSL 上下文结构之后,就可以创建连接了。主机名是使用 BIO_set_conn_hostname 函数 设置的。主机名和端口的指定格式与前面的相同。该函数还可以打开到主机的连接。为了确认已经成功打开连接,必须 执行对 BIO_do_connect 的调用。该调用还将执行握手来建立安全连接。

清单 11. 打开安全连接 

/* Attempt to connect */
BIO_set_conn_hostname(bio, "hostname:port");
/* Verify the connection opened and perform the handshake */
if(BIO_do_connect(bio) <= 0)
{
    /* Handle failed connection */
}

连接建立后,必须检查证书,以确定它是否有效。实际上,OpenSSL 为我们完成了这项任务。如果证书有致命的 问题(例如,哈希值无效),那么将无法建立连接。但是,如果证书的问题并不是致命的(当它已经过期 或者尚不合法时),那么仍可以继续使用连接。

可以将 SSL 结构作为惟一参数,调用 SSL_get_verify_result 来查 明证书是否通过了 OpenSSL 的检验。如果证书通过了包括信任检查在内的 OpenSSL 的内部检查,则返回 X509_V_OK。如果有地方出了问题,则返回一个错误代码,该代码被记录在命令行工具的 verify 选项下。

应该注意的是,验证失败并不意味着连接不能使用。是否应该使用连接取决于验证结果和安全方面的考虑。例如, 失败的信任验证可能只是意味着没有可信任的证书。连接仍然可用,只是需要从思想上提高安全意识。

清单 12. 检查证书是否有效 

if(SSL_get_verify_result(ssl) != X509_V_OK)
{
    /* Handle the failed verification */
}

这就是所需要的全部操作。通常,与服务器进行通信都要使用 BIO_read 和 BIO_write 。并且只需调用 BIO_free_all 或BIO_reset ,就可以关闭 连接,具体调用哪一个方法取决于是否重用 BIO。

必须在结束应用程序之前的某个时刻释放 SSL 上下文结构。可以调用 SSL_CTX_free 来释放该结构。

清单 13. 清除 SSL 上下文 

SSL_CTX_free(ctx);

回页首

错误检测

显然 OpenSSL 抛出了某种类型的错误。这意味着什么?首先,您需要得到错误代码本身; ERR_get_error 可以完成这项任务;然后,需要将错误代码转换为错误 字符串,它是一个指向由 SSL_load_error_strings 或 ERR_load_BIO_strings 加载到内存中的永久字符串的指针。 可以在一个嵌套调用中完成这项操作。

表 1 略述了从错误栈检索错误的方法。清单 24 展示了如何打印文本字符串中的最后一个 错误信息。

  1. 从栈中检索错误
ERR_reason_error_string 返回一个静态字符串的指针,然后可以将字符串显示在屏幕上、写入文件,或者以任何您希望的方式进行处理
ERR_lib_error_string 指出错误发生在哪个库中
ERR_func_error_string 返回导致错误的 OpenSSL 函数

清单 14. 打印出最后一个错误 

printf("Error: %sn", ERR_reason_error_string(ERR_get_error()));

您还可以让库给出预先格式化了的错误字符串。可以调用 ERR_error_string 来 得到该字符串。该函数将错误代码和一个预分配的缓冲区作为参数。而这个缓冲区必须是 256 字节长。如果参数 为 NULL,则 OpenSSL 会将字符串写入到一个长度为 256 字节的静态缓冲区中,并返回指向该缓冲区的 指针。否则,它将返回您给出的指针。如果您选择的是静态缓冲区选项,那么在下一次调用ERR_error_string 时,该缓冲区会被覆盖。

清单 15. 获得预先格式化的错误字符串 

printf("%sn", ERR_error_string(ERR_get_error(), NULL));

您还可以将整个错误队列转储到文件或 BIO 中。可以通过 ERR_print_errors 或 ERR_print_errors_fp 来实现这项操作。队列是以可读格式被转储的。第一个函数将队列发送到 BIO ,第二个函数将队列发送到 FILE 。 字符串格式如下(引自 OpenSSL 文档):

[pid]:error:[error code]:[library name]:[function name]:[reason string]:[file name]:[line]:[optional text message]

其中, [pid] 是进程 ID, [error code] 是一个 8 位十六进制代码, [file name] 是 OpenSSL 库中的源代码文件, [line] 是源文件中的行号。

清单 16. 转储错误队列 

ERR_print_errors_fp(FILE *);
ERR_print_errors(BIO *);

回页首

开始做吧

使用 OpenSSL 创建基本的连接并不困难,但是,当试着确定该如何去做时,文档可能是一个小障碍。本文向您介绍了一些基本概念,但 OpenSSL 还有很多灵活之处有待发掘,而且 您还可能需要一些高级设置,以便项目能够充分利用 SSL 的功能。

本文中有两个样例。一个样例展示了到 的非安全连接,另一个则展示了到 的安全 SSL 连接。两者都是连接到服务器并下载其主页。它们没有进行 任何安全检查,而且库中的所有设置都是默认值 —— 作为本文的一部分,应该只将这些用于教学目的。

在任何支持的平台上,源代码的编译都应该是非常容易的,不过我建议您使用最新版本的 OpenSSL。在撰写本文时,OpenSSL 的最新版本是 0.9.7d。

参考资料

  • 您可以参阅本文在 developerWorks 全球站点上的 英文原文. 

  • 下载本文中用到的 源代码。 

  • 您可以从 OpenSSL Project 下载 OpenSSL 源文件;一定要去查看一下 文档 的当前状态。您还可以从 邮件列表(滚动到底部,以获得到存档文件的链接)中学到很多知识,而且应该——当然,如往常一样——花一些时间去 阅读 FAQ! 

  • OpenSSL 源自 SSLeay (它甚至有非常 完善的文档)。 

  • 此外,请参阅由两部分构成的文章“ An Introduction to OpenSSL Programming”( Linux Journal,2001 年) (以及 第二部分), 而且可以通过( informIT, 2001 年)获得的另一篇来自 Sams 的文章 “ Securing Sockets with OpenSSL”和它的 第二部分,该文章也是由两部分构成的。 

  • 在线阅读 BIO library documentation 和 Network Security with OpenSSL (O'Reilly & Associates,2002 年)的样例章节。Linux Socket Programming (Sams,2001 年)摘自 Sams 的书。 

  • OpenSSL 的发布遵循 BSD/Apache-type 许可。如果您是自由软件(Free Software)的支持者(或者是 good documentation 的支持者), 您可能还希望查看 The GNU Transport Layer Security Library (注意,如果没有异常子句,GPL 的软件不能针对 OpenSSL 进行链接)。 Mozilla Network Security Services(NSS) 是双许可的,它既遵循 Mozilla Public License(MPL)又遵循 GNU General Public License (GNU GPL),而且有相当好的 文档。 要深入了解 TLS,请阅读 Wikipedia 的文章 Transport Layer Security。 

  • 可以在 RFC 2246 中找到关于 Transport Layer Security 的 备忘录和技术细节,RFC 2246 定义了标准,并且它被 RFC 3546更新, 后者定义了对 TLS 协议的扩展。 

  • “ 使用 Twisted 框架进行网络编程, 第 4 部分”( developerWorks,2003 年 9 月)中 David Mertz 讨论了使用 Python twisted 框架进行 SSL 编程。 

  • 要深入学习套接字编程,请参阅 Linux Socket 编程,第一部分( developerWorks,2003 年 10 月)和 Linux Socket 编程,第二部分,这也是 David Mertz 的一个教程系列( developerWorks,2004 年 1 月)。 对那些刚开始进行套接字编程的人来说, Beej's Guide to Network Programming Using Internet Sockets 也是一个不错的参考资料。 

  • 如果您是 刚刚 开始接触套接字,那么请先阅读 “ Understanding Sockets in Unix, NT, and Java”( developerWorks,1998 年 6 月), 那篇文章提供了什么是套接字以及它们适用于何处的极好的入门级概述。 

  • 此外,还可以参阅来自 Communications Programming Concepts Sockets 的关于 Sockets 的 IBM 文档,以及来自 Technical Reference: Communications, Volume 2 的 Programming sockets on AIX。 

  • 可以通过“ Encryption using OpenSSL's crypto libraries” ( Linux Gazette,2003 年)初步了解加密,并通过 “ Introduction to Cryptography” ( PGP Corporation,2003 年 5 月 —— XPDF 格式) 或 “ Introduction to cryptography” (developerWorks,2001 年 3 月)获得对加密的总体上的深入理解。 可以以 Postscript 和 PDF 格式在线获得 Handbook of Appplied Cryptography (CRC Press,1996 年) (可以通过订购获得更新后的 2001 版本)。 

  • 在 developerWorks Linux 专区 可以找到 更多为 Linux 开发人员准备的参考资料。 

  • 可以在 Developer Bookstore Linux 区中定购 打折出售的 Linux 书籍。 

  • 从 developerWorks 的 Speed-start your Linux app 专区下载可以运行于 Linux 之上的精选的 developerWorks Subscription 产品免费测试版本,其中包括 WebSphere Studio Site Developer、WebSphere SDK for Web services、WebSphere Application Server、DB2 Universal Database Personal Developers Edition、Tivoli Access Manager 和 Lotus Domino Server。想更快地开始上手,请参阅针对各个产品的 how-to 文章和技术支持。 

关于作者

Kenneth 是 Peru State College(位于 Peru, Nebraska)计算机科学专业的大四学生。他还是 学生报 The Peru State Times 的职业作者。他拥有 Southwestern Community College (位于 Creston, Iowa)计算机编程专业的理学副学士(Associate of Science)学位, 在这所大学里,他是一名半工半读的 PC 技术员。他的研究领域包括 Java、C++、COBOL、 Visual Basic 和网络。

5. 结束

  加上SSL支持的Nebula框架测试通过,虽然不算太复杂,但过程还是蛮曲折,耗时也挺长。这里把Nebula使用openssl开发SSL通信分享出来,希望对准备使用openssl的开发者有用。如果觉得本文对你有用,别忘了到Nebula的Github或码云给个star,谢谢。

<br/>

参考资料:

  • 基于OpenSSL实现的安全连接
  • SSL API文档
  • Https协议详解
  • HTTPS是大势所趋?看腾讯专家通过Epoll+OpenSSL在高并发压测机器人中支持https
  • openssl 编程入门
  • SSL连接建立过程分析
  • SSL socket 通讯详解
  • HTTPS从原理到应用:SSL/TLS协议
  • SSL/TLS 握手优化详解
  • 非阻塞/异步 openssl
  • 两个基于openssl的https client例子
  • OpenSSL编程初探1 --- 使用OpenSSL API建立SSL通信的一般流程简介
  • OpenSSL编程初探2 --- 关于证书文件的加载

DebugPrintf("Connected with %s encryptionn",SSL_get_cipher(pSockInfo->pSSL));//;///*打印所有加密算法的信息(可选)*/

openssl使用方法 - Linux - UNIX技术社区 - Google Chrome (2013/5/30 16:58:46)

OpenSSL 是使用非常广泛的 SSL 的开源实现。由于其中实现了为 SSL 所用的各种加密算法,因此 OpenSSL 也是被广泛使用的加密函数库。

1.1 SSL

SSL(Secure Socket Layer) 安全协议是由 Netscape 公司首先提出,最初用在保护 Navigator 浏览器和 Web 服务器之间的 HTTP 通信 ( 即 HTTPS) 。后来 SSL 协议成为传输层安全通信事实上的标准,并被 IETF 吸收改进为 TLS(Transport Layer Security) 协议。

SSL/TLS 协议位于 TCP 协议和应用层协议之间,为传输双方提供认证、加密和完整性保护等安全服务。 SSL 作为一个协议框架,通信双方可以选用合适的对称算法、公钥算法、 MAC 算法等密码算法实现安全服务。

1.2 OpenSSL

OpenSSL 是著名的 SSL 的开源实现,是用 C 语言实现的。

OpenSSL 的前身是 SSLeay ,一个由 Eric Young 开发的 SSL 的开源实现,支持 SSLv2/v3 和 TLSv1 。

伴随着 SSL 协议的普及应用, OpenSSL 被广泛应用在基于 TCP/Socket 的网络程序中,尤其是 OpenSSL 和 Apache 相结合,是很多电子商务网站服务器的典型配置。



•  编译和安装 OpenSSL

OpenSSL 开放源代码,这对学习、分析 SSL 和各种密码算法提供了机会,也便于在上面进一步开发。

2.1 获得 OpenSSL

到 OpenSSL 的网站即可下载当前版本的 OpenSSL 源代码压缩包。

当前版本 openssl- 0.9.8 .tar.gz ,只有 3M 多,比较精简。解压缩后得到一个目录 openssl-0.9.8 ,共有约 1800 个文件, 15M 。其中 crypto 子目录中是众多密码算法实现, ssl 子目录中是 SSL 协议的实现。

在 Linux 中解压缩:

$tar zxf openssl- 0.9.8 .tar.gz

在 Windows 中可以使用 winzip 或 winrar 。

2.2 编译工具

编译 OpenSSL 需要 Perl 和 C 编译器。在 Windows 下如果要用加密算法的汇编代码实现,还需要 masm 或 nasm 汇编器。 ( 汇编代码可以比 C 代码显著提高密码运算速度 )

Perl 在 Windows 下推荐使用 Active Perl 。

C 编译器可以使用 gcc 。在 W indows 下可以使用 Visual C 编译器。

汇编器推荐使用 nasm 。

这些工具所在目录必须加入到 PATH 环境变量中去。

2.3 编译和安装步骤

查看 readme 是个好习惯。从 readme 了解到需要进一步查看 INSTALL 和 INSTALL.W32 文件。

在 Windows 中:

>perl Configure VC-WIN32

>msdo_nasm ( 如果不使用汇编代码实现,则可 >msdo_ms)

>nmake -f msntdll.mak

>cd out32dll

>..mstest

编译结果得到头文件、链接库、运行库和 openssl.exe 工具。头文件位于 ./inc32 或者 ./inculde 目录,有一个 openssl 子目录,内有几十个 .h 文件。链接库即 ./out32dll 目录中的 libeay32.lib 和 ssleay32.lib ,分别是密码算法相关的和 ssl 协议相关的。运行库是 ./out32dll 目录中的 libeay32.dll 和 ssleay32.dll ,和链接库相对应。在 ./out32dll 中还有一个工具 openssl.exe ,可以直接用来测试性能、产生 RSA 密钥、加解密文件,甚至可以用来维护一个测试用的 CA 。

在 Linux 中的编译和安装步骤较简单 :

$./config

$make

$make test

$make install

在 Linux 下,头文件、库文件、工具都已被安装放到了合适的位置。库文件是 .a 或 .so 格式。



•  使用 OpenSSL.exe

使用 OpenSSL.exe(Linux 中可执行文件名是 openssl) 可以做很多工作,是一个很好的测试或调试工具。

3.1 版本和编译参数

显示版本和编译参数: >openssl version -a

3.2 支持的子命令、密码算法

查看支持的子命令: >openssl ?

SSL 密码组合列表: >openssl ciphers

3.3 测试密码算法速度

测试所有算法速度: >openssl speed

测试 RSA 速度: >openssl speed rsa

测试 DES 速度: >openssl speed des

3.4 RSA 密钥操作

产生 RSA 密钥对: >openssl genrsa -out 1.key 1024

取出 RSA 公钥: >openssl rsa -in 1.key -pubout -out 1.pubkey

3.5 加密文件

加密文件: >openssl enc -e -rc4 -in 1.key -out 1.key.enc

解密文件: >openssl enc -d -rc4 -in 1.key.enc -out 1.key.dec

3.6 计算 Hash 值

计算文件的 MD5 值: >openssl md5 < 1.key

计算文件的 SHA1 值: >openssl sha1 < 1.key



•  算法编程 API

OpenSSL 中支持众多的密码算法,并提供了很好的封装和接口。密码算法主要分为如下几类:对称算法、公钥算法、散列算法、随机数产生算法等。

OpenSSL 的目标是实现安全协议。其中相关协议和标准包括: SSL/TLS 、 PKCS#1 、 PCKS#10 、 X.509 、 PEM 、 OCSP 等。

4.1 对称算法接口

OpenSSL 中实现的对称算法太多,举三个例子: DES 、 AES 、 RC4 。

4.1.1 DES

DES 加密算法是分组算法。 DES 的基本操作是把 64 比特明文在 56 比特密钥指引下加密成 64 比特密文。在实际使用中把密钥看作 64 比特可以更方便。

DES ( IN , KEY ) = OUT

(1) DES ECB 模式

在 OpenSSL 中 ECB 操作模式对应的函数是 DES_ecb_encrypt() ,该函数把一个 8 字节明文分组 input 加密成为一个 8 字节密文分组 output 。参数中密钥结构 ks 是用函数 DES_set_key() 准备好的,而密钥 key 是用随机数算法产生的 64 个随机比特。参数 enc 指示是加密还是解密。该函数每次只加密一个分组,因此用来加密很多数据时不方便使用。

void DES_ecb_encrypt(const_DES_cblock *input,DES_cblock *output, DES_key_schedule *ks,int enc);

int DES_set_key(const_DES_cblock *key,DES_key_schedule *schedule);

(2) DES CBC 模式

DES 算法 CBC 操作模式加解密函数是 DES_ncbc_encrypt() 。参数 length 指示输入字节长度。如果长度不是 8 字节的倍数,则会被用 0 填充到 8 字节倍数。因此,输出可能比 length 长,而且必然是 8 字节的倍数。

void DES_ncbc_encrypt(const unsigned char *input,unsigned char *output, long length, DES_key_schedule *schedule, DES_cblock *ivec, int enc);

(3) DES CFB 模式

DES 算法 CFB 操作模式加解密函数是 DES_cfb_encrypt() 。参数 length 指示输入字节长度。参数 numbits 则指示了 CFB 每次循环加密多少明文比特,也即密文反馈的比特数目。 ivec 是初始向量,被看做第 0 个密文分组,是不用保密但应随机取值的 8 个字节。如果在一次会话中数次调用 DES_cfb_encrypt() ,则应该记忆 ivec 。由于 CFB 模式中每次 DES 基本操作只加密 numbits 比特明文,因此如果 numbits 太小则效率太低。

void DES_cfb_encrypt(const unsigned char *in, unsigned char *out, int numbits, long length, DES_key_schedule *schedule, DES_cblock *ivec, int enc);

另有一个 numbit 是 64 比特的版本,既高效又没有填充的麻烦,推荐使用。 num 中的返回值指示了 ivec 中的状态,是和下次调用衔接的。

void DES_cfb64_encrypt(const unsigned char *in, unsigned char *out, long length, DES_key_schedule *schedule, DES_cblock *ivec, int *num, int enc) ;

(4) DES OFB 模式

OFB 和 CFB 类似,也有两个函数,用法一样。

void DES_ofb_encrypt(const unsigned char *in,unsigned char *out,int numbits,long length,DES_key_schedule *schedule,DES_cblock *ivec);

void DES_ofb64_encrypt(const unsigned char *in,unsigned char *out,long length,DES_key_schedule *schedule,DES_cblock *ivec,int *num);

(5) DES 函数示例程序

见附件 A.1 。

4.1.2 A ES

AES 加密算法是分组算法。典型参数的 AES 的基本操作是把 128 比特明文在 128 比特密钥指引下加密成 128 比特密文。

AES ( IN , KEY ) = OUT

OpenSSL 中关于 AES 的函数名和参数接口和 DES 的雷同。相关函数名如下 ( 参数略 ) 。

int AES_set_encrypt_key();

int AES_set_decrypt_key();

void AES_ecb_encrypt();

void AES_cbc_encrypt();

void AES_cfb128_encrypt();

void AES_ofb128_encrypt();

AES 示例程序见附件 A.2 。

4.1.3 RC4

RC4 密码算法是流算法,也叫序列算法。流算法是从密钥作为种子产生密钥流,明文比特流和密钥流异或即加密。 RC4 算法由于算法简洁,速度极快,密钥长度可变,而且也没有填充的麻烦,因此在很多场合值得大力推荐。

OpenSSL 中 RC4 算法有两个函数 : RC4_set_key() 设置密钥, RC4() 加解密。可以把 RC4 看作异或,因此加密两次即解密。

void RC4_set_key(RC4_KEY *key, int len, const unsigned char *data);

void RC4(RC4_KEY *key, unsigned long len, const unsigned char *indata, unsigned char *outdata);

RC4 示例程序见附件 A.3 。

例子 A.3.(1) 是利用 OpenSSL 动态库函数。例子 A.3.(2) 是把 RC4 的实现代码从 OpenSSL 中分离出来的。例子 A.3.(3) 是另一个演示实现。

4.2 公钥算法

OpenSSL 中实现了 RSA 、 DSA 、 ECDSA 等公钥算法。

4.2.1 RSA

RSA 是分组算法,典型的密钥模长度 1024 比特时,分组即是 1024 比特,即 128 字节。

(1) RSA 密钥

RSA 密钥产生函数 RSA_generate_key() ,需要指定模长比特数 bits 和公钥指数 e 。另外两个参数为 NULL 即可。

RSA * RSA_generate_key(int bits, unsigned long e, void (*callback) (int,int,void *),void *cb_arg);

如果从文件中读取密钥,可使用函数 PEM_read_bio_PrivateKey()/ PEM_read_bio_PUBKEY(); EVP_PKEY 中包含一个 RSA 结构,可以引用。

EVP_PKEY *PEM_read_bio_PrivateKey(BIO *bp, EVP_PKEY **x, pem_password_cb *cb, void *u);

(2) RSA 加密解密

RSA 加密函数 RSA_public_encrypt() 使用公钥部分,解密函数 RSA_private_decrypt() 使用私钥。填充方式常用的有两种 RSA_PKCS1_PADDING 和 RSA_PKCS1_OAEP_PADDING 。出错时返回 -1 。输入必须比 RSA 钥模长短至少 11 个字节(在 RSA_PKCS1_PADDING 时?)。输出长度等于 RSA 钥的模长。

int RSA_public_encrypt(int flen, const unsigned char *from,unsigned char *to, RSA *rsa,int padding);

int RSA_private_decrypt(int flen, const unsigned char *from,unsigned char *to, RSA *rsa,int padding);

(3) 签名和验证

签名使用私钥,验证使用公钥。 RSA 签名是把被签署消息的散列值编码后用私钥加密,因此函数中参数 type 用来指示散列函数的类型,一般是 NID_md5 或 NID_sha1 。正确情况下返回 0 。

int RSA_sign(int type, const unsigned char *m, unsigned int m_length, unsigned char *sigret, unsigned int *siglen, RSA *rsa);

int RSA_verify(int type, const unsigned char *m, unsigned int m_length, unsigned char *sigbuf, unsigned int siglen, RSA *rsa);

(4) RSA 函数示例程序

RSA 示例程序见附件 A.4 。

例子 A.4.(1) 是加密解密例子。例子 A.4.(2) 是签名验证例子。

4.2.2 DSA

( TOBE )

4.2.2 ECDSA

( or NOT TOBE )

4.3 Hash 算法

Hash 算法举 MD5 和 SHA1 两个例子。 Hash 算法重复接收用户输入,直到最后一次结束时输出散列结果。

4.3.1 MD5

MD5 算法输出的散列值是 16 字节。

int MD5_Init(MD5_CTX *c);

int MD5_Update(MD5_CTX *c, const void *data, size_t len);

int MD5_Final(unsigned char *md, MD5_CTX *c);

4.3.2 SHA1

SHA1 算法输出的散列值是 20 字节。

int SHA1_Init(SHA_CTX *c);

int SHA1_Update(SHA_CTX *c, const void *data, size_t len);

int SHA1_Final(unsigned char *md, SHA_CTX *c);

4.3.3 MD5 例子

MD5 示例程序见附件 A.5 。

md5sum 这是一个实用小工具,可以计算一个文件的 MD5 值。

4.4 随机数算法

随机性是密码安全的基石。为了产生安全的伪随机数,必须有好的随机因素作为种子。 OpenSSL 在内部做了努力,但是仍建议在实用随机数产生函数之前添加随机因素。

函数 RAND_add() 可以添加随机因素到内部状态中去。然后,即可以使用 RAND_bytes() 获得随机数。

void RAND_add(const void *buf,int num,double entropy);

int RAND_bytes(unsigned char *buf,int num);



•  SSL 协议编程 API

5.1 客户端

5.2 服务器端

5.3 SSL 示例程序

参见 A.6 。



•  CA 和证书

6.1 OpenSSL 中 CA 的配置

6.2 配置示例

参见 A.7.(1) 。

6.3 证书解析

6.4 解析示例程序

参见 A.7.(2) 。



•   



•  参考网址

SSL 3.0 Specification

http://www.netscape.com/eng/ssl3/

Transp ort Layer Security (tls) Charter

http://www.ietf.org/html.charters/tls-charter.html

OpenSSL: The Open Source toolkit for SSL/TLS

http://www.openssl.org/

SSLeay

http://www2.psy.uq.edu.au/~ftp/Crypto/

OpenSSL 中文论坛

http://openssl.cn/

Perl

http://www.cpan.org/src/README.html

http://www.activestate.com/Products/ActivePerl/

NASM

http://www.perl.com/

studio



•   



•  示例程序

注 : 此嵌入的文件对象可以被拖放到磁盘目录中去。

•  DES 示例程序

•  AES 示例程序

•  RC4 示例程序

( 1 ). ( 2 ). ( 3 )

•  RSA 示例程序

( 1 ). ( 2 ).

•  Hash 算法示例程序

•  SSL 示例程序

•  CA 配置示例和证书解析示例程序

null

/* SSL 库初始化 */

    SSL_library_init();

    /* 载入所有 SSL 算法 */

    OpenSSL_add_all_algorithms();

    /* 载入所有 SSL 错误消息 */

    SSL_load_error_strings();

    /* 以 SSL V2 和 V3 标准兼容方式产生一个 SSL_CTX ,即 SSL Content Text */

    ctx = SSL_CTX_new(SSLv23_server_method());

    /* 也可以用 SSLv2_server_method() 或 SSLv3_server_method() 单独表示 V2 或 V3标准 */

详细介绍:

现行网上银行和电子商务等大型的网上交易系统普遍采用HTTP和SSL相结合的方式。服务器端采用支持SSL的Web服务器,用户端采用支持SSL的浏览器实现安全通信。

    SSL是Secure Socket Layer(安全套接层协议)的缩写,可以在Internet上提供秘密性传输。Netscape公司在推出第一个Web浏览器的同时,提出了SSL协议标准,目前已有3.0版本。SSL采用公开密钥技术。其目标是保证两个应用间通信的保密性和可靠性,可在服务器端和用户端同时实现支持。目前,利用公开密钥技术的SSL协议,已成为Internet上保密通讯的工业标准。本文着重在SSL协议和SSL程序设计两方面谈谈作者对SSL的理解。

SSL协议初步介绍

    安全套接层协议能使用户/服务器应用之间的通信不被攻击者窃听,并且始终对服务器进行认证,还可选择对用户进行认证。SSL协议要求建立在可靠的传输层协议(TCP)之上。SSL协议的优势在于它是与应用层协议独立无关的,高层的应用层协议(例如:HTTP,FTP,TELNET等)能透明地建立于SSL协议之上。SSL协议在应用层协议通信之前就已经完成加密算法、通信密钥的协商及服务器认证工作。在此之后应用层协议所传送的数据都会被加密,从而保证通信的私密性。

通过以上叙述,SSL协议提供的安全信道有以下三个特性:

1.数据的保密性

信息加密就是把明码的输入文件用加密算法转换成加密的文件以实现数据的保密。加密的过程需要用到密匙来加密数据然后再解密。没有了密钥,就无法解开加密的数据。数据加密之后,只有密匙要用一个安全的方法传送。加密过的数据可以公开地传送。

2.数据的一致性

加密也能保证数据的一致性。例如:消息验证码(MAC),能够校验用户提供的加密信息,接收者可以用MAC来校验加密数据,保证数据在传输过程中没有被篡改过。

3.安全验证

加密的另外一个用途是用来作为个人的标识,用户的密匙可以作为他的安全验证的标识。

    SSL是利用公开密钥的加密技术(RSA)来作为用户端与服务器端在传送机密资料时的加密通讯协定。目前,大部分的Web 服务器及浏览器都广泛支持SSL 技术。当浏览器试图连接一个具有SSL认证加密的服务器时,就会唤醒一个SSL会话,浏览器检查认证,必须具备下面三个条件:

1)有一个权威机构发放证书,当然可以创建自我签订的证书(x509 结构)。

2)证书不能过期。

3)证书是属于它所连接的服务器的。

    只有全部具备了这三个条件,浏览器才能成功完成认证。通过这三个条件,用户能确认其浏览器连接到正确的服务器,而不是连接到一些想盗取用户密码等重要信息的虚假的服务器上。

    在当今的电子商务中还有一项被广泛使用的安全协议是SET协议。SET(Secure Electronic Transaction,安全电子交易)协议是由VISA和MasterCard两大信用卡公司于1997年5月联合推出的规范。SET能在电子交易环节上提供更大的信任度、更完整的交易信息、更高的安全性和更少受欺诈的可能性。SET交易分三个阶段进行:用户向商家购物并确定支付;商家与银行核实;银行向商家支付货款。每个阶段都涉及到RSA对数据加密,以及RSA数字签名。使用SET协议,在一次交易中,要完成多次加密与解密操作,故有很高的安全性,但SET协议比SSL协议复杂,商家和银行都需要改造系统以实现互操作。

在Linux 下,比较流行支持SSL认证的是OpenSSL服务器。OpenSSL项目是一个合作的项目,开发一个健壮的、商业等级的、完整的开放源代码的工具包,用强大的加密算法来实现安全的Socket层(Secure Sockets Layer,SSL v2/v3)和传输层的安全性(Transport Layer Security,TLS v1)。这个项目是由全世界的志愿者管理和开发OpenSSL工具包和相关文档。

     如何在Linux下配置OpenSSL服务器,首先从OpenSSL的主页()上下载openssl-version.tar.gz软件包来编译安装,与Apache服务器配合可以建立支持SSL的Web服务器,并可以使用自我签订的证书做认证,关于如何编译、安装OpenSSL服务器,可以参考一下OpenSSL HOWTO文档。

SSL 程序设计初步介绍

    SSL 通讯模型为标准的C/S 结构,除了在 TCP 层之上进行传输之外,与一般的通讯没有什么明显的区别。在这里,我们主要介绍如何使用OpenSSL进行安全通讯的程序设计。关于OpenSSL 的一些详细的信息请参考OpenSSL的官方主页 。

在使用OpenSSL前,必须先对OpenSSL 进行初始化,以下的三个函数任选其一:

SSL_library_init(void);

OpenSSL_add_ssl_algorithms();

SSLeay_add_ssl_algorithms();

事实上 后面的两个函数只是第一个函数的宏。

如果要使用OpenSSL的出错信息,使用SSL_load_error_strings (void)进行错误信息的初始化。以后可以使用void ERR_print_errors_fp(FILE *fp) 打印SSL的错误信息。

一次SSL连接会话一般要先申请一个SSL 环境,基本的过程是:

  1. SSL_METHOD* meth = TLSv1_client_method(); 创建本次会话连接所使用的协议,如果是客户端可以使用

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_METHOD *TLSv1_server_method(void);

SSL_METHOD *SSLv2_server_method(void);

SSL_METHOD *SSLv3_server_method(void);

SSL_METHOD *SSLv23_server_method(void);

需要注意的是客户端和服务器需要使用相同的协议。

2.申请SSL会话的环境 CTX,使用不同的协议进行会话,其环境也是不同的。申请SSL会话环境的OpenSSL函数是

SSLK_CTX* SSL_CTX_new (SSL_METHOD*); 参数就是前面我们申请的 SSL通讯方式。返回当前的SSL 连接环境的指针。

然后根据自己的需要设置CTX的属性,典型的是设置SSL 握手阶段证书的验证方式和加载自己的证书。

void SSL_CTX_set_verify (SSL_CTX* , int , int* (int, X509_STORE_CTX*) )

设置证书验证的方式。

第一个参数是当前的CTX 指针,第二个是验证方式,如果是要验证对方的话,就使用 SSL_VERIFY_PEER。不需要的话,使用SSL_VERIFY_NONE.一般情况下,客户端需要验证对方,而服务器不需要。第三个参数是处理验证的回调函数,如果没有特殊的需要,使用空指针就可以了。

void SSL_CTX_load_verify_locations(SSL_CTX*, const char* , const char*);

加载证书;

第一个参数同上,参数二是证书文件的名称,参数三是证书文件的路径;

int SSL_CTX_use_certificate_file(SSL_CTX *ctx, const char *file, int type);

加载本地的证书;type 指明证书文件的结构类型;失败返回-1

int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx, const char *file, int type);

加载自己的私钥;type 参数指明私钥文件的结构类型;失败返回-1

加载了证书和文件之后,就可以验证私钥和证书是否相符:

BOOl SSL_CTX_check_private_key (SSL_CTX*);

3.既然SSL 使用TCP 协议,当然需要把SSL attach 到已经连接的套接字上了:

SSL* SSL_new (SSL_CTX*); 申请一个SSL 套节字;

int SSL_set_rfd (SSL*); 绑定只读套接字

int SSL_set_wfd (SSL*); 绑定只写套接字

int SSL_set_fd ( SSL*); 绑定读写套接字

绑定成功返回 1, 失败返回0;

4. 接下来就是SSL 握手的动作了

int SSL_connect (SSL*); 失败返回 -1

  1. 握手成功之后,就可以进行通讯了,使用SSL_read 和SS_write 读写SSL 套接字代替传统的read 、write

int SSL_read (SSL *ssl, char *buf, int num );

int SSL_write (SSL *ssl, char *buf, int num);

如果是服务器,则使用 SSL_accept 代替传统的 accept 调用

int SSL_accept(SSL *ssl);

  1. 通讯结束,需要释放前面申请的 SSL资源

int SSL_shutdown(SSL *ssl); 关闭SSL套接字;

void SSL_free (ssl); 释放SSL套接字;

void SSL_CTX_free (ctx); 释放SSL环境;

    OpenSSL 虽然已经发展到了0.9.96版本,但是它的文档还很少,甚至连最基本的man 函数手册都没有完成。所以,本文紧紧是讲述了使用OpenSSL 进行程序设计的框架。更加详细的资料可以参考OpenSSL 的文档或者 Apache mod_ssl 的文档。

    通过以上的介绍,我想读者对SSL协议已经有了一定的了解,作者有机会将会继续给大家介绍SSL协议的其他方面的内容。

 

框架图:

图片 7

图片 8

本文由10bet手机官网发布于微服架构,转载请注明出处:建立SSL安全通信的一般流程,笔记整理

上一篇:Java输出正反金字塔循环,空心菱形的显示 下一篇:mysql数据库的备份与恢复,linux服务器下mysql备份恢复命令介绍
猜你喜欢
热门排行
精彩图文