内容来自ANDREA CORBELLINI的椭圆曲线密码学的介绍:Elliptic Curve Cryptography: a gentle introduction

本文是椭圆曲线介绍中的第三篇:ECDH和ECDSA。

之前的博客中已经说明了椭圆曲线是什么,并证明了椭圆曲线作为群的性质。然后我们将椭圆曲线限定到有限域中。通过这种限制,椭圆曲线中的点可以生成循环子群。后面又介绍了base pointordercofactor这些术语的概念。

最后提出了椭圆曲线上面的离散对数问题,接下来看一下如何通过离散对数问题来构造密码协议。

全局参数

椭圆曲线算法是在有限域内的椭圆曲线中的循环子群上面运行的。因此,需要定义以下这些参数:

  • 素数 p p p:定义了有限域的大小
  • 参数 a a a b b b:椭圆曲线等式的参数
  • 生成元 G G G:子群的生成元
  • n n n:子群的阶
  • cofactor h h h:子群的cofactor。 n h = N nh=N nh=N,其中 N N N是整个椭圆曲线的大小(阶)。

总结,全局参数为 ( p , a , b , G , n , h ) (p,a,b,G,n,h) (p,a,b,G,n,h)

随机椭圆曲线

之前说椭圆曲线上面的离散对数问题是困难的说法其实并不严谨,因为有些特定的曲线的安全性是较弱的,可以用一些特定的算法来解决离散对数问题。比如某些曲线的 p = h n p=hn p=hn(也就是椭圆曲线的阶和有限域的大小相等),这时候就可以用Smart攻击来在多项式时间内解决离散对数问题。

那么问题就出现了,就是如果别人给我了一组椭圆曲线的参数,我要怎么确定这些参数不是精心选取的某个安全性比较弱的曲线呢

解决方法很简单,让椭圆曲线的参数是随机数生成而非通过选取得到。我们加入一个新的参数,随机种子 S S S,对 S S S进行一个哈希运算,然后根据哈希的结果然得出 a a a b b b的值。因为哈希运算是单向的,所以没办法先选定 a a a b b b,然后根据 a , b a,b a,b的值选出一个种子 S S S。这个过程是不可逆的。

通过随机种子得到a和b是容易的

而通过a和b反向得出S是基本不可能的,因为哈希函数是抗原像攻击的

通过随机种子来生成的曲线被称为可验证随机。这种通过哈希值来生成参数的方法在密码学中很常见,比如Fiat-Shamir转换。这样做可以相对保证曲线不是由某个人故意构造的

随机椭圆曲线的生成和验证算法在ANSI X9.62标准中有描述,哈希算法是基于SHA-1的。如果对具体的算法比较好奇,可以看SECG的椭圆曲线标准当中的"Verifiably Random Curves and Base Point Generators"这一节。

作者这里给了一个python的脚本来进行教学,可以查看一下。

椭圆曲线密码学

基于椭圆曲线的密码学的公私钥基本是如下形式的:

  1. 私钥为一个随机数 d d d,是从 1 , . . . , n − 1 {1,...,n-1} 1,...,n1当中随机选的,这里 n n n是子群的阶。
  2. 公钥为一个点 H = d G H=dG H=dG,这里 G G G是子群的生成元。

可以看到,如果知道私钥 d d d G G G,那么计算公钥 H H H是简单的。但如果公钥 H H H G G G,那么计算 d d d是很难的,因为这需要解决一个离散对数问题。

接下来这里会介绍两个基于椭圆曲线的公钥密码算法:ECDH(Elliptic curve Diffie-Hellman),是一个加密算法,ECDSA(Elliptic Curve Digital Signature Algorithm),是用于数字签名的算法。

ECDH加密

ECDH是Diffle-Hellman算法的一个变体。其实是一个密钥协商协议。代表着ECDH定义了密钥是如何在两方之间生成并交换。

ECDH具体解决的问题如下:两方(比如Alice和Bob)想要安全地交换某些信息,使得某个中间人只能窃听但不能解码得知他们的消息。这也是TLS协议的设计宗旨。

具体的步骤如下:

  1. 首先,Alice和Bob生成他们各自的公私钥。Alice的私钥 d A d_A dA以及公钥为 H A = d A G H_A=d_AG HA=dAG,Bob的密钥为 d B d_B dB H B = d B G H_B=d_BG HB=dBG。注意到Alice和Bob公用一套全局参数,比如同一个生成元 G G G和同一个椭圆曲线。
  2. Alice和Bob通过安全的信道来交换他们的公钥 H A , H B H_A,H_B HA,HB。中间人可能可以窃听到他们传输的 H A , H B H_A,H_B HA,HB的值,但是因为离散对数难题的存在,中间人无法通过 H A , H B H_A,H_B HA,HB计算出 d A d_A dA d B d_B dB的值。
  3. Alice计算 S = d A H B S=d_AH_B S=dAHB,Bob计算 S = d B H A S=d_BH_A S=dBHA,注意到两个人算出来的 S S S其实是一样的,因为:
    S = d A H B = d A ( d B G ) = d B ( d A G ) = d B H A S=d_AH_B=d_A(d_BG)=d_B(d_AG)=d_BH_A S=dAHB=dA(dBG)=dB(dAG)=dBHA

由此,Alice和Bob就都得到了一个协商的值 S S S,而中间人是无法得到的。这里的安全性基于椭圆曲线上的Diffie-Hellman难题,形式化的定义如下:

给定三个点 P , a P , b P P,aP,bP P,aP,bP,如何计算 a b P abP abP

或者在有限域内的原本的Diffie-Hellman难题:

给定三个数 k , k x , k y k,k^x,k^y k,kx,ky,如何计算 k x y k^{xy} kxy

Diffie-Hellman的密钥协商思想:Alice和Bob都能轻易计算出秘密值,对于中间人来说计算出秘密值是一个困难问题。

Diffie-Hellman问题的设计宗旨有一个YouTube视频讲的比较清楚,但他是有限域内的Diffie-Hellman。

椭圆曲线内的Diffie-Hellman难题被公认为是一个“困难问题”,被认为是与离散对数难题一样难解决的,虽然学界还没有形式化的证明说这两个问题一样难。我们现在可以保证的事情就是Diffle-Hellman问题不会比离散对数难题更加难,因为如果解决了离散对数难题,那么就可以解决Diffle-Hellman问题。

注意到现在Alice和Bob有了一个共享的秘密值,所以他们就可以使用对称加密来进行数据传输了。

比如,他们可以用点 S S S x x x坐标作为AES算法的私钥来加密消息。这就是TLS背后的思想,区别在于在TLS是将 x x x坐标和其他一些信息窜连起来,然后计算了一次哈希,将哈希值作为了密钥。

ECDH写个实际例子

这里有另外一个python脚本模拟了ECDH的密钥交换过程,非常的简单易懂,推荐看一下。

不像是目前为止其他的例子,这里的python脚本用了SECG(“Standard for Efficient Cryptography Group”)提供标准的椭圆曲线:secp256k1曲线。这个曲线也被用在比特币中。下面是一些全局参数:

  • p = p= p= 0xffffffff ffffffff ffffffff ffffffff ffffffff ffffffff fffffffe fffffc2f
  • a = a= a= 0
  • b = b= b= 7
  • x G = x_G= xG= 0x79be667e f9dcbbac 55a06295 ce870b07 029bfcdb 2dce28d9 59f2815b 16f81798
  • y G = y_G= yG= 0x483ada77 26a3c465 5da4fbfc 0e1108a8 fd17b448 a6855419 9c47d08f fb10d4b8
  • n = n= n= 0xffffffff ffffffff ffffffff fffffffe baaedce6 af48a03b bfd25e8c d0364141
  • h = h= h= 1

这里的参数可以被替换,但要注意替换掉的参数要符合Weierstrass normal form

临时的ECDH

有些人可能听说过ECDHE而不是ECDH。其中的"E"表示"Ephemeral"(短暂的)的意思,代表着密钥交换的结果是临时的,而不是静态的。

ECDHE被用于TLS层中,比如客户端和服务器临时生成一对公私钥,当链接建立之后。双方会对公钥进行TLS认证以及签名,然后使用ECDHE进行密钥交换。

ECDSA签名

场景如下:Alice想要用自己的私钥 d A d_A dA来对一个消息进行签名,而Bob想要用Alice的公钥 H A H_A HA来验证这个签名是否有效。除了Alice之外任何人都没有能力给出有效的签名。任何人都可以验证这个签名是否有效。

同样的,Alice和Bob要使用一样的全局参数。那么ECDSA就是达到上述目标的一种算法。

ECDSA是作用于消息的哈希值,而不是消息本身。如何选择哈希函数是使用者自己决定的,但这个哈希函数应该是一个密码学上安全的哈希函数。哈希得到的哈希值要被截断到与 n n n(子群的阶)的比特大小相同的程度,将截断后的哈希值记为 z z z

签名算法如下:

  1. 1 , . . . , n − 1 {1,...,n-1} 1,...,n1中选取一个随机数。
  2. 计算 P = k G P=kG P=kG
  3. 计算一个数 r = x P   m o d   n r=x_P \bmod n r=xPmodn(其中 x P x_P xP P P P x x x坐标)。
  4. 如果 r = 0 r=0 r=0,那么就从第一步重新开始。
  5. 计算 s = k − 1 ( z + r d A )   m o d   n s=k^{-1}(z+rd_A) \bmod n s=k1(z+rdA)modn
  6. 如果 s = 0 s=0 s=0,那么就从第一步重新开始。

签名为 ( r , s ) (r,s) (r,s)

Alice使用自己的私钥和随机数生成对消息z的签名,Bob收到之后可以用Alice的公钥进行验证

简而言之,算法首先产生秘密 k k k。然后用 r r r k k k隐藏起来(基于离散对数问题)。 r r r是和消息 z z z绑定起来的,通过算式 s = k − 1 ( z + r d A )   m o d   n s=k^{-1}(z+rd_A) \bmod n s=k1(z+rdA)modn

注意到为了计算 s s s,需要计算 k − 1   m o d   n k^{-1} \bmod n k1modn,这就要求 n n n是个素数。所以当子群不是素数阶的时候,ECDSA算法是不能用的。

验证算法

验证需要的输入为:Alice的公钥 H A H_A HA,哈希值 z z z,签名 ( r , s ) (r,s) (r,s)

  1. 计算 u 1 = s − 1 z   m o d   n u_1 = s^{-1}z \bmod n u1=s1zmodn
  2. 计算 u 2 = s − 1 r   m o d   n u_2 = s^{-1}r \bmod n u2=s1rmodn
  3. 计算 P = u 1 G + u 2 H A P=u_1G+u_2H_A P=u1G+u2HA

当且仅当 r = x P   m o d   n r=x_P \bmod n r=xPmodn成立时,验证通过。

算法的正确性

算法背后的逻辑可能并不是一眼能看出来的,但我们可以将目前所有的等式放在一起,那么就会清晰一点。

首先看 P = u 1 G + u 2 H A P=u_1G+u_2H_A P=u1G+u2HA。因为 H A = d A G H_A=d_AG HA=dAG(公钥的定义)。那么可以得知:
P = u 1 G + u 2 H A = u 1 G + u 2 d A G = ( u 1 + u 2 d A ) G \begin{aligned} P&=u_1G+u_2H_A\\ &=u_1G+u_2d_AG\\ &=(u_1+u_2d_A)G \end{aligned} P=u1G+u2HA=u1G+u2dAG=(u1+u2dA)G
u 1 , u 2 u_1,u_2 u1,u2的值带入可得:
P = ( u 1 + u 2 d A ) G = ( s − 1 z + s − 1 r d A ) G = s − 1 ( z + r d A ) G \begin{aligned} P&=(u_1+u_2d_A)G\\ &=(s^{-1}z+s^{-1}rd_A)G\\ &=s^{-1}(z+rd_A)G \end{aligned} P=(u1+u2dA)G=(s1z+s1rdA)G=s1(z+rdA)G

由于 s = k − 1 ( z + r d A ) s=k^{-1}(z+rd_A) s=k1(z+rdA),可以很容易推出 k = s − 1 ( z + r d A ) k=s^{-1}(z+rd_A) k=s1(z+rdA),将这个带入上式,就得到了:
P = s − 1 ( z + r d A ) G = k G \begin{aligned} P&=s^{-1}(z+rd_A)G\\ &=kG \end{aligned} P=s1(z+rdA)G=kG

所以说验证算法中得到的 P P P点是和签名算法中一致的。

ECDSA例子

同样的,作者这里也写了一个python脚本来跑ECDSA算法,总的来说还是比较简单的,因为没有涉及双线性配对,感兴趣的同学可以点进去看看。

k k k的重要性

当生成ECDSA签名的时候,保证 k k k是保密的很关键。如果我们在很多签名中使用同一个 k k k,或者说我们的随机数生成器被攻击者预测到了,那么攻击者就可以得到我们的私钥了。

索尼的PS3在之前就因为随机值 k k k翻车了。PS3游戏控制台运行游戏的话需要验证某个游戏是否由索尼给出了ECDSA签名。因此,如果我没有索尼给出的签名的话,是没办法在PS3上面发布游戏的。但问题在于,索尼的所有签名都是用同一个静态的 k k k来进行签名的。

(索尼用的随机数生成器可能是下面两个之一)
在这里插入图片描述在这里插入图片描述
在这种情况下,我们只需要买两个索尼签名的游戏,就可以恢复出索尼的密钥了。下面是步骤,首先我们要知道两个游戏的哈希值 z 1 , z 2 z_1,z_2 z1,z2,以及他们的签名 ( r 1 , s 1 ) , ( r 2 , s 2 ) (r_1,s_1),(r_2,s_2) (r1,s1),(r2,s2)

  • 首先,注意到 r 1 = r 2 r_1=r_2 r1=r2,因为 P = k G P=kG P=kG,而 r 1 r_1 r1 r 2 r_2 r2都是 P P P x x x坐标。
  • 计算 ( s 1 − s 2 ) = k − 1 ( z 1 − z 2 ) (s_1-s_2)=k^{-1}(z_1-z_2) (s1s2)=k1(z1z2)
  • 两边乘 k k k k ( s 1 − s 2 ) = ( z 1 − z 2 ) k(s_1-s_2)=(z_1-z_2) k(s1s2)=(z1z2)
  • 除以 ( s 1 − s 2 ) (s_1-s_2) (s1s2) k = ( z 1 − z 2 ) ( s 1 − s 2 ) − 1   m o d   n k=(z_1-z_2)(s_1-s_2)^{-1}\bmod n k=(z1z2)(s1s2)1modn

那么我们就知道了 k k k,通过 k k k很容易就能计算出私钥:
s = k − 1 ( z + r d S )   m o d   n ⇒ d S = r − 1 ( s k − z )   m o d   n s=k^{-1}(z+rd_S) \bmod n \quad \Rightarrow \quad d_S=r^{-1}(sk-z) \bmod n s=k1(z+rdS)modndS=r1(skz)modn

如果 k k k不是静态的,而是一个可预测的随机数的话,那也可以实现类似的攻击。

Logo

开放原子开发者工作坊旨在鼓励更多人参与开源活动,与志同道合的开发者们相互交流开发经验、分享开发心得、获取前沿技术趋势。工作坊有多种形式的开发者活动,如meetup、训练营等,主打技术交流,干货满满,真诚地邀请各位开发者共同参与!

更多推荐