总所周知,若服务器的私钥泄漏,任何可以访问私钥的人都可以在会话建立时解密消息查看会话密钥,用会话密钥解密所有数据。所以确保你的私钥绝对私密至关重要。但有时私钥确实会泄漏,或因为系统被黑,或因为有访问权限的内部人士,或因为政府的命令。

如果你知道密钥已经暴露了,可以生成新的公私密钥对和新的证书,把旧证书撤销掉,这样客户端就不会再接受它,即使拿到旧私钥的人也不能用旧证书冒充你。但这样只是照顾到了将来的数据交换。当窃取了私钥的人监测并记录下了以前的会话,他们仍能用旧密钥解密并查看以前会话中数据。

有种安全连接的保护方法甚至连这种回溯性破解也可以防护。它被称为完全正向保密(perfect forward secrecy,简称PFS)。本文详细介绍PFS的概念、发展历史、工作原理、适用协议等信息,帮助你深入了解PFS。

什么是PFS

完全正向保密(perfect forward secrecy,简称PFS)是信息安全中提出的观点。它要求:

  • 一个密钥只能访问由它所保护的数据;
  • 用来产生密钥的元素一次一换,不能再产生其他的密钥;
  • 一个密钥被破解,并不影响其他密钥的安全性。

该设计旨在长期使用密钥不能确保起安全性的情况下而不影响过去会话的保密性。

发展历史

前向保密由Whitfield Diffie、Paul van Oorschot、Michael James Wiener提出的概念。它用来描述一个属性站对站协议(STS),其所述的是使用长时间的密钥来加密。

2014年,由于心血漏洞(存在服务器私钥泄漏的可能),没有使用前向保密的站点,加密通信的数据可以通过私钥来解密,导致信息泄漏,这使得前向保密被重视起来。

适用协议

PFS适用于:

  • IPsec的可选功能(RFC 2412)
  • Secure Shell(SSH)协议
  • SSL/TLS协议

实现原理

PFS为客户端和服务器端提供了一种为会话创建共享密钥的办法,而这个密钥不会暴露给任何监测数据交换的人。

正向保密的标准实现是采用某种Diffie-Hellman密钥交换。Diffie-Hellman的基本思想是利用整数模素数群的乘法性质。计算某个数的幂在群中的值很容易,即便这个群和幂都很大也没关系。但用已知的数学知识做逆向计算非常困难,即给定一个数和一个值,很难根据这个群中的值算出这个数的幂是多少(这被称为离散对数问题)。Diffie-Hellman 密钥交换的双方都使用相同的群和原根,但所用的幂和生成的值不同,然后他们互相交换由各自的幂生成的值。最终双方都得到了一个结合了两个私密幂的共享值。任何监测数据交换的人要想得到私密幂,都要做逆向工作去解决计算困难的离散对数问题,并且只要值足够大,以现有的技术计算出结果所需要的时间让这种计算失去了实际意义。

实现方式

但对浏览器来说有个坏消息,很多web服务器都不支持安全连接的完全正向保密。部分原因是这样会增加一些开销,但经常仅仅是因为用了过时的安全实现。

Google在对正向保密的支持上非常积极主动,并且其他主流网站也开始支持它了。在Chrome浏览器中,你可以点击绿色“锁”型图标,在连接的详细信息中是否给出包含字母DHE的密钥交换,如:ECDHE_RSA,来获知查某个安全站点是否使用了完全正向保密。

在客户端

在Java程序代码中要求使用PFS相当容易,使用系统属性https.cipherSuites=TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA。如果你想控制每条连接上的协议,可以采用跟控制协议版本号相同的办法,参考github代码库中的com.sosnoski.tls.ForceSuite类。这可能是在Oracle的Java 7上用TLS 1.1能用到的最好密码套件。

如果用TLS 1.2 ,使用更强更安全的SHA2摘要算法,此密码套件可升级成TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA256

理想情况下,用GCM(伽罗瓦/计数器模式)之类的模式代替CBC更好,但Oracle的 Java 7 JSSE (Java 安全套接字扩展)实现还不支持。

说明:

  • TLS指TLS协议。
  • ECDHE表明使用带有短暂性密钥的椭圆曲线Diffie-Hellman密钥交换,也就是说要为每个会话创建新密钥并且事后也不会记下来。
  • RSA表明用RSA非对称加密保护TLS握手的安全。
  • AES_128_CBC表明在密码块链接模式中用带有128位密钥的AES非对称加密保护真正的数据交换。
  • SHA表明用SHA安全哈希算法。

在服务端

一般用应用服务器上的配置选项控制密码套件。例如在使用带Java连接器的Tomcat时,可以用<Connector>元素上的ciphers属性限制密码套件,所以跟上面客户端的配置相匹配的应该是

<**Connector … ciphers="TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA" …/**>

而在用APR连接器时应该是SSLCipherSuite属性。