RSA加密算法初探
·前言
本文全面的介绍了RSA算法的概念、原理、证明和实现。我在写作本文之前在网上查阅过相关资料,可这些资料不是含糊其辞就是满篇谬误。所以我力求用通俗易懂的文字将算法深入剖析,用最严谨的步骤进行论相关的各项算法,以降低文章的阅读难度。读者只要学过初中代数就可以理解全文,我衷心希望更多读者能认识到加密算法其实并不难。
文中的算法均为伪代码,由于伪代码没有办法进行测试,再加上我个人数学功底比较薄弱,所以错漏之处在所难免,还请各位老师给予指教。质疑或指正请发送电子邮件到fireseed1949@hotmail.com,我会认真阅读并回复的!
感谢北航数学系(毕业)李桢老师、西工大计算机系(毕业)张小宁老师在数学上对我的指点。
另注:文中mod就是求余的符号,X mod Y表示X除以Y所得的余数。
·概述
RSA算法是世界上第一个既能用于数据加密也能用于数字签名的非对称性加密算法。它易于理解和操作,所以流行甚广。算法的名字以发明者的名字命名,他们是:Ron Rivest,Adi Shamir和Leonard Adleman。虽然RSA的安全性一直未能得到理论上的证实,但它经历了各种攻击,至今未被完全攻破。为了让读者更容易的理解RSA加密,先大概讲述一下信息加密技术的相关概念和原理。
我们对于在数字媒体上进行交换的数据进行加密的方法称为信息交换加密技术,它分为两类,即对称加密和非对称加密。
在对称加密技术中,对信息的加密和解密都使用相同的钥,也就是说一把钥匙开一把锁。这种加密方法可简化加密处理过程,信息交换双方都不必彼此研究和交换专用的加密算法。如果在交换阶段私有密钥未曾泄露,那么机密性和报文完整性就可以得以保证。对称加密技术也存在一些不足,如果交换一方有N个交换对象,那么他就要维护N个私有密钥,对称加密存在的另一个问题是双方共享一把私有密钥,交换双方的任何信息都是通过这把密钥加密后传送给对方的。如三重DES是DES(数据加密标准)的一种变形,这种方法使用两个独立的56为密钥对信息进行3次加密,从而使有效密钥长度达到112位。
在非对称加密(或称公开密钥加密)体系中,密钥被分解为一对,即公开密钥(公钥)和私有密钥(私钥)。这对密钥中任何一把都可以作为公开密钥,通过非保密方式向他人公开,而另一把作为私有密钥,加以妥善保存。公开密钥用于加密,私有密钥用于解密,私有密钥只能由生成密钥的交换方掌握,公开密钥可广泛公布,但它只对应于生成密钥的交换方。非对称加密方式可以使通信双方无须事先交换密钥就可以建立安全通信,广泛应用于身份认证、数字签名等信息交换领域。非对称加密体系一般是建立在某些已知的数学难题之上,是计算机复杂性理论发展的必然结果。最具有代表性是RSA公钥密码体制。
在RSA算法中,我们先要获得两个不同的质数P和Q做为算法因子,再找出一个正整数E,使得E与 ( P - 1 ) * ( Q - 1 ) 的值互质,这个E就是私钥。找到一个整数D,使得( E * D ) mod ( ( P - 1 ) * ( Q - 1 ) ) = 1成立,D就是公钥1。设N为P和Q的乘积,N则为公钥2。加密时先将明文转换为一个或一组小于N的整数I,并计算ID mod N的值M,M就密文。解密时将密文ME mod N,也就是M的E次方再除以N所得的余数就是明文。
因为私钥E与( P - 1 ) * ( Q - 1 )互质,而公钥D使( E * D ) mod ( ( P - 1 ) * ( Q - 1 ) ) = 1成立。破解者可以得到D和N,如果想要得到E,必须得出( P - 1 ) * ( Q - 1 ),因而必须先对N进行因数分解。如果N很大那么因数分解就会非常困难,所以要提高加密强度P和Q的数值大小起着决定性的因素。一般来讲当P和Q都大于2128时,按照目前的机算机处理速度破解基本已经不大可能了。
·证明
下面将会开始讨论RSA算法的原理及其算法证明。如果您只关心RSA算法的实现,则可以略过这一步。我把每一个有用的定理都用粗标标记了,对于数学不很在行的朋友可以只了解一下相关定理的说明而不需要验证求证过程了。
一、 费马小定理[1]的转化
费马小定理:有N为任意正整数,P为素数,且N不能被P整除,则有:
NP mod P = N
费马小定理可变形为:
NP - N mod P = 0
( N ( NP - 1 - 1 ) ) mod P = 0
因为
( N ( NP - 1 - 1 ) ) mod N = 0
所以N和P的公倍数为:
N ( NP - 1 - 1 ) (1)
又因为N与P互质,而互质数的最小公倍数为它们的乘积,所以一定存在正整数M使得:N ( NP - 1 - 1 ) = MNP成立。并化简为:
NP - 1 - 1 = MP
( NP - 1 - 1 ) mod P = 0
可以变形为:
NP - 1 mod P = 1 (2)
(2)就是费马小定理的转化定理,为方便叙述,下文简称为定理一。小提示,可能很多人认为费马小定理本来就是(2),实际上不是这样,因为费马小定理的转化非常容易,而转化定理又是一个无论在数学上还是计算机程序上都很常用的公式,所以人们就普遍认为(2)就是费马小定理了。
二、 积模分解公式
有X、Y和Z三个正整数,且X * Y大于Z,则有:
( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z
证明如下
当X和Y都比Z大时,可以将X和Y表示为:
X = ZI + A (1)
Y = ZJ + B (2)
将(1)和(2)代入( X * Y ) mod Z得:
( ( ZI + A )( ZJ + B ) ) mod Z
( Z( ZIJ + IA + IB ) + AB ) mod Z (3)
因为Z( ZIJ + IA + IB )是Z的整数倍,所以(3)式可化简为:
AB mod Z
因为A和B实际上是X和Y分别除以Z的余数,所以有:
( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z成立。
当X比Z大而Y比Z小时
X = ZI + A
代入( X * Y ) mod Z得:
( ZIY + AY ) mod Z
AY mod Z
因为A = X mod Z, 又因为Y mod Z = Y,所以有:
( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z成立。
同理,当X比Z小而Y比Z大时,上式也成立。
当X和Y都比Z小时,X = X mod Z,Y = Y mod Z所以有:
( X * Y ) mod Z = ( ( X mod Z ) * ( Y mod Z ) ) mod Z成立。
积模分解公式成立。
三、 定理二
有P和Q两个互质数,如果有X mod P = 0,X mod Q = 0,则有:X mod PQ = 0
证明:
因为P和Q互质,所以它们的公倍数为KPQ(K为整数),最小公倍数为PQ。又因为X为P和Q的公倍数,所以X / PQ = K,所以X mod PQ = 0。
四、 定理三
有P和Q两个互质数,设有整数X和Y满足Y mod P = X,Y mod Q = X,则有:Y mod PQ = X
证明:
X = Y mod P
可以表示为:
Y = X + kP
Y - X = kP
即Y - X可以被P整除,同理Y - X可以被Q整除。又因为P、Q互质,根据定理二可得:
( Y - X ) mod PQ = 0
即
Y mod PQ = X
五、 定理三的逆定理
有P和Q两个互质数,设有整数X和Y满足Y mod PQ = X ,则有:Y mod P = X,Y mod Q = X
证明:
Y mod PQ = X
可以表示为:
( Y – X ) mod PQ = 0
显然
( Y – X ) mod P = 0且
( Y – X ) mod Q = 0
所以原命题成立。
六、 RSA定理
若P和Q是两个相异质数,另有正整数R和M,其中M的值与( P - 1 )( Q - 1 )的值互质,并使得( RM ) mod ( P - 1 )( Q - 1 ) = 1。有正整数A,且A < PQ,设C = AR mod PQ,B = CM mod PQ则有:A = B
证明:
将C = AR mod PQ代入B = CM mod PQ得:
B = ( ( AR mod PQ )M ) mod PQ
根据积模分解公式,可变形为:
B = ( AR )M mod PQ
B = ARM mod PQ (1)
因为有( RM ) mod ( P - 1 )( Q - 1 ) = 1,所以有:
RM = K ( P - 1 )( Q - 1 ) + 1,K为正整数。
代入(1)得:
B = AK ( P - 1 )( Q - 1 ) + 1 mod PQ (2)
如果ARM < PQ时,明显有B = A。
如果ARM > PQ,且A不是P的倍数也不是Q的倍数时,(2)可变形为:
B = ( AAK ( P - 1 )( Q - 1 ) ) mod PQ
根据积模分解公式可变形为:
B = ( ( A mod PQ )( AK ( P - 1 )( Q - 1 ) mod PQ ) ) mod PQ (3)
根据定理三的逆定理:
AK ( P - 1 )( Q - 1 ) mod PQ = ( AK ( P - 1 ) ) ( Q - 1 ) mod Q
根据费马小定理可得:
( AK ( P - 1 ) ) ( Q - 1 ) mod Q = 1,则
AK ( P - 1 )( Q - 1 ) mod PQ = 1
故( 3 )可转化为:
B = ( A mod PQ ) mod PQ
因为A < PQ,所以B = A成立。
在述证明过程中可以总结一点:
当P为素数且A和P互质时,那么当N为任意自然数时都有AN( P - 1 ) mod P = 1成立,这个定理下面还要用到,我们称之为定理四。
如果ARM > PQ,且A不是P的倍数而是Q的倍数时,A可表示为A = NQ,N为一小于A的整数。
那么(2)式可变形为:
B = ( NQ )K ( P - 1 )( Q - 1 ) + 1 mod PQ
B = ( NK ( P - 1 )( Q - 1 ) + 1 )( QK ( P - 1 )( Q - 1 ) + 1 ) mod PQ
把Q作为公因子提出来,得:
B = ( ( NNK ( P - 1 )( Q - 1 ) ) ( QK ( P - 1 )( Q - 1 ) mod P ) ) Q
用积模分解公式进行分解,得:
B = ( ( NNK ( P - 1 )( Q - 1 ) mod P )( QK ( P - 1 )( Q - 1 ) mod P ) mod P ) Q
跟据定理四,NK ( P - 1 )( Q - 1 )和QK ( P - 1 )( Q - 1 )的值都为1,所以有:
B = ( ( ( N mod P ) mod P ) mod P ) Q
B = NQ mod PQ mod PQ mod PQ
B = A mod PQ mod PQ mod PQ
因为A < PQ,所以B = A成立
同理,当A是P的倍数而不是Q的倍数时,B = A也成立。
又因为A小于PQ,而P和Q又都是质数,所以A既是P的倍数又是Q的倍数的情况不存在。
RSA定理成立。
·大整数存储运算
由于安全需要,目前主流的RSA加密算法都是基于2进制的512位或1024位的大整数,而目前的主流高级语言编译器最多也只能支持到2进制64位整数,所以大整数的存储和运算对于RSA算法的实现都是至关重要的。一个最容易理解的方法就是将大数用十进制表示,并将每一位(0 – 9)都做为一个单独的数用数组进行管理。做加减乘除等运算时,人工的对其进行进、借位。然而计算机对于10进制数的处理并不在行,而且表示非2n进制的数会浪费很多空间,所以应该采用8进制、16进制、32进制、64进制的表示法,使得每一位数字都能占据一个完整的内存空间。目前绝大多数PC机都是基于32位运算的,所以采用232进制表示大数将会很大提高计算机的处理效率。现实中,就使用32位的整数数组进行存储每一位数,另设一个布尔值表示正负。进行计算时常会遇到进位借位的情况,而且常常会超过232次方,幸好目前的编译器都支持64位整数,可以满足( 232 - 1 ) * ( 232 - 1 )以内的运算,所以使用64位整数作为运算中间量将会是很好的选择。
大数除了加减乘除等基本运算以外,还有一些如赋值、比较、左右移位、或、与等,为了方便使用,我们可以利用面向对象的方法把大数进行封装,并利用C++的特性进行运算符重载,使它成为一个整体对象来进行操作。这样我们就可像使用int一样来使用它了。当然,大数类的实现并不是一篇文章就可以叙述完的,而且目前有一些很成熟并且开源的大数类库,如GTK、HugeCalc等,因此我在这里就不对具体的算法做进一步阐释了。
·幂模运算
幂模运算是RSA算法中的关键,无论是素数测试,还是加密解密,都要用到幂模运算。简单的讲,幂模运算就是计算NR mod D的值。但是对于计算机来讲,计算R很大的NR的值时将会非常浪费存储空间,并使计算变的非常缓慢而难以实现。但是我们通过上文讨论的积模分解公式,可以发现NR mod D是可以进行转换的。
NR mod D = ( ( N mod D )R ) mod D (1)
这样,在运算( ( N mod D )R ) mod D的过程中,在最坏的情况下,可能出现的最大的值就是( D - 1 )R,而D <= N,从而较大的减少了数据量,提高了运算速度。通过观查发现,(1)式仍然可以进行分解以减少运算步骤。计算A13时,如果让A直接进行连乘,需要12次运算。但是如果把A * A的值保存起来,我们就只需要进行B = A * A和B * B * B * B * B * B * A,一共七次运算,如果我们再把B * B的值保存起来,那我们就只需要进行B = A * A、C = B * B、D = C * C、
D * C * A,一共五次运算。总结这个规律可以发现,运算过程中如果某次的幂数为奇数时,在乘方过后还需要乘以上次保留的积。跟据这个规律,我们把运算分为两部分的乘积,下面是伪代码。
算法一:计算N的E次方,令R为计算结果。
R := N ; R用来存储2n
K := 1 ; K用于存储另一部分的乘积
M := 0 ; M表示幂数每次除2的余数
WHILE E > 1
E := E / 2,余数存入M
IF M = 1
K := R * K
END IF
R := R * R
NEXT
R := R * K
再回到我们刚才讨论的幂模运算。事实上在(1)式中,我们需要求出的就是( N mod D )R的值,那么只要令上面伪代码中参量N的值为N mod D,并对结果R求R mod D就可以了,下面是基于上面求乘方算法的幂模运算的伪代码。
算法二:计算N的E次方再取D的模,令R为计算结果。
R := N mod D
R := R ^ E ;调用算法一
R := R % D
如果再利用上文过程中提到积模分解公式对算法做进一步优化,直接把取余的运算代入到乘方中,就成为了著名的蒙格马利快速幂模运算法,伪代码如下。
算法三:蒙格马利法计算N的E次方再取D的模,令R为计算结果。
R := 1
A := N
B := E
WHILE Z != 0
IF B & 1 ;判断是否为奇数
B := B - 1
R := R * A
X := X % D
ELSE
B := B / 2
A := A * A
A := A % D
END IF
NEXT
蒙格马利快速幂模运算,是目前世界上效率最高的幂模运算,很多硬件芯片在处理类似算法时都采用的这种方法。
·寻找大素数
为了有效防止破解,必要须找到两个很大的素数作为算法因子。而寻找大素数,是数学家们一个永恒的话题。素数的定义是只能被自己和1整除的自然数,按照常规的理解,使用计算机对一个很大的数进行素数测试时,需要遍历所有小于它且大于1的自然数,并逐个判断是否能被该数整除。这个过程对于非常大的素数而言是非常缓慢的。但是根据费马小定理,我们可以设计一种算法来快速测试素数。当A和Q互质时,有:AQ - 1 mod Q = 1,那么,我们可以通过判断AQ - 1 mod Q的值是否等于1对Q进行素数测试。如果取了很多个A,Q仍未测试失败,那么则认为Q是素数。当然,测试次数越多越准确,但一般来讲50次就足够了。另外,预先用常归算法构造一个包括500个素数的数组,先对Q进行整除测试,将会大大提高通过率,方法如下:
算法四:费马定理测试可能素数P
C := 500 ;素数表大小
S[ 0 TO C ] ;素数表
B := P - 1
T := 50 ;表示进行测试的次数
A := 0
FOR I := 0 TO C ;进行素数表初步测试
IF P mod S[I] = 0
RETURN FAILE
END IF
IF P < S[I]
BREAK
END IF
NEXT I
FOR I := 0 TO T
A := S[ RAND() mod C ]
IF A ^ ( P - 1 ) mod P <> 1
RETURN FAILE
END IF
NEXT I
RETURN PASS
这个算法看起来很完美,但实际上从一开始它就犯了一个很大的错,那就是对于任意与Q互质的A都有AQ - 1 mod Q = 1,这是素数的性质,是素数成立的一个必要条件,但不是充分条件!让我们来看一下29341这个数,它等于13 * 37 * 61,但任何与它互质的A都有A29341 - 1 mod 29341 = 1成立。这种数字还有不少,数学上把它们称为卡尔麦克数,现在数学家们已经找到所有1016以内的卡尔麦克数,最大的一个是9585921133193329。我们必须寻找更为有效的测试方法。数学家们通过对费马小定理的研究,并加以扩展,总结出了多种快速有效的素数测试方法,目前最快的算法是拉宾米勒测试算法,其过程如下:
首先确定N是否为奇数,不是奇数的判断失败。
选择T个随机整数A,并且有 0 < A < N成立。
进行费马小定理测试,计算并判断AN - 1 mod N的结果是否等于1,如果不是,则测试失败。
找到R和M,使得N = 2R * M + 1成立。
找R和M的方式如下:
N用二进制数B来表示,令C = B - 1。因为N为奇数,所以C的最低位为0,从C的最低位的0开始向高位统计,一直到遇到第一个1,0的个数即为R,M为B右移R位的值。
如果AM mod N = 1,则通过B对于N的测试,然后进行下一个A对N的测试,直到T个A对N的测试全部通过。
如果AM mod N的值不是1,那么将AM mod N式子中的AM看做底数S,我们将S乘方后再计算SMmod N的值,并判断是否等于1,如果是则通过B对于N的测试,然后进行下一个A对N的测试,直到T个A对N的测试全部通过;如果不是则继续将底数乘方。
如果一直到S = A ^ 2MR时,并且SM mod N = 1仍未通测试,那么测试失败。
通过验证得知,当T为素数,并且A是平均分布的随机数,那么测试有效率为1 / 4K。如果T > 50那么测试失误的机率就会小于10-30,这对于目前的计算机硬件来说已经足够证明N就是素数了。下面是伪代码。
算法五:拉宾米勒测试法测试P是否为素数。
C := 500 ;素数表大小
S[ 0 TO C ] ;素数表
B := P - 1
T := 50 ;表示进行测试的次数
A := 0 ;用来测试通过的随机整数
FOR I := 0 TO C ;进行素数表初步测试
IF P mod S[I] = 0
RETURN FAILE
END IF
IF P < S[I]
BREAK
END IF
NEXT I
M := P - 1 ;使二进制N的最后一位变为偶数
R := 0
WHILE M & 1 = 0 ;一直到有某一位为1为止
M := M >> 1
R := R + 1 ;计算R
NEXT
X := 0
Y := 0
FOR I := 0 TO T
A := S[ RAND() mod C ] ;先进行费马测试
IF A ^ ( P - 1 ) mod P <> 1
RETURN FAILE
END IF
X := A
Y := A ^ ( M * R * 2 )
WHILE X <= Y
IF X ^ M mod P = 1
BREAK
END IF
X := X ^ 2
NEXT
IF X > Y
RETURN FAILE
END IF
NEXT
RETURN PASS
·二元一次不定方程
在算法概述的章节里我们曾经讨论过公钥1的求法:找一个数D,使得( E * D ) mod ( ( P - 1 ) * ( Q - 1 ) ) = 1成立。为了求D,我们先对这个方程变形。实际上这个方程可以看做AX mod B = 1,即:
AX = BY + 1,Y为一整数。
AX - BY = 1
这就是一个二元一次不定方程,有已知数A、B,未知数X、Y。我们现在需要求的是X,那么就是求这个方程对于X的最小整数解。由于方程有两个未知数,所以必须化简方程,使得一个未知数的系数为0时才能得解。设B > A时有:
AX - BY = 1
那么可以认为B = AN + M,则有:
AX - ( AN + M )Y = 1
AX - ANY - MY = 1
A( X - NY ) - MY = 1
实际上M就是B mod A的值,设X’ = X - NY,B’ = B mod A则有AX’ - B’Y = 1,且A > M成立。接着可以用同样的方法来化简A,最终必能将一个系数化为0。此时求出另一个未知数的解,再按逆序代入上一步的方程,求出另一个未知数的解,再代入上一步的方程,一直递推的第一个方程,最终即可获得X和Y的最小整数解。因为每一步递推的方程的余数相同,所以我们称这些方程为“一次同余式”。这个算法被称为欧几里德扩展算法,而欧几里德算法其实就是求公因式的辗转相除法,大多数朋友在中学时就学过了,但是我们下面会用到,所以我这里简单的用伪代码来描述一下欧几里德算法。
算法六:求A和B两相异自然数的最大公因数,另R为结果。
IF A < B
SWAP A, B
END IF
WHILE
A := A mod B
IF A = 0
R := B
BREAK
END IF
B := B mod A
IF B = 0
R := A
BREAK
END IF
NEXT
欧几里德扩展算法虽然容易理解,但当A和B较大时会有很多步的递归,此时对于计算机而言就不是一个有效的算法了,那么如何才能设计一套真正对于计算机可行的大数二元一次方程的求解算法呢?
事实上我国古代数学家发明的大衍求一术,就可以非常好的解决这个问题。 我国对于余数理论的研究是十分悠久的,而且有着卓越的成就。早在公元280到420年间问世的《孙子算经》中就有着对一次同余式的问题有了初步的讨论。到了南宋时代,数学家秦九韶对此加以整理总结,发展成为一个解联立一次同余式的系统方法,称为“大衍求一术”,记录在他所著的《九章算术》中。大衍求一术的描述是这样的:“置奇右上,定居右下,立天元一于左上,先以右上除右下,所得商数与左上一相生,入左下,然后乃以右行上下以少除多,于互除这,所得商数随即于互累乘,于左行上下,须使右上末后奇一而止。乃验左上所得以为乘率,或奇数已见单一者便为乘率”。这句话的意思我用伪代码描述如下。
算法七:已知有自然数N和素数P,N > P。并且有正整数D使ND mod P = 1成立,求D。
IF N和P的最大公因数 <> 1 ;调用算法六
RETURN FAILE
END IF
LT := 1 ;左上
RT := N mod P ;右上
LD := 0 ;左下
RD := P ;右下
X := 0 ;中间变量
WHILE RT <> 1
X := RD / RT
RD := RD % RT
IF RD = 0
RD := RT
LD := ( X - 1 ) * LT + LD
ELSE
LD := X * LT + 1
END IF
X := RT / RD
RT := RT % RD
IF RT = 0
RT := RD
LT := ( X - 1 ) * LD + LT
ELSE
LT := X * LD + 1
END IF
NEXT
D := LT
·结语
到现在,RSA算法中所涉及到的所有算法我们都已经讨论过了。实际还有一个运算,就是私钥的获得办法:计算得到与( P - 1 ) * ( Q - 1 )的值互质的整数E。我情愿不把它称之为算法,因为只需要一个循环和一个判断就可以完成,所以这里也就没有必要对它多加论述了。
·附录
费马小定理的证明:
引理1:设M,A的最大公约数( M,A ) = 1,且M整除AB,即M mod AB = 0,则M mod B = 0。
引理2:设P是素数,<P,J>表示组合数,即从P个数中选出J个数的组合种数,且1 ≤ J ≤ P - 1,则P mod <P,J>。
证明:已知组合数<P,J> = P! / ( J! * ( P - J )! )是整数,即J! * ( P - J )! mod P! = 0。由于P是素数,所以对任意1 ≤ I ≤ P-1有( P,I ) = 1。因此由引理1有( P,j! * ( P - J )! ) = 1,1 ≤ J ≤ P - 1。进而由引理1推出:当1 ≤ J ≤ P - 1时J! * ( P - J )! mod ( P - 1 )! = 0,得证。