SSL

SSL 即 Secure Sockets Layer,是提高信道安全性的协议。不使用 SSL 的 HTTP 通信,是不安全的,所有信息明文传输。

注意:SSL 应用于 socket,因此理论上 UDP、UnixSocket 都可以使用 SSL 协议。是现实反正我知道 openssl 可以运用于 TCP 和 UDP。查了资料显示,DTLS 协议实现了在 UDP 协议之上的 TLS 安全层。

安全通信应该具有下列所需的特性:

  • 机密性:对数据进行加密,仅仅只有接收方和发送方能够对数据解密,其他方就算能获取数据也看不懂。(使用对称加密算法)
  • 报文完整性:防止他人篡改数据。(可以用 MAC 来判断)
  • 端点鉴别:鉴别另一方确实是我想要进行通信的一方。(可以用数字签名)

几个基本概念:

  • 不重数
  • 对称加密
  • 非对称加密
  • 哈希算法
  • MAC 报文鉴别码

不重数解决的是,防止 SSL 握手阶段重放攻击,基本的思想来自 TCP 握手阶段的 随机序号(保证了上次建立连接发送的 SYN 包不会引起下次连接)。

对称加密,必须是双方都持有同一密钥,那么就能对数据加密与解密。

非对称加密,可以用私钥加密,公钥解密(数字签名);也可以用公钥加密私钥解密(加密通信)。

哈希算法,把非定常数据 hash 成定长字符串,无法解密,不可逆向,运算比较耗时。

MAC,即 Message Authentication Code 报文鉴别码。发送方发送在报文末尾带 MAC 的 package。MAC = h(m, s) 其中 h 为 hash 函数,m 为报文数据,s 为鉴别密钥;接收方用 鉴别密钥重新计算一遍 MAC,如果和报文末尾的一致,那么就可以保证报文的完整性。

常见加密算法以及 Hash 函数:

对称加密:AES、DES、Blowfish、CAST、IDEA、RC2、RC5

非对称加密:DH、RSA、DSA、EC

Hash 函数:MD5、SHA1、SHA2

SSL 握手、通信、挥手的整个流程

1. SSL 握手

(1) 客户端发送它支持的加密算法列表以及一个不重数

(2) 服务器从列表中选出一个对称加密算法一个非对称加密算法一个 MAC 算法。它把三种算法连同自己的数字证书以及自己的不重数发给客户端。

(3) 根据 CA 给的公钥解密数字签名,得到 hash 值。验证该 hash 值是否和服务器发过来的元素经过 hash 后的值一致,来进行端点鉴别。

(4) 客户端生成一个 前主密钥 PMS(Pre-Master Secret),并用服务器给的公钥加密该 PMS,然后发给服务器。

注意:这里双方并不直接使用 PMS 对数据进行加密;而是,客户端和服务器分别通过 PMS 和自己收到的不重数,使用相同的密钥导出函数计算出自己的主密钥 MS(Master Secret),然后把 MS 切片成一个数据加密密钥 S 和一个 MAC。这样总共得到四个密钥,客户端和服务器共享这 4 个密钥。然后后面的机密性和数据完整性都由这 4 个密钥来实现。

(5) optional

(6) optional

(7) 客户端发送一个加密后 “finished” 报文表示握手阶段结束。

(8) 服务端发送一个加密后 “finished” 报文表示握手阶段结束。

注意:后续,客户端和服务器各需发送一个所有握手报文的一个 MAC 用于保证数据完整性,使握手免受篡改危害。见自顶向下p414.

(9) 用 S 进行加密通信。用 MAC 保证数据完整性。

2. SSL 挥手

不能简单的用 TCP FIN 来标识 SSL 挥手,因为这样会造成 截断攻击,即破坏者会直接发送一个 TCP FIN 过早的结束会话。具体见自顶向下p414.

使用 openssl 建立 安全 TCP 信道

这是 TCP 的安全通信。

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

1.1 初始化 OpenSSL

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

接下来是 openssl 库初始化的代码,根据 OpenSSL 的不同版本调用了不同的 API 进行初始化。

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
#if OPENSSL_VERSION_NUMBER >= 0x10100003L

if (OPENSSL_init_ssl(OPENSSL_INIT_LOAD_CONFIG, NULL) == 0)
{
LOG_ERROR << "OPENSSL_init_ssl() failed!\n";
return -1;
}

/*
* 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

1.2 创建 CTX

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

1
2
3
4
5
6
7
8
9
10
11
12
13
//客户端、服务端都需要调用
SSL_CTX_new(); //申请SSL会话环境

//若有验证对方证书的需求,则需调用
SSL_CTX_set_verify(SSL_CTX *ctx, int mode,
int (*verify_callback)(int, X509_STORE_CTX *)); //指定证书验证方式
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(); //验证所加载的私钥和证书是否相匹配

1.3 创建 SSL 套接字

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

1
2
3
4
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); //以只写模式绑定流套接字

1.4 完成 SSL 握手

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

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

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

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

1.5 数据传输

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

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

1.6 会话结束

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

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

引用