由于RSA算法涉及到群论和数论,因此先简单说说群论的事,然后说说数论.

    什么是群呢?群就是一个集合S加上一个二元操作 o .当然这个二元操作 o不是瞎定义的,有要求的,要满足群的闭合性.设任意a,b∈S,a o b=c,群的闭合性意思就是说 c也要属于S.就是说任何两个元素通过操作得到的第三个元素还要属于S,不能出轨,不能越界.这种二元操作可以是代数中的加减乘除运算,也可以是某个函数,也可以是几何体在空间中的旋转镜像等操作.可以是任意你能想象得到的操作,只要能保证群的特性就ok.椭圆曲线加密中的操作就极其'风骚',居然是做某个点的切线,然后切线和曲线的交点.好吧,第一次看到这样的定义,我是真心佩服这个算法的发明者,你的脑洞不是一般大.

  举个例子:集合为整数,操作为加法,显然任意两个数相加还是整数符合群的定义,我们把这个群叫做整数的加法群.当然操作设为乘法也是可以的.同样你也可以定义复合操作,加法和乘法都用上,c=a×2+b.

  再举个例子,集合为人群,男人a和女人b一通'操作',造了个小人c也是可以的,呵呵.

  上面说完了群的第一特性,再来说说群的第二个特性:有唯一的单位元e.首先e还是必须要满足eS,其次任何aS,a o e=a,然后最重要的一点是一个群里只能有一个e.例如上面说的加法群就有单位元e=0,而乘法群e就是1,这是毫不疑问的.而上面说的复合操作群c=a×2+b就不符合唯一性的要求,对于a,它的单位元就是-a,这个单位元随着每个元素a在变,显然不符合要求.那么人群就更不符合要求了,因为没有单位元e,否则这不就成了克隆.  

  群的第三个特性就是每个元素都有一个逆元.aS,bS,a o b=e,对比代数中的a×a-1=1,我们一般把b记做a-1.整数的加法群中a的逆元就是-a,而整数乘法群中a的逆元是1/a,但是1/a不是整数,就不满足逆元的特性了.如果把乘法的集合扩展到整个有理数就又满足了这个逆元的特性.

  最后一个特性就是操作的结合律.a,b,cS(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,就认为da的约数.例如:5|25,2|4,3|9.(注意不要和程序中的位操作符混淆了)

  由整除的定义我们得出素数和合数的定义,这个大家都明白的,不再累述.

  除法定理:对于任何整数an,存在唯一的整数qr,满足0 ≤r <n,a=q*n+r.

  说白了就是q是商,r是余数,它们和群中的单位元e一样,对于每个确定an,具有唯一性.显然唯一性是由于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. 

  根据定义如果已知rn,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,yZ)中的最小正元素.

    意思就是说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*11(mod 7),2*41(mod 7),3*51(mod 7),4*21(mod 7),5*31(mod 7),6*61(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, 所以从上面对照表可知3mod 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*d1 mod φ(n) ⇒  e*d1 mod (p-1)*(q-1),所以e*d=1+k*(p-1)*(q-1)   这里k为 (p-1)*(q-1)/(e*d)的商

      Me*dM1+k*(p-1)*(q-1) (mod p)

    ⇒    Me*dM*Mk*(p-1)*(q-1) (mod p),根据费马定理, M(p-1)≡1(mod p)

    ⇒    Me*dM*1k*(q-1) (mod p)

      ⇒     Me*dM (mod p)

      同理,我们可以证明:

        Me*dM (mod q)

     根据中国余数定理的推论:

        n=p*q  ⇒    Me*dM (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选的很大,大到常规编程语言的整数变量都会溢出.