OPENSSL:密钥交换算法的选择


一.从OPENSSL握手过程谈起

当我们尝试着建立一个加密连接的时候,首先需要在客户端和服务器之间进行加密套件的协商以及会话密钥的协商,不幸的是,这一过程是复杂的,并且容易遭到攻击的,因此,单个用户在不使用第三方库的情况下很难建立一个加密连接。还好TLS/SSL协议族为我们提供了一个足够安全的解决方案,而OPENSSL作为最广为人知的也是最常使用的TLS/SSL实现方案也在互联网中得到了广泛应用。
TLS/SSL采用握手(handshake)这个词用来形容客户端与服务器协商会话密钥(session key)的过程,在这一过程中,双方交互信息,并在信息中得到相同的加密套件和会话密钥。这一过程通常而言由客户端发起,客户端会发送client-hello信息给正在监听的服务器,服务器收到该信息之后返回server-hello等信息,并由此开始握手过程。
加密套件(cipher-suite)就是客户端-服务器所用密钥交换算法、认证算法、数据加密算法、摘要算法的组合,加密套件在openssl源码中的./ssl/s3_lib.c中定义,其中第三个加密套件如下所示:

{
1,
SSL3_TXT_RSA_RC4_40_MD5,        
SSL3_CK_RSA_RC4_40_MD5,
SSL_kRSA,                                    //密钥协商算法为RSA
SSL_aRSA,                                    //签名认证算法为RSA
SSL_RC4,                                     //加密算法为RC4
SSL_MD5,                                     //摘要算法为MD5
SSL_SSLV3,
SSL_EXPORT|SSL_EXP40,
SSL_HANDSHAKE_MAC_DEFAULT|TLS1_PRF,
40,
128,
}

通过密钥协商算法,我们可以得到用于加密算法的密钥,在SSL/TLS执行过程中,client-hello信息中会包括一个客户端所支持的加密套件的列表,而服务器在收到client-hello信息中会从中选择一个自己也支持的加密套件作为本次会话所用加密套件,并在server-hello信息中返回自己所选择的加密套件。
现在问题来了:服务器究竟是如何选择加密套件的?不同的实现方案中选择的方式不同,在OPENSSL中,加密套件的选择是和服务器所拥有的证书类型、临时公钥设置情况以及client提供的加密套件的顺序有关的。

二.OPENSSL是在哪里选择加密套件的

在server启动SSL_accept函数进行监听之后,整个程序进入一个类似于有限状态机的过程中,每个状态及状态的转移在s3_srvr.c的ssl3_accept()函数中定义,当收到client-hello信息之后,server调用函数ssl3_get_client_hello读取client-hello中的信息,并且选择相应的加密套件。
具体的加密套件选择函数是ssl3_choose_cipher(在s3_lib.c中定义),返回值为选好的加密套件,这个函数在ssl3_get_client_hello中被调用,进入该函数之后,我们可以在循环中看见如下的代码:

ssl_set_cert_masks(cert,c);
mask_k = cert->mask_k;
mask_a = cert->mask_a;
emask_k = cert->export_mask_k;
emask_a = cert->export_mask_a;

这一过程中,首先调用了ssl_set_cert_masks()函数,这个函数的作用是根据证书和临时公钥的设置情况设置掩码,也就是mask,mask里包括了本次连接中支持的密钥交换算法和认证算法的信息,之后的语句将刚才设置的掩码读取出来,前两种是正常的掩码,后两种是和出口限制有关的,我们在这里只关注一般情况下的掩码。
对于服务器端的每一个加密套件(函数参数中的c),openSSL都要执行一遍这样的步骤,这也就是外面所包裹着的循环的意义,接下来程序的执行流程是这样的:

alg_k=c->algorithm_mkey;
alg_a=c->algorithm_auth;
......          
if (SSL_C_IS_EXPORT(c))
{
    ok = (alg_k & emask_k) && (alg_a & emask_a);
......
}
else
{
    ok = (alg_k & mask_k) && (alg_a & mask_a);
.......
}

这段代码中,先获取了处于当前循环的加密套件的密钥交换算法(alg_k)和认证算法(alg_a),在openSSL中,每一个密钥交换算法(认证算法)都表示为一位,具体来说如下所示(源文件ssl/ssl_locl.h中):

#define SSL_kRSA        0x00000001L /* RSA key exchange */
#define SSL_kDHr        0x00000002L /* DH cert, RSA CA cert */         
#define SSL_kDHd        0x00000004L /* DH cert, DSA CA cert */      
#define SSL_kEDH        0x00000008L /* tmp DH key no DH cert */
#define SSL_kKRB5       0x00000010L /* Kerberos5 key exchange */
#define SSL_kECDHr      0x00000020L /* ECDH cert, RSA CA cert */
#define SSL_kECDHe      0x00000040L /* ECDH cert, ECDSA CA cert */
#define SSL_kEECDH      0x00000080L /* ephemeral ECDH */
#define SSL_kPSK        0x00000100L /* PSK */
#define SSL_kGOST       0x00000200L /* GOST key exchange */
#define SSL_kSRP        0x00000400L /* SRP */

每一个密钥交换算法都占据着不同的位,而在mask中,如果证书和临时公钥的设置情况允许某种密钥交换算法的使用,那么这一位就会置1,而上面一段代码的ok也会变成1,表示密钥交换算法是可以使用的。
之后,openSSL还会对ok为1的加密套件进行其他检测,但是加密套件(对于密钥交换算法)的选择方式的核心部分就在这里。

三.ssl_set_cert_masks函数

所以,其中起着决定性作用的就是ssl_set_cert_masks这个函数,对这个函数的深入理解有助于我们进一步理解SSL/TLS协议族,并且在实际中将DH\EC-DH一族的密钥交换算法应用到我们自己的协议中(现在网上教人怎么使用openSSL的教程中,证书一般都是以RSA证书为例子的)。
可能细心的同学也已经注意到,在上面openSSL对于密钥交换算法的分类中,对于DH(Diffie-Hellman)协议以及它的椭圆曲线版本EC-DH都有三个变种。这里可以事先说明一下,采用kDHr(以及kECDHr)的情况是:服务器端的采用公钥为DH参数的公钥,也就是 g^x,其中x为指数,也就是所谓的私钥,服务器端的证书是针对这个公钥的,而为这个证书签名的证书(上级CA)是采用RSA私钥对其进行签名的;与此对应的,采用kDHd/kECDHd的情况时,上级CA采用的是DSA/ECDSA私钥对服务器的公钥证书进行的签名,但证书本身所认证的公钥还是DH/EC-DH公钥,对于这两种密钥交换算法来说,服务器端参加密钥交换的参数都由证书本身提供。然而,对于第三类,也就是kEDH/kEECDH来说,服务器端参加密钥交换的数据并非由证书提供,而是由一个事先设定的临时(ephemeral)参数提供。
接下来我们进入ssl_set_cert_masks(源代码在ssl_lib.c中)这个函数来看一看以上机制的实现,首先是这样一段代码:

rsa_tmp = (c->rsa_tmp != NULL || c->rsa_tmp_cb != NULL);
......
dh_tmp = (c->dh_tmp != NULL || c->dh_tmp_cb != NULL);
......
have_ecdh_tmp = (c->ecdh_tmp || c->ecdh_tmp_cb || c->ecdh_tmp_auto);

上面代码的目的是查看在通信之前是否存在了临时公钥,临时公钥的存在途径有两种,要么是设置了实在的某个数作为临时公钥,还有一种就是调用回调函数生成一个临时参数,如果这些途径中有一个成立,则视为本次协议握手过程中存在临时公钥,对应的tmp变量为1。
当查看临时公钥的存在性之后,出现的是下面一段代码:

cpk = &(c->pkeys[SSL_PKEY_RSA_ENC]);//获取证书中的用于加密的RSA公钥
rsa_enc = cpk->valid_flags & CERT_PKEY_VALID;
//如果密钥合法,则RSA_ENC变量为1,表示存在一个合法的用于RSA加密的公钥
rsa_enc_export = (rsa_enc && EVP_PKEY_size(cpk->privatekey) * 8 <= kl);
cpk = &(c->pkeys[SSL_PKEY_RSA_SIGN]);
rsa_sign = cpk->valid_flags & CERT_PKEY_SIGN;//存在用于RSA签名的公钥
cpk = &(c->pkeys[SSL_PKEY_DSA_SIGN]);
dsa_sign = cpk->valid_flags & CERT_PKEY_SIGN;//用于DSA签名的公钥
cpk = &(c->pkeys[SSL_PKEY_DH_RSA]);
dh_rsa = cpk->valid_flags & CERT_PKEY_VALID;
//存在上级CA使用RSA公钥进行签名的DH公钥
dh_rsa_export = (dh_rsa && EVP_PKEY_size(cpk->privatekey) * 8 <= kl);
cpk = &(c->pkeys[SSL_PKEY_DH_DSA]);
dh_dsa = cpk->valid_flags & CERT_PKEY_VALID;
//存在上级CA采用DSA公钥进行签名的DH公钥
dh_dsa_export = (dh_dsa && EVP_PKEY_size(cpk->privatekey) * 8 <= kl);
cpk = &(c->pkeys[SSL_PKEY_ECC]);
#ifndef OPENSSL_NO_EC
have_ecc_cert = cpk->valid_flags & CERT_PKEY_VALID;
//存在一个合法的ECC公钥(也就是椭圆曲线上的点g^x)
#endif

证书的pkeys是一个数组,而上面的SSL_PKEY_RSA_ENC等数字就是对于这个数组的索引,表示所存的各种类型的公钥,上面的程序目的就是读取证书结构中的公钥,并认证公钥的合法性。

if(rsa_enc || (rsa_tmp && rsa_sign))    //如果证书认证的是RSA加密公钥而且存在RSA临时参数
    mask_k |= SSL_kRSA;                  //就可以使用kRSA作为密钥交换算法
......
if (dh_tmp)                              //如果存在DH临时参数
    mask_k |= SSL_kEDH;                  //可以使用kEDH密钥交换算法
if (dh_rsa)                              //如果存在由RSA签名的认证DH公钥的证书
    mask_k |= SSL_kDHr;                  //可以采用kDHr密钥交换算法
......
if (dh_dsa)                              //如果存在由DSA签名的认证DH公钥的证书
    mask_k |= SSL_kDHd;                  //可以采用kDHd密钥交换算法

接下来对于ECDH密钥交换算法的设置过程也和上面类似,之后掩码就会被设置到证书数据结构(即代码中的变量c)里,ssl3_choose_cipher函数会将这些掩码读出来,看看当前的加密套件所使用的密钥交换算法是否符合证书的要求,以确定究竟使用那个加密套件。
现在,我们就可以根据上面的分析总结出一套在我们自己编写的程序中引入带有DH/EC-DH密钥交换算法的加密套件的条件了:你可以在服务器端的SSL结构中添加一个临时的DH/EC-DH参数或者在服务器端使用一个经过上级CA采用RSA/DSA/EC-DSA算法签名的DH/EC-DH公钥证书,之后就可以使用对应的加密套件了。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 199,175评论 5 466
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 83,674评论 2 376
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 146,151评论 0 328
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 53,597评论 1 269
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 62,505评论 5 359
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 47,969评论 1 275
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 37,455评论 3 390
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,118评论 0 254
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 40,227评论 1 294
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,213评论 2 317
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 37,214评论 1 328
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,928评论 3 316
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 38,512评论 3 302
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 29,616评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,848评论 1 255
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 42,228评论 2 344
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 41,772评论 2 339

推荐阅读更多精彩内容