SSL原理介绍
SSL——安全套接字层(Secure Sockers Layer)协议由著名的Netscape公司开发。为了保证通信双方建立安全可靠的传输隧道,SSL使用PKI中的数字证书技术对通信双方进行身份认证;使用对称加密来保证数据保密性;使用消息认证码(MAC)来保证数据完整性。
SSL位于TCP/IP和各种应用层协议间,如图1所示,分为记录层协议和握手层协议。
图1
握手层负责对服务器和客户进行认证,并确定数据加密传输采用的密钥、加密参数和加密算法等。记录层负责具体的加密解密、计算和验证MAC等操作,然后将加密后的数据交付给传输层。
在具体的应用场景里,某个客户端试图接入服务器时,它将通过握手层与服务器进行算法协商、身份认证、确定通信密钥等操作。然后,记录层根据协商的压缩算法,对数据进行分片压缩处理;用共享密钥,根据下面公式计算MAC:hash(MAC_write_secret+pad_2+hash(MAC_write_secret+pad_1+seq_num+SSLCompressed.type+SSLCompressed.length+SSLCompressed.fragment))。计算MAC后,加密压缩数据和MAC 形成密文。服务器方做相反的处理,在此就不赘述了。
安全通信程序设计
这里我们采用C/S模式,上层应用只是简单的收发数据,目的主要是介绍如何利用OPENSSL函数接口,编程实现SSL/TLS协议,用户可以根据自己的需要跟上层应用相结合,如HTTPS等。涉及到的代码以服务器端为例子,服务端流程图如图2所示,客户端类似,不再赘述。
图2
常用函数及数据结构介绍
1)初始化SSL算法库函数:“int SSL_library_init(void);”。
2)初始化SSL上下文环境变量函数:“SSL_CTX *SSL_CTX_new(SSL_METHOD *meth);”。传入SSL协议算法,调用成功后返回SSL_CTX结构体指针,否则返回空。这里涉及到了一个重要的数据结构 SSL_CTX,这个数据结构定义的内容非常多,主要是SSL上下文环境,包括SSL握手中的证书文件和私钥、协议版本、随机数、主密钥、读写MAC密钥等信息,后面通过完整代码可以看出来。
3)以文件形式设置SSL证书函数。
int SSL_CTX_use_certificate_file(SSL_CTX *ctx,const char *file,int type);
这里, file用于设置证书文件的路径;type则是设置证书的编码类型。证书的编码类型一般分为两种:PEM格式和ASN1格式,即Base64编码格式和DER编码文件,它们分别传入SSL_FILETYPE_PEM、SSL_FILETYPE_ASN1。
4)以文件形式设置SSL私钥。
int SSL_CTX_use_PrivateKey_file(SSL_CTX *ctx,const char *file,int type);
参数与设置证书一样,这里就不重复了。这里是SSL初始化时涉及到的重要的函数。下面以服务器为例,给出初始化的代码。初始化后,就可以开始建立SSL连接了。
//初始化OpenSSL环境
SSL_load_error_strings();
SSLeay_add_ssl_algorithms();
SSL_METHOD *meth;
meth = SSLv23_server_method();
//使用SSL V2或V3协议
ctx = SSL_CTX_new (meth);
//初始化SSL上下文
if (!ctx)
{
return FALSE;
}
//设置验证客户端(SSL中,服务器对客户端的验证时可选的,默认模式是不验证客户端证书的)
//SSL_CTX_set_verify(ctx,SSL_VERIFY_PEER|SSL_VERIFY_FAIL_IF_NO_PEER_CERT,verify_callback_server//验证回调函数);//如果验证客户端
SSL_CTX_set_verify(ctx,SSL_VERIFY_FAIL_IF_NO_PEER_CERT,0);
//如果不验证客户端
SSL_CTX_load_verify_locations(ctx,ROOTCERTF,NULL);
//若验证,则放置CA证书
SSL_CTX_set_verify_depth(ctx,1);
SSL_CTX_set_cipher_list(ctx,"RC4-MD5");
//设置服务器证书
if (SSL_CTX_use_certificate_file(ctx, CERTF, SSL_FILETYPE_PEM) <= 0)
{
return FALSE;
}
//设置服务器私钥
if (SSL_CTX_use_PrivateKey_file(ctx, KEYF, SSL_FILETYPE_PEM) <= 0)
{
return FALSE;
}
//检查服务器私钥和证书是否匹配
if (!SSL_CTX_check_private_key(ctx))
{
return FALSE;
}
5)新建SSL句柄函数:“SSL* SSL_new(SSL_CTX* ctx);”。这个函数利用前面初始化好的SSL_CTX数据结构,来形成一个SSL数据结构,用于后面的通信。
6)绑定SSL与Socket句柄函数:“int SSL_set_fd(SSL* ssl,int fd);”。参数中,ssl就是用调用SSL_new后返回的SSL结构体指针,fd为Socket链接句柄。
7)建立SSL连接函数,其中“int SSL_accept(SSL *ssl);”用于服务器,接受一个SSL链接,类似Socket中的accept函数;“int SSL_connect(SSL *ssl);”用于客户端,连接服务器,类似Socket中的connect函数。
传入上述绑定完成的SSL结构体指针后,则可建立SSL连接。如果成功,返回1,否则为0。下面以服务器为例,给出创建连接的代码。
//新建socket句柄
listen_sd = socket (AF_INET, SOCK_STREAM, 0);
//初始化sockaddr_in结构体,设置TCP协议和端口
memset (&sa_serv, '\0', 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));
if(err < 0)
{
//错误处理……
return;
}
//侦听,TCP连接
err = listen (listen_sd, 5);
if(err < 0)
{
//错误处理……
return;
}
//接收连接
clientFd = accept(sockFd, NULL, NULL);
……
//新建SSL连接句柄
if((ssl = SSL_new(ctx)) == NULL)
{
return;
}
//设置SSL连接Socket句柄
SSL_set_fd(ssl, clientFd);
//接收SSL连接
if(SSL_accept(ssl) == 0)
{
return;
}
客户端与服务器核心部分代码类似,在此就不赘述了。建立SSL安全连接后,我们可以进行数据交换。
8)发送SSL读取/数据函数。
int SSL_write(SSL *ssl,const void *buf,int num);
int SSL_read(SSL *ssl,const void *buf,int num);
上述函数类似Socket编程中的write和read函数,其中ssl为SSL连接句柄,buf为数据缓冲区指针,num为长度。
最后调用SSL_free(SSL *ssl)函数释放连接。到此为止,一个SSL协议基本搭建完成,加上多线程技术,一个基于SSL的安全通信软件就完成了。
此外还可以通过OPENSSL中的函数,读取对方证书名称、单位以及协议的安全指标等具体内容。服务器端代码如下:
/*打印所有加密算法的信息(可选)*/
strcpy(msg,SSL_get_cipher (ssl));
//自定义显示……
/*得到客户端的证书并打印些信息(可选) */
client_cert = SSL_get_peer_certificate (ssl);
if (client_cert != NULL)
{
strcpy(msg,"Client certificate:");
//自定义显示……
}
//得到对方证书信息
str=X509_NAME_oneline (X509_get_subject_name (client_cert), 0, 0);
strcpy(msg,"subject:");
//自定义显示……
OPENSSL_free (str);
str = X509_NAME_oneline (X509_get_issuer_name (client_cert), 0, 0);
strcpy(msg,"issuer:");
//自定义显示……
OPENSSL_free (str);
总结
本文主要向大家介绍了利用OPENSSL实现SSL/TLS编程。本文附带的程序是一个多线程的通信软件,因为代码过长,上文只贴出了涉及到SSL/TLS的核心部分,具体代码见附带工程文件。为了方便区分,我还设计了一个连接状态栏和数据交换栏,运行界面如图3和图4所示。
图3
图4
对于身份验证用到的数字证书,文中没有介绍,我会在以后单独介绍数字证书的原理和应用,以及OPENSSL中对数字证书的操作,敬请关注。