原文链接:Elliptic Curve Cryptography: ECDH and ECDSA
这篇文章是ECC系列的第三篇。
在之前的文章中,我们已经知道了椭圆曲线是什么,并且为了对椭圆曲线上的点做一些数学运算我们定义了群公理。然后我们将椭圆曲线限制在整数取模的有限域上。在该条件下,我们发现椭圆曲线上的点能生成循环子群,接着引入了基点、阶数和辅助因子。
最后,我们已经知道了,有限域中的标量乘法是一个“简单”问题,而离散对数问题似乎是一个“困难”问题。现在我们将看看如何将这些知识应用于密码学。
主要参数(Domain parameters)
我们的椭圆曲线算法将运用在有限域上的椭圆曲线所生成的循环子群上。因此,我们的算法将需要以下参数:
- 描述有限域范围大小的素数p
- 椭圆曲线方程的参数a和b
- 生成循环子群的基点G
- 子群的阶数n
- 子群的辅助因子h
总之,我们算法的主要参数可以定义为一个六元组
随机曲线(Random curves)
当我说离散对数问题是一个“困难”问题时,这个说法并不完全正确。有一些椭圆曲线是特别弱的以至于一些不怀好意的算法可以有效率地求解离散对数问题。例如,所有具有p = hn(即有限域的阶数等于椭圆曲线的阶数)的曲线都容易受到Smart攻击,这种攻击可以用经典计算机在多项式时间内解决离散对数问题。
现在,假设我给你们一条曲线的主要参数。有可能我发现了一类新的没人知道的弱曲线,也有可能我建立了一个“快速”算法来计算我给你们的曲线上的离散对数。我怎样才能让你相信恰恰相反,也就是说,我不知道自己(所给的曲线)有任何弱点?我如何向你保证这条曲线是“安全的”(也就是说它不会被我用于特殊目的的攻击)?
为了解决这类问题,有时我们需要一个额外的主要参数:种子S。这是一个随机数,用于生成参数a和b,或基点G,或两者。这些参数是通过计算种子S的哈希生成的。哈希,正如我们所知道的那样,“容易”计算,但“难以”逆推(正向计算容易,反向计算困难)。
一个关于如何从种子生成随机曲线的简单示意图:一个随机数的哈希用于计算曲线的不同参数
如果我们想作弊并试图从主要参数中构造一个种子,我们将不得不解决一个“难解的”问题:逆向哈希
通过种子生成的曲线称为可验证的随机曲线。使用哈希生成参数的原理被称为“nothing up my sleeve”,这个原理在密码学中经常用到。
这个技巧应该能在某种程度上保证曲线不是专门为暴露作者已知的漏洞而设计的。事实上,如果我给你一个带种子的曲线,这意味着我不能自由选择参数a和b,你应该相对肯定的是,曲线不能被我用于特殊目的攻击。我说“相对”的原因将在下一篇文章中解释。
在ANSI X9.62中描述了一种基于 SHA-1的生成和检查随机曲线的标准化算法。如果您感兴趣,可以阅读specification by SECG 规范中生成可验证随机曲线的算法(寻找“可验证随机曲线和基点生成器”)。
我创建了一个tiny Python script ,用于验证shipped with OpenSSL当前提供的所有随机曲线。我强烈推荐你去看看!
椭圆曲线密码学(Elliptic Curve Cryptography)
前面的学习使我们花了很长时间,但终于到了椭圆曲线密码学这部分内容了!因此,简单地说:
- 私钥是一个从中选择的一个整数d(其中n为子群的阶)
- 公钥为点(其中G为子群的基点)
你看到了什么?如果我们知道d和G(以及其他的主要参数),那么找到H便很“容易”。但如果我们知道H和G,想找到私钥d就很困难了,因为这需要我们解决离散对数问题。
现在我们将基于此来阐述两个公钥算法:用于加密的ECDH (Elliptic curve Diffie-Hellman)和用于数字签名的ECDSA (Elliptic curve Digital Signature Algorithm)。
用ECDH加密(Encryption with ECDH)
ECDH是椭圆曲线的 Diffie-Hellman algorithm 的变体。它实际上是一种密钥协商协议,而不仅仅是一种加密算法。这基本上意味着ECDH(在某种程度上)定义了应该如何在各方之间生成和交换密钥。如何使用这些密钥加密数据取决于我们自己。
它解决的问题如下:双方(通常是Alice和Bob)希望安全地交换信息,这样第三方(中间人)可能会拦截他们,但可能不会解码信息。这是TLS背后的原则之一,举个例子。
工作流程如下:
- 首先,Alice和Bob生成自己的私钥和公钥。我们有Alice的私钥和公钥, Bob的私钥和公钥。请注意,Alice和Bob都使用相同的主要参数:同一有限域上同一椭圆曲线上的同一基点G。
- Alice和Bob通过一个非安全信道交换他们的公钥和。中间人会截获和,但如果不解决离散对数问题就不能求出或。
- Alice计算(使用自己的私钥和Bob的公钥),Bob计算(使用自己的私钥和Alice的公钥)。注意,S对于Alice和Bob来说都是一样的:
而中间人只知道和(以及其他的主要参数),无法找到共享秘密S。这就是所谓的Diffie-Hellman问题,可以表述如下:
给定三个点P, aP和bP,那么abP的结果是什么?
换言之:
给定三个整数k,kx和ky,kxy的结果是什么?
(后一种形式在最原始的基于模算法的Diffie-Hellman算法中使用。)
Diffie-Hellman密钥交换:Alice和Bob可以“轻松地”计算共享密钥,中间人必须解决一个“难解”问题。
Khan Academy在YouTube上的一个视频中也解释了Diffie-Hellman问题背后的原理,该视频后来解释了基于模运算的Diffie-Hellman算法(而不是椭圆曲线)。 YouTube video by Khan Academy
假设椭圆曲线的Diffie-Hellman问题是一个“难解”问题。它被认为和离散对数问题一样“难解”,尽管没有数学证明。我们可以确定的是,它不会“更困难”,因为解决对数问题是解决Diffie-Hellman问题的一种方法。
现在Alice和Bob已经获得了共享的秘钥,他们可以用对称加密算法交换数据了。
例如,它们可以使用S的x坐标作为密钥,使用AES或3DES等安全的加密算法对消息进行加密。这与TLS所做的或多或少相似,不同之处在于TLS将x坐标与其他与通信连接相关的信息结合起来,然后计算得到的字节串的哈希值。
ECDH实践(Playing with ECDH)
我创建了another Python script ,用于计算椭圆曲线上的公私钥和共享秘钥。
与我们目前看到的所有示例不同,该脚本使用标准化的曲线,而不是在一个小的域内的简单曲线。我选择的曲线是secp256k1,来自SECG(“高效加密标准组”,由Certicom创建)。这条曲线也被比特币用于数字签名。以下是主要参数:
- p=0xffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f
- a=0
- b=7
- xG=0x79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 59f2815b 16f81798
- yG=0x483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 9c47d08f fb10d4b8
- n=0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141
- h=1
(这些数字取自OpenSSL source code。)
当然,也可以随意修改脚本来使用其他曲线和主要参数,只是要确保使用原始字段和Weierstrass标准形式的曲线,否则脚本将无法运行。
该脚本非常简单,包含了我们迄今为止描述的一些算法:点加、点乘、ECDH。我建议你阅读并运行它。它将产生如下输出:
Curve: secp256k1 Alice's private key: 0xe32868331fa8ef0138de0de85478346aec5e3912b6029ae71691c384237a3eeb Alice's public key: (0x86b1aa5120f079594348c67647679e7ac4c365b2c01330db782b0ba611c1d677, 0x5f4376a23eed633657a90f385ba21068ed7e29859a7fab09e953cc5b3e89beba) Bob's private key: 0xcef147652aa90162e1fff9cf07f2605ea05529ca215a04350a98ecc24aa34342 Bob's public key: (0x4034127647bb7fdab7f1526c7d10be8b28174e2bba35b06ffd8a26fc2c20134a, 0x9e773199edc1ea792b150270ea3317689286c9fe239dd5b9c5cfd9e81b4b632) Shared secret: (0x3e2ffbc3aa8a2836c1689e55cd169ba638b58a3a18803fcf7de153525b28c3cd, 0x43ca148c92af58ebdb525542488a4fe6397809200fe8c61b41a105449507083)
Ephemeral ECDH
你们可能听说过ECDHE而不是ECDH。ECDHE中的“E”代表“短暂的”,表示交换的密钥是临时的动态的,而不是固定的静态的。
例如,在TLS中使用ECDHE,其中客户端和服务器在建立连接时动态地生成它们的公私钥。然后使用TLS证书对密钥进行签名(用于身份验证),并在双方之间进行交换。
用ECDSA签名(Signing with ECDSA)
假设场景如下:Alice想用她的私钥(dA)签名消息,Bob想用Alice的公钥(HA)验证签名。除了Alice,没人能出示有效签名。每个人都能够验证签名。
同样,Alice和Bob使用相同的主要参数。我们将要看到的算法是ECDSA,一种DSA(Digital Signature Algorithm)应用了椭圆曲线的变体。
ECDSA 需要使用明文的哈希结果,而不是明文本身。哈希函数的选择取决于我们,但显然应该选择cryptographically-secure hash function。为了使哈希结果的比特长度和 n (子群的阶)的比特长度一致,消息的哈希结果需要被截断,被截断后的哈希值是一个整数,将表示为z。
Alice对消息进行签名的算法工作原理如下:
1. 从中选择一个随机数(其中n仍然是子群的阶)
2. 计算点(其中G为子群的基点)
3.计算数字(其中是的P的横坐标)
4. 如果,然后选择另一个k,再试一次
5. 计算(其中是Alice的私钥,是k对n的乘法逆元)
6. 如果,再选一个k,再试一次
便是签名。
Alice使用她的私钥和一个随机数k对哈希z进行签名。Bob使用Alice的公钥验证签名的正确性。
简单地说,这个算法首先生成一个secret(k)。由于点的乘法,这个秘密隐藏在r中(正如我们所知,一种方法“易解”,另一种方法“难解”)。然后通过等式将r绑定到消息哈希。
注意,为了计算s,我们必须计算k对n的乘法逆元,我们在之前的文章中已经说过,只有当n是素数时,这个才有效。如果子群具有非素数顺序,则不能使用ECDSA。不是偶然的,几乎所有标准化曲线都有一个素数阶,而那些有非素数阶的曲线不适用ECDSA。
验证签名(Verifying signatures)
为了验证签名,我们需要Alice的公钥、被截断的哈希值z,显然还需要签名(r, s)。
1. 计算整数
2. 计算整数
3.计算点
只有当时签名才有效。
算法的正确性(Correctness of the algorithm)
乍一看,这个算法背后的逻辑可能不太明显,但是如果我们把到目前为止所写的所有方程放在一起,事情就会更清楚。
让我们从开始。根据公钥的定义,我们知道(其中是私钥)。我们可以写:
使用u1和u2的定义,我们可以这样写:
为了简洁,我们在这里省略了"mod n",因为G生成的循环子群有O(n),所以"mod n"是多余的。
之前,我们定义了。等式两边乘以k,然后除以s,我们得到:。将这个结果代入方程,得到:
这与签名生成算法第二步中P的方程相同!在生成签名和验证签名时,我们是在计算同一个点P,只是用一组不同的方程。这就是这个算法有效的原因。
ECDSA实践(Playing with ECDSA)
当然,我已经创建了一个用于签名生成和验证的 a Python script 。该代码与ECDH脚本共享一些部分,特别是主要的参数和公私密钥对生成算法。
下面是脚本产生的输出类型:
Curve: secp256k1 Private key: 0x9f4c9eb899bd86e0e83ecca659602a15b2edb648e2ae4ee4a256b17bb29a1a1e Public key: (0xabd9791437093d377ca25ea974ddc099eafa3d97c7250d2ea32af6a1556f92a, 0x3fe60f6150b6d87ae8d64b78199b13f26977407c801f233288c97ddc4acca326) Message: b'Hello!' Signature: (0xddcb8b5abfe46902f2ac54ab9cd5cf205e359c03fdf66ead1130826f79d45478, 0x551a5b2cd8465db43254df998ba577cb28e1ee73c5530430395e4fba96610151) Verification: signature matches Message: b'Hi there!' Verification: invalid signature Message: b'Hello!' Public key: (0xc40572bb38dec72b82b3efb1efc8552588b8774149a32e546fb703021cf3b78a, 0x8c6e5c5a9c1ea4cad778072fe955ed1c6a2a92f516f02cab57e0ba7d0765f8bb) Verification: invalid signature
如您所见,该脚本首先对消息(字节字符串“Hello!”)进行签名,然后验证签名。然后,它尝试对另一条消息(“Hi there!”)验证相同的签名,验证失败。最后,它尝试根据正确的消息验证签名,但是使用另一个随机公钥验证再次失败。
k的重要性(The importance of k)
在生成ECDSA签名时,保持秘密k的真正秘密是非常重要的。如果我们对所有签名使用相同的k,或者如果我们的随机数生成器在某种程度上是可预测的,攻击者将能够找到私钥!
这是索尼在几年前所犯的错误。基本上,PlayStation 3游戏机只能运行索尼与ECDSA签署的游戏。通过这种方式,如果我想为PlayStation 3创造一款新游戏,我便不能在没有索尼签名的情况下将其发行给公众。问题是:索尼制作的所有签名都是通过电台生成的k。
(显然,索尼的随机数生成器是受到XKCD或呆伯特的启发。)
在这种情况下,我们可以很容易地恢复索尼的私钥ds,只需购买两款已签名的游戏,提取它们的哈希(和)和签名((, )和(, ),以及域参数。
方法如下:
- 首先,请注意(因为和对于两个签名都是一样的)
- 假设(这个结果直接来自于s的方程)
- 等式两边各乘以k:
- 除以得到
最后一个方程让我们只用两个哈希及其对应的签名来计算k。现在我们可以使用如下等式来提取私钥s:
如果k不是静态的,但在某种程度上是可预测的,则可以使用类似的技术。
周末愉快
希望你喜欢我在这里写的东西。像往常一样,如果你需要帮助,不要犹豫留下评论或send me a poke。
下周,我将发表本系列的第四篇也是最后一篇文章。它将是关于解决离散对数的技术,椭圆曲线密码学的一些重要问题,以及ECC和RSA的比较。不要错过它!