背景 : ECC 是一种极其巧妙的非对称加密算法, 其完美利用了 椭圆曲线几何累加 不可逆的性质,拥有 密钥体积小,计算速度快的优势,被广泛用于各种区块链,移动端APP的认证过程。 此文章致力于用最少的数学公式解释 ECC 椭圆曲线加密算法 ,包括椭圆曲线加法性质,进而推导到非对称加密,安全性分析, 最后再推导到 有限域上的椭圆曲线加密算法.
什么是 椭圆曲线
x, y 符合以下定义的曲线就是 椭圆曲线:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> y 2 = x 3 + a x + b , 且 4 a 3 + 27 b 2 ≠ 0 y^2 = x^3 + a x +b , 且 4 a^3 +27 b^2 \neq 0 </math>y2=x3+ax+b,且4a3+27b2=0
它可以是下面的各种形状:
椭圆曲线 上的加法
接上面, 椭圆曲线长上面那种样子都无所谓,我们这里不care它的形状的, 我们来讨论在椭圆曲线上面做加法。
假设有一个点P 和 点Q, 那么我们定义 P + Q 为 : 经过 P,Q 两点的直线与 椭圆曲线的交点, 再将这个交点做 关于 X轴的对称, 关于 x 轴的对称点 就是 P+Q 。 比如下面这幅图:
聪明的你可能会问如果P,Q两点重合怎么办, 重合的话 就是 P+P =2P, 也就是 P点的切线 与椭圆曲线的交点, 再将这个交点做 关于 X轴的对称, 关于 x 轴的对称点 就是 2P.
椭圆曲线上 两点相加计算公式
如果有两个点 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 1 , y 1 ) (x_1, y_1) </math>(x1,y1) , <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 2 , y 2 ) (x_2, y_2) </math>(x2,y2), 那么 两点相加得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> ( x 3 , y 3 ) (x_3, y_3) </math>(x3,y3) 公式如下:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> x 3 = k 2 − x 1 − x 2 y 3 = k ∗ ( x 1 − x 3 ) − y 1 k = ( y 2 − y 1 ) / ( x 2 − x 1 ) , 当 x 1 ≠ x 2 时 k = ( 3 x 1 2 + a ) / 2 y 1 , 当 x 1 = x 2 时 x_3 = k^2 - x_1 -x_2 \newline y3 = k*(x_1 - x_3) - y_1 \newline k = (y_2-y_1)/ (x_2-x1), 当 x_1 \neq x_2 时 \newline k = (3 x_1^2 +a) / 2 y_1, 当 x_1 = x_2 时 </math>x3=k2−x1−x2y3=k∗(x1−x3)−y1k=(y2−y1)/(x2−x1),当x1=x2时k=(3x12+a)/2y1,当x1=x2时
上面这个公式其实就是 给定两点 求直线, 再求 直线与椭圆曲线的交点,然后关于 x 轴对称的结果, 计算过程非常复杂,需要解三次方程, 你只需要知道有这个公式即可
椭圆曲线 上的加法 推广到 乘法
有了 2P, 那么同样的方法 求出3P。 我们将 过 P点和2P点做一条直线, 这条直线与 椭圆曲线的交点的 关于 X轴 对称就是 3P, 比如下面这样:
依次类推, 我们可以画出 4P, 5P... NP, 只要不停地连接两点画直线 找出与 椭圆曲线的交点, 再关于 X轴对称即可。我们把这叫做 椭圆曲线上的乘法。
事实上计算NP并不是需要一步步进行计算, 比如计算 100P, 并不用计算 1P, 2P, 3P, ..., 99P. 我可以直接告诉你一个结论 那就是 椭圆曲线上的加法符合结合律。 符合结合律有什么好处呢?好处相当地大, 我把 计算100P展开来说:
100P = 50P+50P, 而 50P =25P+ 25P , 25P =1P +24P, 24P = 12P +12P, 12P=6P+6P, 6P=3P+3P, 3P=1P+2P, 2P=1P+1P, 也就是计算 100P ,我实际上并不需要做100次加法, 而只需要做 8次加法即可,每当 NP(N为偶数时), 都可以将 NP 拆成 两个 N/2 * P 之和, 所以椭圆曲线上的乘法 Q=k*P 的时间复杂度为 O(logK)。 记住这个结论, 非常重要 !!!
从 椭圆曲线上的乘法 推广到 非对称加密
你可能会问求 椭圆曲线上的乘法这有什么用呢?
这个问题就是 椭圆曲线的精妙之处了:
给定一条椭圆曲线一个起点P, 和大的质数数k, 我们可以很容易地找到 Q=kP点(时间复杂度为 O(logK))。但是反过来, 给出终点Q 和起点P ,让你找出 k, 你只有一次次地 从 k =1, k=2, ... k=N 去暴力尝试, 这个时间复杂度是 为 O(k) 级别的。
这个性质是不是很熟悉?正向容易计算, 逆向极其困难, 是不是和RSA有点像了? RSA 根据 私钥计算公钥很容易, 公钥反推私钥几乎不可能。
如果我们将 k 看作私钥, 那么 Q 就可以被看作公钥, 这就是 椭圆曲线非对称加密算法的原理, 椭圆曲线加密算法的强度保障就在于 给定P 和 k 计算 Q是否容易, 但反之 给定Q和P 想要计算 k 则是否困难。
具体如何加解密:
- 选定一条椭圆曲线, 选定起点(也叫基点)P(通常选得非常大), 选取一个整数 k( k<n , k通常很大, 几百bit级别, 但同样的强度相对于 RSA 又明显小一个数量级) 作为私钥, 计算公钥 Q=kP
- 加密过程如下: 选取一个随机整数r ( r<n, r 通常也很大), 使用公钥Q 和 r 对 明文 <math xmlns="http://www.w3.org/1998/Math/MathML"> M M </math>M 进行加密 得到 <math xmlns="http://www.w3.org/1998/Math/MathML"> C i p h t e r T e x t CiphterText </math>CiphterText。 <math xmlns="http://www.w3.org/1998/Math/MathML"> C i p h e r T e x t Cipher_Text </math>CipherText 由两部分组成:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> C i p h e r T e x t = [ C i p h e r T e x t 1 , C i p h e r T e x t 2 ] = [ r P , M + r Q ] 其中 M 为原始数据在椭圆曲线上的编码 , 也是一个坐标点 CipherText= [CipherText1, CipherText2] =[r P, M+r Q] \newline 其中 M 为 原始数据在 椭圆曲线上的 编码, 也是一个坐标点 </math>CipherText=[CipherText1,CipherText2]=[rP,M+rQ]其中M为原始数据在椭圆曲线上的编码,也是一个坐标点
- 解密过程如下:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> M + r Q − K ( r P ) = M + k r P − k r P = M M+r Q -K (r P)= M+k r P-k r P= M </math>M+rQ−K(rP)=M+krP−krP=M
椭圆曲线非对称加密 安全性分析
- 椭圆曲线参数公开, 基点P公开, Q 公开, 私钥 k 解密方保密, r 为加密方保密(一次性随机数,不存储, 不具备重放性)
- <math xmlns="http://www.w3.org/1998/Math/MathML"> C i p h e r T e x t CipherText </math>CipherText 加密需要 公开的 Q, M ,以及 一次性随机数 r
- <math xmlns="http://www.w3.org/1998/Math/MathML"> C i p h e r T e x t CipherText </math>CipherText 解密需要 公开的 <math xmlns="http://www.w3.org/1998/Math/MathML"> C i p h e r T e x t CipherText </math>CipherText 以及 私钥 k
- 欲截获 <math xmlns="http://www.w3.org/1998/Math/MathML"> C i p h e r T e x t CipherText </math>CipherText 并破解 只有两种可能:
-
想办法获取到私钥 k, 若私钥不泄露则无此风险
-
直接通过 <math xmlns="http://www.w3.org/1998/Math/MathML"> M + r Q − r k P = M M+rQ-rkP= M </math>M+rQ−rkP=M, 此处由两重困难:
-
k只能通过 终点Q 和 基点P 反推, 上面分析过极其困难
-
r只能通过 终点 rP 和 基点P 反推, 也是极其困难的, 并且r 是一次性随机数, 不能被重放
-
-
从连续的椭圆曲线 推广到 有限域
根据上面的分析, 你已经知道了椭圆曲线从几何意义上是如果确保 密钥的强度了。复述一遍 就是 给定基点P 和 私钥 k, 计算终点 也就是 公钥 Q 比较容易;反之知道 终点Q和基点P ,想要反推密钥 k 将极其困难。
那么如果要在计算机上面使用椭圆曲线上的 密钥体系, 则还需要解决两个问题
- 将连续的曲线进行离散化处理,计算机是不能处理 1.111111111111111... 这种精度无穷大的小数的
- 将离散化的曲线个数进行有限化,计算机无法处理一个无穷大的数字的
解决方法也很简单,对症下药既可:
- 对连续的曲线进行取整, 对x只考虑整数
- 对曲线表达式两边同时进行 取模 操作, 取模操作约束了 等式左右两边的大小
如此一来, 离散且有限的"曲线" 表达式就变成下面这样:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> y 2 ≡ x 3 + a ∗ x + b ( m o d p ) , x ∈ Z 且 4 ∗ a 3 + 27 ∗ b 2 ≠ 0 y^2 \equiv x^3 + a*x + b (mod p) , x \in Z 且 4*a^3 +27*b^2 \neq 0 </math>y2≡x3+a∗x+b(modp),x∈Z且4∗a3+27∗b2=0
这条曲线记作 <math xmlns="http://www.w3.org/1998/Math/MathML"> G F ( p ) GF(p) </math>GF(p), 特别的是 p 一般取质数(实际应用中会取大质数)。 之所以取质数,是为了取值的多样性,为了取值的方碰撞性。
在 <math xmlns="http://www.w3.org/1998/Math/MathML"> G F ( p ) GF(p) </math>GF(p) 上的加法变成了这样:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> x 3 ≡ k 2 − x 1 − x 2 ( m o d p ) y 3 ≡ k ∗ ( x 1 − x 3 ) − y 1 ( m o d p ) k ≡ ( y 2 − y 1 ) / ( x 2 − x 1 ) ( m o d p ) , 当 x 1 ≠ x 2 时 k ≡ ( 3 ∗ x 1 2 + a ) / 2 ∗ y 1 ( m o d p ) , 当 x 1 = x 2 时 x_3 \equiv k^2 - x_1 -x_2 (mod p) \newline y3 \equiv k*(x_1 - x_3) - y_1 (modp)\newline k \equiv (y_2-y_1)/ (x_2-x1) (mod p), 当 x_1 \neq x_2 时 \newline k \equiv (3*x_1^2 +a) / 2*y_1 (mod p), 当 x_1 = x_2 时 </math>x3≡k2−x1−x2(modp)y3≡k∗(x1−x3)−y1(modp)k≡(y2−y1)/(x2−x1)(modp),当x1=x2时k≡(3∗x12+a)/2∗y1(modp),当x1=x2时
我们用 python 画一下 <math xmlns="http://www.w3.org/1998/Math/MathML"> y 2 ≡ x 3 + x + 1 ( m o d 23 ) y^2 \equiv x^3 + x +1 (mod 23) </math>y2≡x3+x+1(mod23)的图像
python
import matplotlib.pyplot as plt
# 定义有限域上的曲线方程
def curve(x):
return (x**3 + x + 1) % 23
# 生成离散的数据点
x = []
y = []
txt = []
for i in range(23):
for j in range(23):
if curve(i) == j**2 % 23:
x.append(i)
y.append(j)
txt.append('(' +str(i)+',' + str(j)+')')
# 绘制曲线图像
plt.figure(figsize=(13, 10))
plt.scatter(x, y, marker='o' , s=50)
for i in range(len(x)):
plt.annotate(txt[i], xy = (x[i], y[i]), xytext = (x[i]+0.05, y[i]+0.1),fontsize=15)
plt.xlabel('x', fontsize=20)
plt.ylabel('y', fontsize=20)
plt.title('y^2 ≡ x^3 + x + 1 (mod 23)',fontsize=25)
plt.xlim(-1,15)
plt.ylim(-2,24)
plt.grid(True)
plt.show()
图上的每一点都满足 <math xmlns="http://www.w3.org/1998/Math/MathML"> y 2 ≡ x 3 + x + 1 ( m o d 23 ) y^2 \equiv x^3 + x +1 (mod 23) </math>y2≡x3+x+1(mod23)。 比如(3,10)这个点,恒等式左边 <math xmlns="http://www.w3.org/1998/Math/MathML"> 1 0 2 m o d ( 23 ) = 100 ( m o d 23 ) = 8 10^2 mod(23) = 100 (mod23) =8 </math>102mod(23)=100(mod23)=8, 恒等式右边 <math xmlns="http://www.w3.org/1998/Math/MathML"> 3 3 + 3 + 1 m o d ( 23 ) = 8 3^3 + 3 +1 mod(23) = 8 </math>33+3+1mod(23)=8.
接下来试试有限域上面的加法, 给定一点 <math xmlns="http://www.w3.org/1998/Math/MathML"> P = ( 3 , 10 ) P=(3,10) </math>P=(3,10), 求 <math xmlns="http://www.w3.org/1998/Math/MathML"> 2 P 2P </math>2P
解:
<math xmlns="http://www.w3.org/1998/Math/MathML" display="block"> 2 P = P + P k ≡ ( 3 ∗ x 1 2 + a ) / 2 ∗ y 1 ( m o d 23 ) = ( 3 ∗ 3 2 + 1 ) / ( 2 ∗ 10 ) = 7 / 5 ( m o d 23 ) 令 n ≡ 7 / 5 ( m o d 23 ) → 5 ∗ n ≡ 7 m o d ( 23 ) = 7 → n = 6 故 k = 6 x 3 = 6 2 − 3 − 3 ( m o d 23 ) = 7 ( m o d 23 ) = 7 y 3 = 6 ∗ ( 3 − 7 ) − 10 = − 34 ( m o d 23 ) = 12 即 : 2 P = ( 7 , 12 ) 2P= P+P \newline k \equiv (3*x_1^2 +a) / 2*y_1 (mod 23) = (3*3^2 +1) / (2* 10) = 7/5 (mod23) \newline 令 n \equiv 7/5 (mod23) \rightarrow 5*n \equiv 7 mod(23)=7 \rightarrow n=6 \newline 故 k=6 \newline x_3 = 6^2-3-3 (mod 23) = 7 (mod23)= 7 \newline y_3 = 6*(3-7)-10 = -34 (mod 23) =12 \newline 即: 2P= (7,12) </math>2P=P+Pk≡(3∗x12+a)/2∗y1(mod23)=(3∗32+1)/(2∗10)=7/5(mod23)令n≡7/5(mod23)→5∗n≡7mod(23)=7→n=6故k=6x3=62−3−3(mod23)=7(mod23)=7y3=6∗(3−7)−10=−34(mod23)=12即:2P=(7,12)
依此类推, 可以计算 3P, 4P ..., kP
我们用python计算 P, 2P, ..., 22P
python
import matplotlib.pyplot as plt
# 定义有限域GF(p)
p = 23
# 定义椭圆曲线方程 y^2 = x^3 + ax + b (mod p)
a = 1
b = 1
# 定义椭圆曲线上一个点P(x, y)
P = (3, 10)
# 点乘运算 Q = dP
def ecc_point_multiply(P, d, a, p):
Q = (0, 0)
for i in range(d):
Q = ecc_point_add(Q, P, a, p)
return Q
# 点加法
def ecc_point_add(P, Q, a, p):
if P == (0, 0):
return Q
if Q == (0, 0):
return P
x1, y1 = P
x2, y2 = Q
if x1 == x2 and y1 != y2:
return (0, 0)
if P == Q:
return ecc_point_double(P, a, p)
s = (y2 - y1) * pow(x2 - x1, p - 2, p) % p
x3 = (s*s - x1 - x2) % p
y3 = (s*(x1 - x3) - y1) % p
return (x3, y3)
# 点倍运算
def ecc_point_double(P, a, p):
if P == (0, 0):
return (0, 0)
x1, y1 = P
s = (3*x1*x1 + a) * pow(2*y1, p-2, p) % p
x3 = (s*s - 2*x1) % p
y3 = (s*(x1 - x3) - y1) % p
return (x3, y3)
k=[1,2,3,4,5,6,7,8,9,10,11,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27]
Q=[]
txt = []
for i in range(len(k)):
Q.append ( ecc_point_multiply(P, k[i], a, p) )
txt.append(str(i+1)+'P')
plt.figure(figsize=(15, 10))
for i in range(len(Q)):
plt.scatter(Q[i][0], Q[i][1], marker='o' , s=50, color ='gray')
for i in range(len(Q)):
plt.annotate(txt[i], xy = (Q[i][0], Q[i][1]), xytext = (Q[i][0]+0.1, Q[i][1]+0.1),fontsize=15)
for i in range(len(Q)-1):
plt.plot([Q[i][0],Q[i+1][0]] , [Q[i][1],Q[i+1][1]] )
plt.xlabel('x', fontsize=20)
plt.ylabel('y', fontsize=20)
plt.title('y^2 ≡ x^3 + x + 1 (mod 23), p=(3,10), Q=kP',fontsize=20)
plt.xlim(-1,20 )
plt.ylim(-1,23)
plt.grid(linewidth=0.5, color='gray')
plt.show()
说明一下这里计算 Q=kP 还是使用了暴力计算(和逆求解k相同),并没有使用 将 逐步二分拆解的优化(比如100P = 50P+50P, 而 50P =25P+ 25P , 25P =1P +24P, 24P = 12P +12P, 12P=6P+6P, 6P=3P+3P, 3P=1P+2P, 2P=1P+1P, 只需计算8次这种优化) 。这里的 k 和 p都 比较小, 放上代码只作为一个 demo。实际应用中还是得通过优化将为 logK。
可以看到在有限域上的乘法操作非常的无序,混乱,完全没有任何规律。这种杂乱无章的特性就非常适合用来加密, 让人无从下手,找不到任何规律,才能保障密文的安全性。
总结
至此,我已经完整地定性地推导了一次ECC椭圆曲线算法。
回顾一下, 我们从椭圆曲线的 几何加法说起, 从加法推广到乘法, 从乘法分析计算过程得出: 给定一条椭圆曲线 和基点 P, 正向计算 Q=kP 非常容易, 但反向计算 k 十分困难。 这种正向计算容易, 反向计算困难的性质,就是ECC椭圆曲线加密算法的保障。我们 顺势推出如何利用这一性质设置加密用的公钥和解密用的私钥, 并且做了安全性分析。接着我们从连续的椭圆曲线推广到离散且有界的椭圆曲线(有限域)。