椭圆曲线密码学简介(三):ECDH和ECDSA
本文主要翻译自这篇文章,并参考笔者之前翻译的另外一篇文章ECDSA算法如何保护我们的数据。
译者注
本文承接上文所引出的离散对数问题,在加密和数字签名两个场景就离散对数问题的具体应用方式展开具体的讨论。首先介绍了用于定义一个特定椭圆曲线离散域的子群的领域参数,然后基于这组领域参数分别在加密领域介绍
ECDH算法,在数字签名领域介绍ECDSA算法。
领域参数(Domain parameters)
我们的椭圆曲线算法是在一个定义在一个椭圆曲线离散有限域的循环子群上面的,因此,我们的算法需要以下参数:
- 标识有限域规模的素数
- 椭圆曲线方程的系数和
- 产生子群的基准点
- 子群的阶
- 子群的协因子
一言以蔽之,我们算法的领域参数就是一个包含六个元素的元组
随机曲线(Random curves)
当我们在说离散对数问题是“hard”的时候,其实也并不完全对。有些特定的椭圆曲线所对应的离散对数问题可以用特殊目的的算法进行高效地破解。举个例子,所有满足的曲线(有限域的阶与椭圆曲线的阶相同)在Smart’s attack面前是脆弱的,该算法可以用经典计算机在多项式时间复杂度内解决离散对数问题。
现在,假设我给你一条椭圆曲线的领域参数。那么存在这样一个可能性,我已经发现了一些你并不知道的难度较低的椭圆曲线,并且还可能构思了一些破解我所给定的曲线的算法。我要如何向你证明我并不知道这条曲线的任何弱点呢?换句话说,我如何向你确保我所给你的这条曲线是安全的呢?
为了解决这类问题,有时候我们需要一个额外的领域参数:种子参数。这是一个用于产生方程系数和/或基准点的随机数。这些参数通过计算种子的哈希获得。众所周知,哈希的计算是“easy”的,但是求逆是“hard”的。


通过种子产生的曲线可以被认为是随机的。这种通过哈希来产生参数的原则被称为nothing up my sleeve,通过哈希计算产生的数可以在根本上排除其具有掩藏特性的嫌疑,在密码学当中被大量的使用。
从某种程度上说,这个技巧可以确保给定的曲线没有被特殊的认为加工以向其作者暴露其脆弱性。事实上,如果我同时给你一条曲线和一个种子,这意味着我并不能随意选择参数,你也能够相对比较明确其实我并不能对该曲线进行特殊目的的攻击。
用于产生和验证随机曲线的标准算法在ANSI X9.62当中有进行描述,主要基于SHA-1,如果你对此比较好奇,你可以阅读这篇文档:a specification by SECG,了解产生可验证的随机曲线的算法。
原作者写了一段简单的Python 脚本来验证当前所有基于OpenSSL的随机曲线,强烈推荐看一下。
椭圆曲线密码学(Elliptic Curve Cryptography)
前面经过了很多的铺垫,不过最终我们来到了这里,可以讲下椭圆曲线密码学到底是什么了,简洁的版本如下:
- 私钥(private key)是从随机选取的一个正整数,其中是子群的阶
- 公钥(public key)则是点,其中是子群的基准点
你看,如果我们知道和 ,结合其它的领域参数,求解是“easy”的。但是,如果我们知道和 ,求解私钥是“hard”的,因为这需要我们解决离散对数问题。
接下来我们介绍两个基于椭圆曲线密码学的公钥算法:
- ECDH(Elliptic Curve Diffie-Hellman):主要用于加密
- ECDSA(Elliptic Curve Digital Signature Algorithm):主要用于数字签名
ECDH加密算法(Encryption with ECDH)
ECDH是Diffie-Hellman algorithm基于椭圆曲线的变种,更像是一个key-agreement protocol,而不是一个加密算法。因为,ECDH仅仅定义了各种密钥如何产生以及在各方之间如何交换,具体如何用这些密钥来对数据进行加密由用户自己决定。
这个问题是如下解决的:双方(还是喜闻乐见的Alice and Bob)想安全的交换信息,第三方可能截取信息,但可能无法进行解码。这是传输层安全(TLS)设计的原则之一,下面来一起看一个例子。
具体工作原理如下:
-
第一步,Alice和Bob产生各自的私钥和公钥,Alice的私钥和公钥分别为和 ,Bob的私钥和公钥分别为和 。需要注意的是,Alice和Bob使用相同的领域参数:在相同的有限域内定义的相同的椭圆曲线上的相同的基准点
-
Alice和Bob通过不安全的渠道交换他们的公钥和 ,第三方可能截取和 , 但是无法仅仅通过公钥避开离散对数难题而推导出私钥和
-
Alice用自己的私钥和Bob的公钥计算,然后Bob也用自己的私钥和Alice的公钥计算,事实上,对于Alice和Bob,这个是相同的:
截取信息的中间人,只知道和 以及其它的领域参数,但是依然无法获得在Alice和Bob之间共享的秘密信息。这个问题就是众所周知的Diffie-Hellman problem,可以具体表述如下:
给出三个点, 和,求
或者等价于:
给出三个整数, 和,求
后面这个等价基于幂模运算,在原始的Diffie-Hellman算法当中使用。

Diffie-Hellman problem背后的基本原理在Youtube video by Khan Academy当中也有解释,一会儿也会对采用幂模运算的原始Diffie-Hellman算法进行介绍。
采用椭圆曲线的Diffie-Hellman problem被认为是“hard”的,可以等同于离散对数问题的难度级别,不过并没有数学上面的严格证明。我们能够确认的是并没有比离散对数更难,因为解决离散对数问题是解决Diffie-Hellman problem的一种方式。
现在,Alice和Bob已经获得了共享的机密信息,这样他们就可以用对称加密的方式进行数据交换。
举个例子,Alice和Bob可以使用的 坐标作为安全加密算法AES或者3DES对信息进行加密的密钥。
这个和TLS的做法类似,区别在于,TLS将坐标于其它相关的数字连接起来,然后计算连接结果字符串的哈希。
ECDH应用举例(Playing with ECDH)
这段代码可以用来计算基于特定椭圆曲线的公钥和私钥以及共享加密信息。
不同于我们之前所使用的例子,这段代码使用了一个标准的曲线,而不是一个在一个很小域上面的简单曲线。这个曲线是secp256k1,也是比特币里面用于数字签名的曲线。具体的领域参数如下:
- = 0xffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f
- = 0
- = 7
- = 0x79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 59f2815b 16f81798
- = 0x483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 9c47d08f fb10d4b8
- = 0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141
- = 1
这些参数来自OpenSSL 源码
这段代码非常简单,包含了一些我之前描述过的算法:点加法、翻倍与累加、ECDH等等。建议阅读并跑一下,代码的输出如下:
1 | Curve: secp256k1 |
Ephemeral ECDH
可能有些人除了ECDH,还听说过ECDHE。在ECDHE当中的“E”代表“Ephemeral”,指的是密钥的交换是暂时的。
举个ECDHE的应用例子,在TLS当中,服务器和用户端在每次建立起连接的时候都产生密钥对,然后用TLS证书对密钥进行签名后在双方进行交换。
ECDSA数字签名算法(Signing with ECDSA)
关于数字签名算法──ECDSA,在这篇文章已经有了较为详细的描述,不过为了行文的完整性,这里还是再复述一遍。
我们考虑这样的一个场景:Alice想用他的私钥对一个信息进行签名,然后Bob想用Alice的公钥来验证这个签名。没有其它人,只有Alice才能够产生这样有效的签名。因为Alice的公钥可以公开,因此,每个人可以检查这个签名。
再一次,Alice和Bob使用相同的领域参数。接下来要看的算法是ECDSA,应用与椭圆曲线的数字签名算法的变种。
ECDSA工作在加密信息的哈希上面,而不是信息本身。哈希函数的选择由用户自己确定,显然,为了确保安全性,还是需要采用密码级安全的哈希函数。加密信息的哈希需要进行截取以确保哈希的字节长度与子群的阶相等,被截取的哈希是一个正整数,通常用来表示。
Alice采用的信息签名算法工作流程如下:
- 从中随机选择一个整数
- 计算点,其中是领域参数当中的基点
- 计算数字,其中是点的 坐标
- 如果,选择另外一个
- 计算,其中是Alice的私钥,是 模 的乘法逆元
- 如果,则选择另外一个继续
最后是数字签名对。

用朴实的语言来描述,该算法首先产生机密信息,然后将这个机密信息用椭圆曲线上面的点乘法(根据之前的知识,这个算法也是单向容易,反向难)隐藏在当中。然后将通过方程捆绑在加密消息哈希当中。
需要注意的是为了计算,我们需要计算 模的逆元,我们已经在前面讲到只有在是素数的时候这个算法才能够有效。如果子群有非素数的阶,那么ECDSA是不能使用的。几乎所有标准曲线都用一个素数阶,而那些具有非素数阶的曲线并不适合于ECDSA。
数字签名验证(Verifying signatures)
为了对数字签名进行验证,我需要Alice的公钥,截取后的哈希,当然,也需要签名对,主要分以下三步:
- 计算
- 计算
- 计算点
如果则签名是有效的
算法的正确性(Correctness of the algorithm)
初看起来,隐藏在算法背后的逻辑并不是那么清晰,不过,当我们将目前所有列出的方程都放到一起就清楚明了了。
让我们从开始,我们知道,从公钥的定义可知,,其中是私钥,我们可以将式子改写如下:
再次利用和 的定义,我们可以将式子改写如下:
这里为了简化,我们将后续的省略,另外,因为由产生的循环子群的阶为,因此,是多余的。
在前面,我们定义,方程两边都乘以在除以,我们得到,将这个方程代入前面计算的方程,我们有:
这个方程与前面算法第二步当中产生签名算法计算的方程是相同的,那意味着产生签名和验证签名的时候,我们计算同一个点,只是采用了不同的方程,这也是算法有效的原因。
ECDSA应用举例(Playing with ECDSA)
当然,原作者同样写了一段python代码用来签名的产生和验证。这段代码与ECDH的代码有部分相同,特别是领域参数和公/私钥对产生算法。
下面是这段代码可能的一个输出内容:
1 | urve: secp256k1 |
你可以看到,这段代码首先对一段信息进行签名(字节信息“Hello!”),然后对签名进行验证。然后,试图用同样的签名对另外一段信息(“Hi there”)进行验证且失败了。最后,试图采用另外一个随机的公钥对同样的信息进行验证也失败了。
的重要性(The importance of )
当我们产生ECDSA签名的时候,保持随机数的机密性和随机性非常重要。如果我们在多个签名使用了同一个,或者我们的随机数生成器某种程度上是可预测的,则很容易受到攻击而泄漏我们的私钥!
下面举一个多年前sony公司犯过的一个错误。通常,PS3平台只能运行Sony用ECDSA签名过的游戏。这样,如果我想为我的PS3平台创建一个新游戏,在没有Sony的签名的情况下,我无法进行私自发布。但是,问题在于所有Sony产生的签名使用了同一个随机数。
在这种情况下,我们只要购买两个签名过的游戏,抽取出它们的哈希和 以及签名对(),和领域参数一起就可以破解出Sony的私钥。下面是具体步骤:
- 首先,,因为且 对于两个签名是一样的
- 从的定义可以推导出
- 上式两旁都乘以,有:
- 再两边除以得到:
最后的式子可以让我们只用两个哈希和对应的签名来计算,得到以后我们可以使用的定义方程计算私钥:
即便不是静态的,如果是可预测的,同样的技术也可以用于对私钥的破解。
预告
下一篇,也是这个系列的最后一篇,主要关注离散对数问题的求解、若干椭圆曲线密码学的重要问题以及RSA与ECC的比较等等,希望大家不用错过。