Linux系统安全_PFS/ECDH

数百万网站和数十亿网民都依靠SSL保护敏感数据如密码、信用卡号码和个人信息的传输。但最近泄漏的机密文档显示,美国国家安全局会记录大量互联网流量,储存加密数据以用于以后的密码分析。美国当然并不是唯一一个这么做的国家,沙特、中国和伊朗都是如此。保留的加密数据可以通过各种方法解密,例如法庭命令,社会工程,网站攻击,乃至密码分析。如果得到了密钥,所有相关网站的历史流量可以一次性解密。这就像打开了潘多拉的盒子。然而,互联网实际上存在应对之策——密钥协商协议Perfect Forward Secrecy(PFS),如果SSL网站的私钥泄漏,PFS可以保护以前的加密流量不会因此受到影响,因为PFS可以为每次会话分配不同密钥加密通信。PFS需要客户端浏览器和服务器端同时支持才适用。–solidot.org

但问题也不止如此,当HTTPS的网站的私钥被攻破,那么攻击者便可以很容易的制造出了中间人攻击。 这便需要一种向前的保密协议。

该协议要求,今天的秘密即使在将来的密钥被泄露,而秘密也是不被泄露的。要理解这个协议,我们首先可以从经典的TLS三次握手AES128-SHA加密套件开始,在握手期间,服务器出示证书,客户端和服务器同意主密钥。

这个过程是建立在48个字节的预置密码,是由客户端使用服务器公开的密钥进行生成和加密。然后在三次握手的过程中,客户端将密钥信息交换发送给服务器。主密钥来自客户端与服务器进行hello会话的公钥与随机值。这个方案是安全,只要服务器能够对预置的密码解密(自己的私钥)客户端发送的数据。假设,攻击者记录该服务器1年内的所有客户端和服务器之间的交流数据。2年后,服务器退役,并送往循环再造,攻击者是能够恢复出磁盘驱动器的私钥。根据这个私钥,便可以对会话进行解密。攻击者依然可以恢复密码和其他敏感信息。

现在主要的问题一个事实,私钥被用于2个目的,认证服务器加密服务共享同一个密钥。认证只是在建立通信时,而加密则持续数年之久。

为了解决这个问题的方法之一便是保持使用私钥进行验证,但是要使用一个共享密钥的独立机制。Diffie-Hellam密钥交换协议,在TLS是如何工作的呢,服务器只需要生成1次:

1 P,是一个很大的素数,

2 g,是一个原根primitive root(它能生产1~P-1所有数的一个数)

3 现设g为p的原始根,则: g mod p,g^2 mod p,…g^p-1 mod p;两两互不相同,构成一个1~p-1的全体数的一个排列。

4 对于任意数b以及素数p的原始根g,可以找到一个唯一的指数i,满足b = g^I mod p,0C=i<=p-1,则称指数i为以g为底,模p的b的离散对数。

算法描述为:

假如A和B在不安全的网络上进行协商共同的密码:

1 A和B预先选择一个大素数P和一个原始根g;

2   A随机选择一个随机数Xa,Xa&lt;p,计算 Ya = g^Xa mod p ,然后把Ya发送给B。

3   B随机选择一个随机书Xb,Xb&lt;p,计算 Yb = g^Xb mod p,然后把Yb发送给A。

4   每一方保存X值,把Y值交给对方。

5   A 用户计算出k = Yb^Xa mod p;

6   B 用户计算出 k’ = Ya^Xb mod p;
k = Yb^Xa mod p =(g^Xb)^Xa mod p = (g^Xa)^Xb mod p = Ya^Xb mod p = k’;

因为不安全的线路上,窃听者只能得到a,p,X,Y,除非能够计算离散对数x和y,否则将无法得到密钥k,因为k是A和B独立计算出的密钥。有Xa,Xb计算出Ya,Yb容易,但反过来由Ya,Yb计算出Xa,Xb很难。安全性上基于求有限域上求离散对数的难度。

但DH算法却容易遭中间人攻击。

为了避免中间人的攻击,构造出了基于有限域上椭圆曲线之间的同源计算问题构造细腻的公钥密码系统。

假设y^2 = X^3 + ax +b,素数p和原始根g,这些参数都是公开的,事实上,它可以有服务器生成。

利用椭圆曲线是在RFC-4492中TLS延伸的部分描述的。与经典的DH交换密钥的方法不同,在客户端和服务器端需要同意各参数。完成本协议是在客户端和服务器发送hello的消息内,虽然可以定义任意一个参数,WEB浏览器将只支持少数的预定义的曲线,通常为NIST P-256,P-384,P-521.

下面简要说一下,DH的密钥交换椭圆曲线:

1 服务器随机选择一个整数a,和计算一个ag,未加密,但用自己的私钥进行签名,在服务器交换消息的时候,发送出去。

2 客户端检测签名是否正确,然后,随机选择一个整数b,计算出bg,利用客户端密钥进行消息交换。客户端也会计算b * ag = abg;这个预置的密码来自服务器主密钥的派生。

3 服务器端接收bg,计算a *bg = abg。客户端知道这个是相同的预置密钥。

4 窃听者将只能获得ag或bg,而无法有效的计算出abg;

 

使用ECDHE-RSA-AES128-SHA加密套件(例如P-256)已经是很程度上提高了速度。也是因为DHE-RSA-AES128-SHA缩小所涉及的各种参数的规模。

但不是所有浏览器都支持椭圆曲线加密,最近的chrome和firefox支持了NIST P-256, P-384, P-521.但大多数的IE浏览器还支持的不是很好。,最近的openssl已经加入了ECDHE密码服务套件,如果要使用64位的优化本版本,需要选择OPENSSL 1.0.1,启用ec_nistp_64_gcc_128选项。

在选择套件上,ECDHE-RSA-AES128-SHA:AES128-SHA:RC4-SHA是大多浏览器所兼容的。如果要选择PFS的方式,ECDHE-RSA-AES128-SHA,DHE-RSA-AES128-SHA,EDH-DSS-DES-CBC3-SHA。但需要确保密码套件的顺序。Nginx (1.0.6/1.1.0)是ssl_prefer_server_ciphers。Apache(2.3.3)则是SSLHonorCipherOrder。

但在使用PFS的时候,也要注意服务器端定期更新所生成的随机密钥。

Openssl的检测使用指令:openssl s_client -tls1 -cipher ECDH -connect 127.0.0.1:443

Nginx 关于PFS的代码:

/* a temporary 512-bit RSA key is required for export versions of MSIE */
494 SSL_CTX_set_tmp_rsa_callback(conf->ssl.ctx, ngx_ssl_rsa512_key_callback); 
495 
496 if (ngx_ssl_dhparam(cf, &conf->ssl, &conf->dhparam) != NGX_OK) {
497 return NGX_CONF_ERROR;
498 } 
499 
500 if (ngx_ssl_ecdh_curve(cf, &conf->ssl, &conf->ecdh_curve) != NGX_OK) {
501 return NGX_CONF_ERROR;
502 }

SSL_CTX_set_tmp_rsa_callback 设置callback for ssl

ngx_ssl_dhparam ()

使用RSA算法的时候,产生一个临时的DH密钥磋商发生,这样会话将根据这个临时的密钥加密。而证书中的密钥作为签名。这样增加了安全性。

该方法实现了OPENSSL提供的默认DH_METHD,实现了根据密钥参数生成DH公私钥,以及根据DH公钥(一方)以及DH私钥(另一方)来生成一个共享密钥,用于密钥交换。

ngx_int_t
420 ngx_ssl_dhparam(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *file) 
421 {
422 DH *dh;
423 BIO *bio;
424 
425 /*
426 * -----BEGIN DH PARAMETERS-----
427 * MIGHAoGBALu8LcrYRnSQfEP89YDpz9vZWKP1aLQtSwju1OsPs1BMbAMCducQgAxc
428 * y7qokiYUxb7spWWl/fHSh6K8BJvmd4Bg6RqSp1fjBI9osHb302zI8pul34HcLKcl
429 * 7OZicMyaUDXYzs7vnqAnSmOrHlj6/UmI0PZdFGdX2gcd8EXP4WubAgEC
430 * -----END DH PARAMETERS-----
431 */
432 
433 static unsigned char dh1024_p[] = {
434 0xBB, 0xBC, 0x2D, 0xCA, 0xD8, 0x46, 0x74, 0x90, 0x7C, 0x43, 0xFC, 0xF5,
435 0x80, 0xE9, 0xCF, 0xDB, 0xD9, 0x58, 0xA3, 0xF5, 0x68, 0xB4, 0x2D, 0x4B,
436 0x08, 0xEE, 0xD4, 0xEB, 0x0F, 0xB3, 0x50, 0x4C, 0x6C, 0x03, 0x02, 0x76,
437 0xE7, 0x10, 0x80, 0x0C, 0x5C, 0xCB, 0xBA, 0xA8, 0x92, 0x26, 0x14, 0xC5,
438 0xBE, 0xEC, 0xA5, 0x65, 0xA5, 0xFD, 0xF1, 0xD2, 0x87, 0xA2, 0xBC, 0x04,
439 0x9B, 0xE6, 0x77, 0x80, 0x60, 0xE9, 0x1A, 0x92, 0xA7, 0x57, 0xE3, 0x04,
440 0x8F, 0x68, 0xB0, 0x76, 0xF7, 0xD3, 0x6C, 0xC8, 0xF2, 0x9B, 0xA5, 0xDF,
441 0x81, 0xDC, 0x2C, 0xA7, 0x25, 0xEC, 0xE6, 0x62, 0x70, 0xCC, 0x9A, 0x50,
442 0x35, 0xD8, 0xCE, 0xCE, 0xEF, 0x9E, 0xA0, 0x27, 0x4A, 0x63, 0xAB, 0x1E,
443 0x58, 0xFA, 0xFD, 0x49, 0x88, 0xD0, 0xF6, 0x5D, 0x14, 0x67, 0x57, 0xDA,
444 0x07, 0x1D, 0xF0, 0x45, 0xCF, 0xE1, 0x6B, 0x9B
445 };
446 
447 static unsigned char dh1024_g[] = { 0x02 };

if (file->len == 0) {
451 
452 dh = DH_new();
453 if (dh == NULL) {
454 ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "DH_new() failed");
455 return NGX_ERROR;
456 }
457 
458 dh->p = BN_bin2bn(dh1024_p, sizeof(dh1024_p), NULL);//保存公钥
459 dh->g = BN_bin2bn(dh1024_g, sizeof(dh1024_g), NULL);//保存私钥
460 
461 if (dh->p == NULL || dh->g == NULL) {
462 ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0, "BN_bin2bn() failed");
463 DH_free(dh);
464 return NGX_ERROR;
465 }
466 
467 SSL_CTX_set_tmp_dh(ssl->ctx, dh);//装载
468 
469 DH_free(dh);
470 
471 return NGX_OK;
472 }

503 ngx_ssl_ecdh_curve(ngx_conf_t *cf, ngx_ssl_t *ssl, ngx_str_t *name)
504 { 
505 #if OPENSSL_VERSION_NUMBER >= 0x0090800fL
506 #ifndef OPENSSL_NO_ECDH
507 int nid;
508 EC_KEY *ecdh;
509 
510 /*
511 * Elliptic-Curve Diffie-Hellman parameters are either "named curves"
512 * from RFC 4492 section 5.1.1, or explicitly described curves over
513 * binary fields. OpenSSL only supports the "named curves", which provide
514 * maximum interoperability.
515 */
516 // #define NGX_DEFAULT_ECDH_CURVE  "prime256v1"
517 nid = OBJ_sn2nid((const char *) name->data); //有名称查代号 
518 if (nid == 0) {
519 ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
520 "Unknown curve name \"%s\"", name->data);
521 return NGX_ERROR;
522 }
523 
524 ecdh = EC_KEY_new_by_curve_name(nid); //创建ec_key,具体参数在ec_key.c文件中EC_KEY_new().
525 if (ecdh == NULL) {
526 ngx_ssl_error(NGX_LOG_EMERG, ssl->log, 0,
527 "Unable to create curve \"%s\"", name->data);
528 return NGX_ERROR;
529 }
530 
531 SSL_CTX_set_options(ssl->ctx, SSL_OP_SINGLE_ECDH_USE);
532 
533 SSL_CTX_set_tmp_ecdh(ssl->ctx, ecdh); //装载
534 
535 EC_KEY_free(ecdh);

return

}

ECDH参数不完全和DH一样,对与DH所产生参数是一个耗时的过程,所以服务器允许通过外部文件加载DH参数。ECDH的参数的形成是一套硬编码的曲线,所以参数的形成只是寻找他们,当服务器使用时,便可以加载他们。但在openssl1.0.2之前是不支持的。

可以做的是,提供ECDH参数从一个文件里读取,为了一个共同的组的一个后备。P-256是一个不错的选择则。

EC_KEY *ecdh;

ecdh = EC_KEY_new_by_curve_name(NID_X9_62_prime256v1);

if(echd == NULL) /*error */

SSL_CTX_set_tmp_ecdh(ctx,ecdx);

而这个设置不需要在服务器端进行设置,而使用的参数是有服务器端指定。

One Reply to “Linux系统安全_PFS/ECDH”

发表评论

您的电子邮箱地址不会被公开。