由于RSA算法涉及到群论和数论,因此先简单说说群论的事,然后说说数论.
什么是群呢?群就是一个集合S加上一个二元操作 o .当然这个二元操作 o不是瞎定义的,有要求的,要满足群的闭合性.设任意a,b∈S,a o b=c,群的闭合性意思就是说 c也要属于S.就是说任何两个元素通过操作得到的第三个元素还要属于S,不能出轨,不能越界.这种二元操作可以是代数中的加减乘除运算,也可以是某个函数,也可以是几何体在空间中的旋转镜像等操作.可以是任意你能想象得到的操作,只要能保证群的特性就ok.椭圆曲线加密中的操作就极其'风骚',居然是做某个点的切线,然后切线和曲线的交点.好吧,第一次看到这样的定义,我是真心佩服这个算法的发明者,你的脑洞不是一般大.
举个例子:集合为整数,操作为加法,显然任意两个数相加还是整数符合群的定义,我们把这个群叫做整数的加法群.当然操作设为乘法也是可以的.同样你也可以定义复合操作,加法和乘法都用上,c=a×2+b.
再举个例子,集合为人群,男人a和女人b一通'操作',造了个小人c也是可以的,呵呵.
上面说完了群的第一特性,再来说说群的第二个特性:有唯一的单位元e.首先e还是必须要满足e∈S,其次任何a∈S,a o e=a,然后最重要的一点是一个群里只能有一个e.例如上面说的加法群就有单位元e=0,而乘法群e就是1,这是毫不疑问的.而上面说的复合操作群c=a×2+b就不符合唯一性的要求,对于a,它的单位元就是-a,这个单位元随着每个元素a在变,显然不符合要求.那么人群就更不符合要求了,因为没有单位元e,否则这不就成了克隆.
群的第三个特性就是每个元素都有一个逆元.a∈S,b∈S,a o b=e,对比代数中的a×a-1=1,我们一般把b记做a-1.整数的加法群中a的逆元就是-a,而整数乘法群中a的逆元是1/a,但是1/a不是整数,就不满足逆元的特性了.如果把乘法的集合扩展到整个有理数就又满足了这个逆元的特性.
最后一个特性就是操作的结合律.a,b,c∈S, (a o b) o c=a o (b o c),显然整数的加法群是满足的.
我们把这样的集合S+操作o叫群,记做:(S,o)
如果集合中的元素是有限的,我们把元素的个数叫做群的规模,记做:|S|
由于群就是一个集合,因此它具有集合的某些特性,例如子集A⊆S,我们叫做子群A,当然群论是很高大上的学问,我们主要是为了弄懂RSA算法原理,其他的就不再涉及.
如果你经常看仙侠小说,你就会发现猪脚发展到最后慢慢有领域这样的技能了,说白了就是可以自定义天地法则.这不就是我们说的群论嘛.小时候学数学,什么加法啊,乘法啊都是前辈们定义好了,1+1=2,1×1=1,.....,我们在做题时只能按照加法表,乘法表来行事,等学到群论以后你就会明白,哦,加法可以不是那个加法,1+1可以不等于2,你就可以开启自己的领域.
总结下群:集合S,二元操作 o,单位元e,逆元a*a-1=e,结合律.就是这么简单.
下面再来说说数论,这是RSA算法的重头戏.
由于是为了RSA算法的学习,这里所说的数只涉及整数,更进一步地说我们只讨论自然数N={0,1,2,3,4,5.......}这个集合.
整除和约数
d|a:d整除a,定义是:存在一个整数k,使得a=k*d.如果d|a,就认为d是a的约数.例如:5|25,2|4,3|9.(注意不要和程序中的位操作符混淆了)
由整除的定义我们得出素数和合数的定义,这个大家都明白的,不再累述.
除法定理:对于任何整数a和n,存在唯一的整数q和r,满足0 ≤r <n,a=q*n+r.
说白了就是q是商,r是余数,它们和群中的单位元e一样,对于每个确定a和n,具有唯一性.显然唯一性是由于0 ≤r <n这个条件才保证的.例如:23=10*2+3,也可以23=10*1+13.
大家都是程序员,应该明白q=a/n,r=a mod n,mod为求余运算符.如果在python中就是这样写:q=a//n,r=a%n.
根据定义如果已知r和n,a显然有很多个满足这个条件.我们把这些数的集合记做[r]n={r+k*n:k∈N}
例如:[3]7={3,10,17,24,....},[0]7={0,7,14,21}
对于n,我们可以得到n个这样的集合[0]n,[1]1,[2]n....[n-1]n,我们把这n个集合的并集记做:Zn={[r]n:0≤r<n},为了后续方便简写为:Zn={0,1,2,....,n-1},这里的每个数不是数本身而是代表的一个集合.0=[0]n,1=[1]n.我们是为了编程写算法,因此姑且在这里就把每个数字当成数字本身,这样便于理解.
如果a∈[b]n,我们就记做:a≡b (mod n) ,意思就是对于n,a和b同余.
公约数和最大公约数
公约数定义:如果d|a并且d|b,则称d是a和b的公约数. 显然a和b的公约数中最大的一个就是最大公约数,我们用操作符 gcd(a,b)表示.
公约数有下面的重要性质:
1. d|a且d|b
2. d|(a+b) 且 d|(a-b)
3. d|(a*x+b*y)
以上三个性质任何一个成立都可以推导出其他性质.
互质:如果gcd(a,b)=1,则a,b互质.显然如果a,b都是质数,gcd(a,b)=1.
gcd定理:如果任意整数a,b不同时为0,则d=gcd(a,b)是a与b的线性组合a*x+b*y(x,y∈Z)中的最小正元素.
意思就是说x,y你在整数里面随便选,得出大于零的结果里面最小的那个数就是gcd(a,b),粗一看这个定理好像什么也没有说,也没有告诉我们怎么求x,y.因为根据公约数性质3也是可以得出gcd(a,b)可以由a,b的一个线性组合表示.
gcd的欧几里得算法
这是欧几里得公元前300年的<<几何原本>>记载的算法,这是个递归算法,今天计算机时代我们还是用它.
1 def euclid(a,b): 2 print(f'gcd({a},{b})') 3 if b==0: 4 return a 5 else: 6 return euclid(b,a%b)
它是根据如下定理来计算: gcd(a,b)=gcd(b,a mod b)
例如计算30,21的最大公约数,调用euclid函数得到的输出是这样的:
gcd(30,21)
gcd(21,9)
gcd(9,3)
gcd(3,0)
下面就是要把群论和数论糅合到一起讲了.
由模加法和模乘法定义的群,还记得上面Zn吗?
(Zn,+n) 模加法群:显然|Zn|=n,这个加法怎么计算呢?它是一个复合运算,就先代数加,然后对n求余.我们来验证下它是个群吗?
设a,b∈Zn,那么 (a+b)%n 必然属于Zn;0显然是单位元e,因为(a+0)%n=a%n;a的逆元,在普通加法里就是-a,但是-a不属于Zn,我们做下运算-a%n=n-a,所以a+a-1=a+(n-a)=n,n%n=0,符合要求.显然它是满足结合律的.
下面代码可以把模加法群的加法表打印出来.
1 def Zn(n): 2 print(f'+',[i for i in range(n)]) 3 for a in range(n): 4 e=[] 5 for b in range(n): 6 e.append((a+b)%n) 7 print(a,e)
下面Zn(6)的输出:
+ [0, 1, 2, 3, 4, 5] 0 [0, 1, 2, 3, 4, 5] 1 [1, 2, 3, 4, 5, 0] 2 [2, 3, 4, 5, 0, 1] 3 [3, 4, 5, 0, 1, 2] 4 [4, 5, 0, 1, 2, 3] 5 [5, 0, 1, 2, 3, 4]
在RSA中并没有用到加法群,这里只是为了演示下模加法是可以成为群的,而RSA中要用到的是乘法群.
我们模仿加法定义一个乘法群: (Zn,*n)看看行不行.
直接编代码打印出Zn*(6)和Zn*(7)的乘法表看看:
1 def ZnStar(n): 2 print(f'{n} *',[i for i in range(n)]) 3 for a in range(n): 4 e=[] 5 for b in range(n): 6 e.append((a*b)%n) 7 print(a,e)
输出为:
6 * [0, 1, 2, 3, 4, 5] 0 [0, 0, 0, 0, 0, 0] 1 [0, 1, 2, 3, 4, 5] 2 [0, 2, 4, 0, 2, 4] 3 [0, 3, 0, 3, 0, 3] 4 [0, 4, 2, 0, 4, 2] 5 [0, 5, 4, 3, 2, 1]
|
7 * [0, 1, 2, 3, 4, 5, 6] 0 [0, 0, 0, 0, 0, 0, 0] 1 [0, 1, 2, 3, 4, 5, 6] 2 [0, 2, 4, 6, 1, 3, 5] 3 [0, 3, 6, 2, 5, 1, 4] 4 [0, 4, 1, 5, 2, 6, 3] 5 [0, 5, 3, 1, 6, 4, 2] 6 [0, 6, 5, 4, 3, 2, 1]
|
分析下表格看看:
首先这两个群是满足封闭性;
再看单位元e,对于0来说,单位元e=0,而其他元素单位元e=1,显然不符合单位元e的唯一性;
再看逆元的问题,左边的先暂定单位元e=1,结果会发现2,3,4找不到一个元素和它相乘使得结果为1.右边的除了0其他1到6都是没有问题的.[1*1≡1(mod 7),2*4≡1(mod 7),3*5≡1(mod 7),4*2≡1(mod 7),5*3≡1(mod 7),6*6≡1(mod 7)]
为什么会这样呢?显然如果直接这么定义乘法群是行不通的,我们必须剔除掉一些不符合要求的元素.首先我们剔除掉0,然后再看看6中的{2,3,4}和{1,5}这两组数有什么区别啊,不用我们想了,前辈们已经找到了:如果gcd(a,6)=1,这个a就符合要求.而7由于是质数,所以gcd(a,7)=1 (0<a<7),因此7中除了0所有的元素都符合要求.
我们再来看看9是不是符合这样的规律:
9 * [0, 1, 2, 3, 4, 5, 6, 7, 8] 0 [0, 0, 0, 0, 0, 0, 0, 0, 0] 1 [0, 1, 2, 3, 4, 5, 6, 7, 8] 2 [0, 2, 4, 6, 8, 1, 3, 5, 7] 3 [0, 3, 6, 0, 3, 6, 0, 3, 6] 4 [0, 4, 8, 3, 7, 2, 6, 1, 5] 5 [0, 5, 1, 6, 2, 7, 3, 8, 4] 6 [0, 6, 3, 0, 6, 3, 0, 6, 3] 7 [0, 7, 5, 3, 1, 8, 6, 4, 2] 8 [0, 8, 7, 6, 5, 4, 3, 2, 1]
可以发现3,6和9的最大公约数不等于1,不符合要求.
现在我们重新定义乘法群,不用原来的Zn这个集合,改用Z*n={[a]n:gcd(a,n)=1}这个集合,因此我们把这个群记做:(Z*n,*n)
到这里我们用φ(n)来表示Z*n的规模(就是集合里有多少个元素),然后欧拉(这是个牛人,那那都有他)找到了计算公式:
φ(n)=n∏(1-1/p) (p:p是素数且p|n)
举个例子来说明:φ(45)=?,首先我们分解45找它的约数{1,3,5,9,15,45},然后找是素数的约数{3,5}.最后求解φ(45)=45*(1-1/3)*(1-1/5)=24.
根据上面的乘法表,我们知道φ(6)=2,φ(7)=6,我们用公式验算下:φ(6)=6*(1-1/2)*(1-1/3)=2,φ(7)=7*(1-1/7)=6(=7-1).
如果n是素数,那么上面的公式就能简化为:
φ(n)=n*(1-1/n)=n-1 (n是素数)
如果n=p*q,其中p,q都是素数:
φ(n)=p*q*(1-1/p)*(1-1/q)=(p-1)*(q-1)
上面我们定义了加法群,乘法群,现在我们来定义幂运算群.
令<a>表示由a幂组成的Z*n的子群.令ordn(a)表示a在Z*n中的阶(通俗点就是<a>有多少个元素).举几个例子看看.
3i mod 7 对照表:
i [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<a> [1, 3, 2, 6, 4, 5, 1, 3, 2, 6]
2i mod 7 对照表:
i [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<a> [1, 2, 4, 1, 2, 4, 1, 2, 4, 1]
2i mod 5 对照表:
i [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<a> [1, 2, 4, 3, 1, 2, 4, 3, 1, 2]
5i mod 9 对照表:
i [0, 1, 2, 3, 4, 5, 6, 7, 8, 9]
<a> [1, 5, 7, 8, 4, 2, 1, 5, 7, 8]
从上面可以看出:<3>7={1,3,2,6,4,5},ord7(3)=6=7-1;<2>5={1,2,4,3},ord5(2)=4=5-1;<5>9={1,5,7,8,4,2},ord9(5)=6
关于幂运算群有下面2个定理
1. 欧拉定理: aφ(n)≡1(mod n) a∈Z*n
举例说明:对于7,由于7是素数,所以φ(7)=7-1=6, 所以从上面对照表可知36 mod 7=1.当然了1到6都是这样的.再看看9,φ(9)=9*(1-1/3)=6, 56 mod 9=1
2. 费马定理:如果p是素数,则ap-1≡1(mod p) ,a∈Z*p.
p是素数,φ(p)=p-1,根据上面的欧拉定理,ap-1=aφ(p),所以ap-1≡1(mod p)
RSA是非对称加密算法,也就是说加密和解密的秘钥不同,确切的说是群(Z*n,*n)中一对互为逆元的元素.已知加密秘钥a,我们要想解密就得计算解密秘钥x使得a*x≡1(mod n),到了这里大家就会疑惑,前面也没有告诉我们怎么求一个元素逆元.当然了,你可以使用上面的程序把(Z*n,*n)乘法表写出来,通过查表得到逆元.可是对于RSA动辄1024位以上的数字,要是这么干就得累死,所以我们必须找到通用的公式求解逆元.
通过定义我们得知,(Z*n,*n)中每个元素a和n是互质的,即:gcd(a,n)=d=1
根据最大公约数的定理:
gcd(a,n)=a*x+n*y=1
⇒ a*x=1-n*y
⇒ a*x≡(1-n*y)(mod n)
⇒ a*x≡1(mod n) (因为n|n*y,所以-n*y mod n=0).
看看那个金光闪闪的1了吗?原来a的逆元x就是a,n的某个线性组合a的系数.看到这里有没有一种感觉:山穷水尽疑无路.因为x,y取值为整个整数范围,我们要选取一对x,y满足a*x+n*y=1,根据排列组合原理,x,y的选取是∞*∞,有无穷多种,至少查表法只需要查n-1次就可以了.
不要着急,让我们回顾下欧几里得求最大公约数的算法:
设gcd(a,n)=d,q=a/n (这里q是a除以n的商),则a mod n=a-n*q
调用函数euclid(a,n)
递归调用euclid(n,a mod n)
显然根据最大公约数定理我们可以得出下面两个等式:
d=gcd(a,n)=a*x+n*y
d=gcd(n, a mod n)=n*x1+(a mod n)*y1=n*x1+(a-n*q)*y1=a*y1+n*(x1-q*y1)
参考上面两个等式,只要x=y1,y=x1-q*y1,就能使得两个线性组合相等.
继续递归下去,我们可以得到x1=y2,y1=x2-q1*y2,............
一直到最后我们来到这里euclid(a0,0),而gcd(a0,0)=a0=a0*x0+0*y0,我们令x0=1,y0=0,显然可以满足等式.
到这里我们就柳暗花明又一村了.上层的x,y可以通过下层的x,y计算得来,现在我们可以直接求解最底层x,y,那么与a相关的系数x就可以求出,它就是a的逆元.
现在编写代码,显然只需要把euclid函数稍微改动下就ok了.代码如下:
1 def EuclidEx(a,n): 2 if n==0: 3 return [a,1,0] 4 else: 5 [d1,x1,y1]=EuclidEx(n,a%n) 6 return [d1,y1,x1-a//n*y1]
在这里要提出一个问题请大家思考下:在上面找x的过程中,递归最底层gcd(a0,0)=a0=a0*x0+0*y0,我们令x0=1,y0=0,有人就觉得y0=0没有道理,因为y0等于任何整数等式都是成立的,请问让y0不等于零可以算出正确x吗?
现在我们来讲讲最后一个定理:中国余数定理. 学了这多年的计算机编程,中国人都是哪里凉快哪里待着去,终于我们中国人出场了,顿时热泪盈眶啊!!!这个定理讲的是啥呢,我们来举个例子就明白了.
如果小时候搞过奥数的同学你可能做过类似的题:如果某数除以3余2,除以5余3,除以7余2,请问这个数是多少?大约公元100年,中国的数学家孙子(不是孙子兵法那个人)找到解这种题的规律,然后公元1200年左右,南宋数学家秦九韶又进一步归纳总结,到了18世纪,瑞士数学家欧拉用现代化的数学语言证明.这个定理讲的就是怎么找这个数,由于很复杂这里就不写出来了,而且RSA算法不直接用到中国余数定理,但是用到了它的一个推论:
设n=p*q,gcd(p,q)=1(也就是p,q互质)
如果x≡a(mod p) 且 x≡a(mod q),则x≡a(mod n)
这里是为了后面的RSA算法就用了p和q两个数进行说明,其实根据中国余数定理,任意多个数的乘积n都是具有这种性质的.
讲到这里终于可以开始讲RSA算法了.
1.随机选2个很大的素数:p,q
2.计算n=p*q⇒ φ(n)=(p-1)*(q-1)
3.选一个小奇数e,使得e,φ(n)互质.
4.对于模φ(n),计算e的逆元d
现在可以开始加密解密了.如果把e当做加密的秘钥,d就是解密的秘钥,反之也可以.
假设需要加密的消息为M,加密就是 Me mod n =C,这个C就是密文,解密就是 Cd mod n,我们来看看Cd mod n是否等于M
把Me mod n=C代入Cd mod n中:(Me mod n)d mod n=Me*d mod n
e,d互为逆元,所以 e*d≡1 mod φ(n) ⇒ e*d≡1 mod (p-1)*(q-1),所以e*d=1+k*(p-1)*(q-1) 这里k为 (p-1)*(q-1)/(e*d)的商
Me*d≡M1+k*(p-1)*(q-1) (mod p)
⇒ Me*d≡M*Mk*(p-1)*(q-1) (mod p),根据费马定理, M(p-1)≡1(mod p)
⇒ Me*d≡M*1k*(q-1) (mod p)
⇒ Me*d≡M (mod p)
同理,我们可以证明:
Me*d≡M (mod q)
根据中国余数定理的推论:
n=p*q ⇒ Me*d≡M (mod n)
显然解密出来就等于原文M.
不多说,上代码:
1 def euclidEx(a,n,y=0): #还记得我们上面提到的问题吗? 2 if n==0: 3 return [a,1,y] 4 else: 5 [d1,x1,y1]=euclidEx(n,a%n,y) 6 return [d1,y1,x1-a//n*y1] 7 8 # 计算Zn* 乘法逆元 9 def reverse(a,n): 10 r=euclidEx(a,n) 11 r=r[1] 12 r=r%n 13 return r 14 15 def RSA(p,q,e,m): 16 n=p*q 17 fn=(p-1)*(q-1) 18 d=reverse(e,fn) 19 return m,d,m**e%n
是不是很简单的一个算法!当然了,这段代码只是用于实验,实际生产中是不行的.因为为了安全性,我们会把p,q选的很大,大到常规编程语言的整数变量都会溢出.