网上写 RSA 算法原理的文章不少,但是基本上要么忽略了数学原理的说明,要么缺少实际的可运行的例子,为此特写了此文,将 RSA 需要用到的数学概念和定理都总结了一番,并基于算法原理使用 python 实现了 RSA 密钥生成和加解密的demo,希望对大家深入了解 RSA 算法有所帮助。本文第 1 节为 RSA 相关的数论基础,第 2 节是 RSA 算法原理描述和证明,第 3 节通过 openssl 生成 RSA 密钥来分析实际应用中的密钥格式。可以直接从第 2 节开始,遇到相关定理可以转到第 1 节处查阅即可。本文所有代码都在 shishujuan:rsa-algrithm,如有错误,也恳请指正。
1 数论基础
本节内容中可能用到的符号说明如下:
- : 指代整数集合,包括正负整数和0。
- : 指代 0 到 n-1 之间的整数集合。
- :数 a 的绝对值。
- : 指代小于等于 a/n 的最大整数,比如 ⌊5/3⌋ = 1,⌊8/3⌋ = 2 等。
- 质数:质数在有些地方又翻译为素数,本文统一用质数表述。
- :p 对模数 q 的模逆元素,即
- 乘法符号:文中可能在证明和示例中用了不同的乘法符号如 ,,意义一致。
1.1 质数和合数
质数和合数: 质数是指除了平凡约数1和自身之外,没有其他约数的大于1的正整数。大于1的正整数中不是素数的则为合数。如 7、11 是质数,而 4、9 是合数。在 RSA 算法中主要用到了质数相关性质,质数可能是上帝留给人类的一把钥匙,许多数学定理和猜想都跟质数有关。
1.2 除法定理
[定理1] 除法定理: 对任意整数 a 和 任意正整数 n,存在唯一的整数 q 和 r,满足 。其中, 称为除法的商,而 称为除法的余数。
[证明] 除法定理
- 存在性证明
设集合 ,则可以知道集合 S 中一定存在非负元素,设这个非负整数集合为 T,根据自然数的良序法则,非空自然数集合中必然存在最小元素。设 为集合 T 中的最小元素,因为集合 T 是非负整数集合,因此 。而 r 必然满足 ,因为 ,则可以推出元素 。但是 ,这与我们定义的 r 是集合 T 中最小的元素矛盾,因此 。这也就证明了 q 和 r 的存在性,即一定存在 q 和 r,且 。- 唯一性证明
假设有两组不同的整数 和 满足条件,则有
两个式子相减可得
左边绝对值 ,但是右边绝对值 显然是 n 的整数倍,因此只能 ,继而可得 ,由此唯一性得证。
整除: 在除法定理中,当余数 时,表示 a 能被 n 整除,或者说 a 是 n 的倍数,用符号 表示。
1.3 约数、公约数和最大公约数
约数和倍数: 对于整数 d 和 a,如果 ,且 ,则我们说 d 是 a 的约数,a 是 d 的倍数。
公约数: 对于整数 d,a,b,如果 d 是 a 的约数且 d 也是 b 的约数,则 d 是 a 和 b 的公约数。如 30 的约数有 1,2,3,5,6,10,15,30,而 24 的约数有 1,2,3,4,6,8,12,24,则 30 和 24 的公约数有 1,2,3,6。其中 1 是任意两个整数的公约数。
公约数的性质:
- 若 ,且 且 。更一般的,对任意整数 x 和 y,有:,且
- 若 ,则要么 ,要么 ,即 且 可以推出
最大公约数: 两个整数最大的公约数称为最大公约数,用 来表示,如 30 和 24 的最大公约数是 6。 有一些显而易见的性质:
[定理2] 最大公约数定理: 如果 a 和 b 是不为0的整数,则 是 a 和 b 的线性组合集合 中的最小正元素。
[证明] 是 a 和 b 线性组合集合最小正元素。
设 s 是 a 和 b 的线性组合集合中最小正元素,且对某个 ,有 ,设 ,则 ,因此 也是 a 和 b 的一种线性组合。由于 ,所以只能 ,不然就与我们假设矛盾了。由 ,于是 ,类似可以得到 ,于是 s 是 a 和 b 的公约数,于是根据定义,。
另一方面,因为 可以同时被 a 和 b 整除,而 s 是 a 和 b 的一个线性组合,由性质 1.1 可得 ,另外 ,由性质 1.2 可知 。结合前面的 ,可证得 。
由定理2可以得到一个推论:
[推论1] 对任意整数 a 和 b,如果 且 ,则 。
推论1的证明比较简单,由定理2可知 是 a 和 b 的一个线性组合,故由性质 1.1 可得证 。
1.4 互质数
互质数: 如果两个整数 a 和 b 只有公因数 1,即 ,则我们就称这两个数是互质数(coprime)。比如 4 和 9 是互质数,但是 15 和 25 不是互质数。
互质数的性质:
- 1与任何正整数都是互质数。
- 任何两个质数都是互质数。
- 质数与小于它的每一个数都是互质数。
1.5 欧几里得算法
欧几里得算法分为朴素欧几里得算法和扩展欧几里得算法,朴素法用于求两个数的最大公约数,而扩展的欧几里得算法则有更多广泛应用,如后面要提到的求一个数对特定模数的模逆元素等。
1.5.1 朴素欧几里得算法
求两个非负整数的最大公约数最有名的是 辗转相除法,最早出现在伟大的数学家欧几里得在他的经典巨作《几何原本》中。辗转相除法算法求两个非负整数的最大公约数描述如下:
例如,,在求解过程中,较大的数缩小,持续进行同样的计算可以不断缩小这两个数直至其中一个变成零。
[证明] 欧几里得辗转相除法求GCD
由条件知 ,令 ,则需要证明 。
首先,令 ,则根据 gcd 定义, 且 。即 且 ,由此可得 ,则 。即 。
其次,令 ,则根据 gcd 定义,。因为 ,因此,。于是 ,可得 ,即 。
由此可得 且 ,由性质1.2 可得 。
欧几里得算法的python实现如下:
gcd = lambda a, b: (gcd(b, a % b) if a % b else b)
1.5.2 扩展欧几里得算法
扩展欧几里得算法在 RSA 算法中求模反元素有很重要的应用,定义如下:
定义: 对于不全为 0 的非负整数 ,则必然存在整数对 ,使得
例如,a 为 3,b 为 8,则 。那么,必然存在整数对 ,满足 。简单计算可以得到 满足要求。
[证明] 扩展欧几里得算法
设 ,则当 时,,则 就是一组解。
都不为 0 时,令 ,由定理2,设 。此外,由定理2,设 ,根据 r 的定义改写此等式,可以得到 ,即 , 根据欧几里得算法,,则令 即可。由此可以得到求解 的递归解法。
扩展欧几里得算法的python实现如下:
# 扩展欧几里得算法实现,参数为 a 和 b,返回最大公约数 d 和 线性组合中的参数 x,y。
def ext_gcd(a, b):
if b == 0:
return a, 1, 0
d2, x2, y2 = ext_gcd(b, a % b)
d, x, y = d2, y2, x2-a/b*y2
return d, x, y
1.6 同余和模运算
同余: 对于正整数 n 和 整数 a,b,如果满足 ,即 a-b 是 n 的倍数,则我们称 a 和 b 对模 n 同余,记号如下: 例如,因为 ,于是有 。
对于正整数 n,整数 ,如果 则我们可以得到如下性质:
譬如,因为 ,则可以推出 。
[证明] 同余加法和乘法性质
因为 ,则说明存在整数 c 和 d,满足 ,因此: 加法性质得证,同理可得: 乘法性质得证。
另外,若 p 和 q 互质,且 ,则可推出:
[证明] 因为 。而 p 和 q 互质,于是可得 ,因此可得 。
此外,模的四则运算还有如下一些性质,证明也比较简单,略去。
模逆元素: 对整数 a 和正整数 n,a 对模数 n 的模逆元素是指满足以下条件的整数 b。 a 对 模数 n 的 模逆元素不一定存在,a 对 模数 n 的模逆元素存在的充分必要条件是 a 和 n 互质,这个在后面我们会有证明。若模逆元素存在,也不是唯一的。例如 a=3,n=4,则 a 对模数 n 的模逆元素为 7 + 4k,即 7,11,15,...都是整数 3 对模数 4 的模逆元素。如果 a 和 n 不互质,如 a = 2,n = 4,则不存在模逆元素。
[推论2] 模逆元素存在的充分必要条件是整数 a 和 模数 n 互质。
[证明] 模逆元素存在性证明
- 若 a 和 n 互质,则 ,由扩展欧几里得算法知道,必然存在 x, y 使得 ,则模除 n 可得 ,显然 x 就是 a 对 模数 n 的模逆元素。事实上, 都是 a 对模 n 的模逆元素,取其中最小的就是 x。
- 若 a 和 n 不互质,设 ,则 ,则在模除 n 时, 不会同余为 1,故此时不存在模逆元素。
1.7 唯一质数分解定理
[定理3] 唯一质数分解定理: 任何一个大于1的正整数 n 都可以 唯一分解 为一组质数的乘积,其中 都是自然数(包括0)。比如 6000 可以唯一分解为 。
[证明] 唯一质数分解定理
存在性证明
- 使用反证法来证明,假设存在大于1的自然数不能写成质数的乘积,把最小的那个称为 n。自然数可以根据其可除性(是否能表示成两个不是自身的自然数的乘积)分成3类:质数、合数和 1。首先,按照定义,n大于1。其次,n 不是质数,因为质数 p 可以写成质数乘积:,这与假设不相符合,因此n只能是合数。
- 但每个合数都可以分解成两个严格小于自身而大于 1 的自然数的积。设 ,由假设可知,a 和 b 都可以分解为质数的乘积,因此 n 也可以分解为质数的乘积,所以这与假设矛盾。由此证明所有大于 1 的自然数都能分解为质数的乘积。
唯一性证明
- 当 n=1 的时候,确实只有一种分解。
- 假设对于自然数 n>1,存在两种因式分解: ,其中 p 和 q 都是质数,我们要证明 。如果不相等,我们可以设 ,从而 小于所有的 。由于 和 是质数,所以它们的最大公约数为1,由欧几里德算法可知存在整数 a 和 b 使得 。因此
(等式1)。由于 ,因此等式1左边是 的整数倍,从而等式1右边的 也必须是 的整数倍,因此必然有 ,而这与前面 小于所有的 矛盾。- 由对称性,对 这种情况可以得到类似结论,故可以证明 ,同理可得 ,由此完成唯一性的证明。
由质数唯一分解定理可以得到一个推论:质数有无穷多个。
[证明] 质数有无穷多个。
用反证法。假设质数有限,为 ,最大质数为 ,则令 为所有质数之积+1,即 ,显然 ,且 不能被已有的任何一个质数整除。
由质数唯一分解定理知道, 可以分解为质数的乘积,但是它无法分解为已有质数的乘积,那么要么 自己是质数;要么 是合数,但是存在比 更大的质数分解因子。不管哪种情况,都与假设的最大质数为 矛盾,故假设不成立,质数有无穷多个。
1.8 中国剩余定理
[定理4] 中国剩余定理(Chinese remainder theorem,CRT),最早见于《孙子算经》(中国南北朝数学著作,公元420-589年),叫物不知数问题,也叫韩信点兵问题。
有物不知其数,三三数之剩二,五五数之剩三,七七数之剩二。问物几何?
翻译过来就是已知一个一元线性同余方程组求 x 的解:
宋朝著名数学家秦九韶在他的著作中给出了物不知数问题的解法,明朝的数学家程大位甚至编了一个《孙子歌诀》:
三人同行七十希,五树梅花廿一支,七子团圆正半月,除百零五便得知。
意思就是:将除以 3 的余数 2 乘以 70,将除以 5 的余数 3 乘以 21,将除以 7 的余数 2 乘以 15,最终将这三个数相加得到 。再将 233 除以 3,5,7 的最小公倍数 105 得到的余数 ,即为符合要求的最小正整数,实际上, 都符合要求。
物不知数问题解法本质
- 从 5 和 7 的公倍数找到一个除以 3 余 1 的 (如70),从 3 和 7 的公倍数中找一个除以 5 余 1 的 (如21),从 3 和 5 的公倍数找一个除以 7 余 1 的 (如15),则 ,模除 105,得最小正整数解 23。
- 因为 除以3 余1, 和 都是 3 的倍数,则由性质 1.9 知, 满足除以 3 余 1,同理也可得它满足除以 5 余 1,除以 7 余 1的条件。而前面找的是余 1 的数,使用性质 1.9 和 1.10 可知,乘以余数后 正好满足要求,即除以 3 余 2,除以 5 余 3,除以 7 余 2。为什么要先找余 1 的数,然后再乘以余数呢?这个可以在后面的通项公式求解知道其中缘由。
求解通项公式
中国剩余定理相当于给出了以下的一元线性同余方程组的有解的判定条件,并用构造法给出了解的具体形式。
模数 两两互质,则对任意的整数:,方程组 有解,且解可以由如下构造方法得到:
- 1)设 M为所有模数的乘积
并设 是除 以外的其他 个模数的乘积。
- 2)设 为 对模数 的模逆元素,即
- 3)则方程组 (S)的通解形式为:
- 4)以物不知数问题为例,则由 ,,同理得 。求 模除 的最小的模逆元素,可得 ,同理得 。故而解为:
中国剩余定理通项公式证明
[证明] 中国剩余定理
1) 先证明简单的情况,假设只有两个方程,互质,求解 x。
这里我们构造一个解:
即 是 对模数 的模逆元素, 是 对模数 的模逆元素 (因为 m_1 和 m_2 互质,则对应的模逆元素一定存在),则可以证明 x 是满足条件的解,证明如下:
当然,对于两个方程的情况,我们也可以直接根据模运算的定义来求解,这个方式也会在后面 RSA 算法优化中用到。因为 ,于是可以令:
代入到第一个方程有:
根据性质 1.9,可得:
因为 互质,所以 对于 必然存在模逆元素 ,满足 ,根据性质 1.10 可得:
由此可以得到 ,从而得到 x 在 范围内的唯一解是:
2)推广到 n 个方程组,对 ,则对 ,因为 与 互质,则可得 ,且 和 互质,则 。因为 ,由推论2 知,必然存在整数 ,满足:,即 是 模 的模逆元素。考察乘积 可知:
而 ,故而 满足:
所以可以得出 x 就是方程组 的一个解。3)另外,假设 和 都是方程组的解,那么:
,而 互质,从而可得 。即 两个解必然相差 的整数倍,故而方程组通解为:
1.9 欧拉函数、欧拉定理和费马定理
1.9.1 欧拉函数
欧拉函数定义:对正整数 n,欧拉函数 是指小于或等于 n 的正整数中与 n 互质的数的数目。比如 (因为 1,3,5,7 都与 8 互质)。由唯一质数分解定理可知,正整数 n 可以分解为质数乘积,据此可以得到欧拉函数的计算公式如下。
唯一质数分解:
欧拉函数计算公式:
如 ,故 。
欧拉函数计算公式推导:
- 1)若 n 为 1,显然 。
- 2)若 n 为质数,有性质 1.8 可知,每个小于 n 的数都与 n 是互质数。则 。
- 3)若 n 为质数的幂,即 (p为质数,k为大于等于1的整数),则因为只有当一个数不包含质数 p,才可能与 n 互质。而包含质数 p 的数一共有 个,故互质数目为 。
- 4)若 n 为两个互质数的乘积,即 (p, q 为互质数)。则 ,例如 。在证明之前,可以看个特例,即 p 和 q 都是质数,则 p 和 q 肯定是互质数,这种情况下与 n 互质的数不能包含质因子 p 和 q,p 的倍数有 q 个,q 的 倍数有 p 个,这里还多算了一个数 n,它即是 p 的倍数也是 q 的倍数,因此 1 到 n 中 p 和 q 的倍数的数一共有 个,则可以推导如下:
若 p 和 q 互质,但是 p 和 q 不一定是质数。 则 ,设小于 p 的数中与 p 互质的正整数有 个,则这些数的集合为 。而小于 q 且与 q 互质的正整数有 个,设这些数的集合为 。从 集合 R 和 S中分别选取一个数 r 和 s,构造方程组 以及 ,由中国剩余定理可知,则当 时方程组存在且仅存在一个正整数解,即 有唯一解。且该方程组在 p 和 q相同的情况下,如果 r 和 s 不同,则解 x 也一定不同。下面证明解 x 满足 。
因为 ,由欧几里得算法可得 ,当 时,则可得 ,同理,。由 和 ,且 互质,可得 ,即 x 是满足欧拉函数的一个数。 而 这样的数对一共有 个,而 一共有 个,故而得证 。
看一个实际的例子,设 ,则对于 ,(a, b) 和 x 存在如下一一对应关系。即若 p 和 q 为互质数,则对于任意的数对 ,存在唯一的 (其中 表示整数集合 )。
(a, b) | x | (a, b) | x | (a, b) | x |
---|---|---|---|---|---|
(0, 0) | 0 | (1, 0) | 10 | (2, 0) | 5 |
(0, 1) | 6 | (1, 1) | 1 | (2, 1) | 11 |
(0, 2) | 12 | (1, 2) | 7 | (2, 2) | 2 |
(0, 3) | 3 | (1, 3) | 13 | (2, 3) | 8 |
(0, 4) | 9 | (1, 4) | 4 | (2, 4) | 14 |
- 综上,可以得到通项公式:
设
(由4可得)
(由3可得)
(由 n 分解式可得)
计算欧拉函数可以使用 python 的这个模块: eulerlib,一个示例代码如下:
import eulerlib
e = eulerlib.numtheory.Divisors(10000)
e.phi(21)
1.9.2 欧拉定理
欧拉定理基于欧拉函数而来,它是 RSA 算法的核心。内容如下:
欧拉定理: 如果两个正整数 a 和 n 互质,则 n 的欧拉函数 满足下面的条件:
基于前面的知识,我们来证明下欧拉定理。
[证明] 欧拉定理
根据欧拉函数定义,小于 且与 互质的数有 个,设这些数的集合为 。显然对于 U 中的任意数 ,都有 。若一个正整数 a 与 n 互质,且 a 比 n 大,同样满足 与 n 互质,即 。证明如下:设 ,由于 kn 不会与 n 互质,故 r 必须和 n 互质,而 ,故 ,即 。因此得到一个结论,只要正整数 a 与 n 互质,则有 。
接着我们可以证明若 a 与 n 互质,且 u 与 n 互质,则 与 n 互质,继而可得到 。这是为什么呢?因为根据唯一质数分解定理,a 和 u 都可以分解为质数乘积,如 ,而 ,同理,n也可以分解为质因数乘积 。而由 a 和 u都和 n 互质,则 a 和 n 的质因子没有交集,且 u 和 n 的质因子没有交集,则 au 的质因子与 n 没有交集,故而 au 和 n 互质,由前面的结论,可得到 。
将集合 U 中的数都乘以 a,可以得到新的集合 ,即 ,由前面分析可知 中的每个数都与 n 互质。所以,对于任意的 ,可得到 ,即 中每个数除以 n 的余数各不相同。这个用反证法不难证明,若存在 ,满足 ,则可以设 ,故可得 ,而 a 与 n 互质,则要满足条件,只能 是 n 的倍数,而我们知道 ,与假设矛盾。由 中每个数与 n 互质,且它们除以 n 的余数与 n 互质且各不相同,故而 中每个数除以 n 得到的余数就是集合 中的那 个数,当然顺序不一定是一一对应。因此 。
有了上一步的结论,再加上模除的性质,就可以证明欧拉定理了:
1.9.3 费马小定理
费马小定理:给定整数a和质数p,若a不是p的倍数,那么:
费马小定理可以看做是欧拉定理的特例。因为当 p 为 质数时,a 不是 p的倍数,则 a与p互质,且 p 的欧拉函数 ,代入欧拉定理即可得证。
2 RSA算法原理
RSA算法基于欧拉定理而来,如果只是为了证明RSA算法原理,只要明白欧拉定理即可。当然,欧拉定理的证明涉及数论中许多经典的定理,有兴趣的同学可以深入了解证明过程。话说回来,即便不看欧拉定理的证明过程,只要明白欧拉定理内容,看懂RSA算法原理也是没有问题的。
2.1 RSA算法
1)生成两个随机的不相等的大质数 p 和 q,它们的积 的二进制位数就是密钥的位数,通常设置的位数有 1024,2048,3072,4096等。
[知识点] 大质数生成算法
如何生成这两个大质数,这是个值得研究的问题。首先可以想到的是生成质数序列,从中随机选取2个大质数即可。一种改进的方法是: 除了 2 外的偶数肯定不是质数,而奇数可能是质数,可能不是,那就可以跳过2与3的倍数,即对于 6n,6n+1, 6n+2, 6n+3, 6n+4, 6n+5,我们只需要判断 6n+1 与 6n+5 是否是质数即可。而判断某个数m是否是质数,最基本的方法就是对 2 到 m-1 之间的数除 m,如果有一个数能够整除 m,则 m 就不是质数。判断 m 是否是质数还可以进一步改进,只需要对 2 到 之间的数除 m 就可以。继续优化,其实只用 2 到 之间的质数去除即可。上面生成大质数是很基础的方法,效率比较低,一般会采用更快的方式,比如随机生成一个 nbits 位的奇数 p,然后从 p 开始遍历之后的奇数,并通过 Miller–Rabin 质数判定算法对遍历到的数字进行质数判定,如果是质数,即可返回(当然,该算法有一定的误判率,不过在判定次数设置够大的情况下,误判率基本可以忽略)。
2)计算 p 和 q 的积 ,以及欧拉函数
3)选择一个整数 ,且 = 1,即 与 互质,在 openssl 中 固定为 65537。
[知识点]费马数
对于e,通常可选 3, 5, 17, 257 和 65537。因为它们都是质数,且二进制中只有2位是1,可以加快 d 的计算。这几个e的可选值实际是费马数的前5个数,费马数定义是 , 到 都是质数,但是从 开始就不是质数了。例如 。实际应用中通常选择 作为 e。
- 计算 e 对于 的模反元素 d。即找到整数d,1 < d < φ,且满足 。
根据扩展欧几里得算法,e 和 φ(n) 为已知量,可以求得一组解 d, k',其中 d 就是我们需要找的模反元素。
5)n 和 e 封装为公钥,n 和 d 封装为私钥。
- n:通常称为 modulus。
- e:通常称为 public exponent。
- d:通常称为 secret exponent。
6)定义好了公私钥,则对信息 ,加密和解密函数如下:
(公钥 n, e 加密)
(私钥 n, d 解密)
需要证明:
(1)
(2)
经过分析可以发现这两个式子证明是一样的,这里证明(1)即可。(1)常用于客户端公钥加密数据然后服务端私钥解密获取原始数据,而(2)则常用于服务端私钥加密签名数据,客户端公钥解密获取签名数据并校验签名。
2.2 实例分析
在证明之前,先简单验证下。假定我们选择 ,则
- ,可以求得一个 。
假定我们原始数据 m=16,则加密后数据为 61,对 61 解密,可以得到原始数据 16。
2.3 算法正确性证明
由上一节可知,我们要证明的是:
由模运算的幂性质,等式左边: ,即我们要证明的是:
由前面RSA算法构造条件知道 ,据此可设
先证明 m 和 n 互质这种特殊情况。因为 m 和 n 互质,由欧拉定理有 。由此推导如下:
(由欧拉定理和模运算的乘法结合律)
-
下面证明的是 m 和 n 不互质的情况,因为 n = pq,且 p 和 q都是质数,则 m 与 n 不互质只能是 p 或者 q 的倍数,不能同时为 p 和 q的倍数,因为 m < n。假设 m 是 q 的倍数,即 ,则 。可得 。
而因为 p 是质数,m 又不是 p 的倍数,故 p 和 m 互质。由费马小定理,可得到 :
(两边 q-1 的幂次)
(因为 )
(两边 k 次幂)
(两边乘以 m)由 以及 ,从而可得 ,因为 ,故 ,因为 ,根据性质 1.11,故而得证 。
注意: 虽然定义中 ,但是实际上对于 加密并没有效果,很容易从加密过程知道, 加密后都是它们自身。
2.4 RSA Key Pair的生成方法及代码实现
RSA的Key Pair的生成方法的伪代码如下:
INPUT: 模数 (即前面提到的N,N=pq) 的二进制位数 k
OUTPUT: RSA的密钥对 ((N,e),d),其中 N 是 模数, N=pq,其二进制长度不超过 k bit。 e 是选择的 public exponent,d 是 secret exponent。
1)首先就是找两个位数为 k/2 的大于0的大质数。在前面提到过,最朴素的方法是遍历正整数集,对于数 n,用 去除 n,但是这个方法的时间复杂度为 ,这是正整数 n 的长度的幂(因为 n 可以表示为 b 位的二进制数,则 ,故而 )。这个复杂度是比较高的,而我们不用遍历所有的整数,完全可以随机找一个大整数,然后判断该数是不是质数即可。令人高兴的是,判断一个数是不是质数比对一个数进行质数因子分解要容易很多。
由费马小定理知道,如果 n 是 质数,则对于任意的数 ,都满足
则可以知道如果对于 中如果有一个数 a 和 n 不满足 2.4.1,则 n 一定是合数。当然我们在实际中也不会将所有小于 n 的正整数都遍历来计算一遍,通常会随机选取一个 a 作为基数,比如选择 a=2,则满足费马小定理而又不是质数的 n ,我们称 n 为基于 a 的一个伪质数。比如 341 满足 ,但是 341 是一个合数。如果将基数a换成3,那也不能保证,对于 ,总有些合数(这些数也被称为 Carmichael数)满足2.4.1,如 561(a=2 满足),1105(a=2和a=3都满足)等。
为此,我们可以使用 Miller-Rabin 算法来解决判断质数问题。其主要思想就是选取 s 个 a 的随机数,然后判断奇数 n 是否是质数(偶数可以直接排除),如果其中任意一个 a 对应的 WITNESS(a, n) 为 true,则表示 n 肯定是合数。如果 s 次测试都通过,则可以近似判定 n 是 质数(误判的概率最大为 ,一般实际应用中选s=50就可以了)。
INPUT: (n, s),n > 2,n 是奇数,s 是测试次数
OUTPUT: COMOSITE 表示为合数,PRIME 则表示极可能是质数
// 肯定不是质数
// 极可能是质数
INPUT: (a, n) 其中 a 为随机选择的基数,n为待判定的奇数
OUTPUT: TRUE 表示肯定是合数, FALSE 表示可能是质数
而 WITNESS 就是根据 2.4.1 来判断 n 是否通过检测,不过做了一些优化,因为 n 是奇数,所以可以将 n-1分解为 ,设 ,则对 x 的结果平方 t 次,即可计算出 。所计算的序列 满足 。 如果前一个值 不等于 1 或者 n-1,但是当前值 ,则 n 一定是合数。证明如下:
正确性证明:
- 首先可以证明这么一个结论:若 a ,n为正整数,且 n 为质数,,则有 。
- 这是因为
或者
(因为 a > 0)- 下面就很容易证明 WITNESS了,因为 ,如果 n 是质数的话,则由 ,可以推出 一定是1 或者 n-1,否则 n一定是合数。
2)第二步就是计算 N=pq,然后计算欧拉函数 L,最后根据 e 和 欧拉函数 L 计算出 e 模除 L 的模拟元素 d 即可。
3)基于该算法,我写了个 RSA 密钥生成的 python 实现供大家参考,见 shishujuan:rsa-algrithm 。
2.5 提高 RSA 加解密效率
由前面分析知道 RSA 加解密效率严重依赖求幂和求模运算效率,为了减少大整数的求幂和求模的运算,可以对 RSA 模幂算法(modular exponentiation)进行一些优化。主要包括两方面,一是对模幂运算本身进行优化,提升运算效率。而是对RSA算法进行优化,基于中国剩余定理来提升RSA加解密效率。
加密:
解密:
2.5.1 提升模幂运算性能
模幂运算-朴素法
朴素法求模幂就是直接求幂然后模除,如下:
def pow_simple(a, e, n):
"""
朴素法模幂运算:a^e % n
"""
ret = 1
for _ in xrange(e):
ret *= a
return ret % n
当幂 b 很大时,这个方法的模幂运算就很慢,在我的机器上 a=5, e=102400, n = 13284 大概需要1秒左右的时间。我们知道 RSA 私钥中的 d 的值可是远大于 102400 的,如果这么慢显然效率上会有问题。
一种简单的优化方法基于下面这个性质,可以很方便的迭代实现,而且每次计算会将参数减小,优化后的模幂运算效率大概提升了100倍左右。
代码如下:
def pow_simple_optimized(a, e, n):
"""
朴素法模幂运算优化:基于 a ≡ c(mod n) => ab ≡ bc(mod n),
即 ab mod n = (b*(a mod n)) mod m
"""
ret = 1
c = a % n
for _ in xrange(e):
ret = (ret * c) % n
return ret
模幂运算-二进制法
虽然优化过的朴素方法的效率已经有了大幅提升,但是对于RSA私钥的超大的d值,仍然性能堪忧。比如当幂级数 e 大小为亿级别时,优化过的朴素方法在我的机器需要接近10秒的时间求模。一种广为应用的模幂方法就是二进制法,基于位运算效率会有惊人的提升,e为亿级别时模幂运算也只需要零点几毫秒。
对于一个n位的幂级数 e,我们可以写成如下二进制形式:
(其中 是最高位,是最低位)
从最低位 开始遍历 ,如果 ,则不影响模除结果,设置base值,继续处理下一位即可。在扫描到 时,有 ,根据模除的乘法结合律 ,可以知道下面算法是正确的。Python的内建函数 就是使用了类似的算法,因此在模幂运算时,使用 函数比 (直接幂运算跟优化过的朴素法性能相当)性能要高出几个数量级。
def pow_binary(a, e, n):
"""
right-to-left binary method:基于位运算模幂运算优化。
"""
number = 1
base = a
while e:
if e & 1:
number = number * base % n
e >>= 1
base = base * base % n
return number
完整的代码见 pow.py。
2.5.2 分解大整数提升模幂运算效率
从前面分析知道,通常使用过程中公钥的幂级数 e 的选取比较小,而且选取的是费马数,只有2位是1,其他位是0。而私钥的幂级数 d 很大,它的位数与模数 N 差不多,且有接近一半位是1。为了提高私钥 d 在模幂运算中性能,可以基于中国剩余定理(CRT)进行相关优化。
使用中国剩余定理提升私钥模幂运算效率(其中 代表 n 对模数 m 的模逆元素,即 )
这里多出来的 dP,dQ,qInv的值在使用 openssl 生成的 rsa 密钥中也有,其目的就是为了加速私钥模幂运算。以 2.2 中的例子为例,直接求 m 可得 ,而使用中国剩余定理优化后求解流程如下,亦可得到正确的结果 m = 16。
[证明]
由中国剩余定理知道,对于 这个方程组在 p 和 q互质时,则必然存在一个唯一解 。因为 , 因此对于任意的 ,必然存在唯一的数对 使之满足方程组。我们要计算的是 ,如果我们知道了 ,则由中国剩余定理知,必然存在一个唯一的值 与之对应。
由 1.12 中中国剩余定理的解的格式可知,若已知
,则有
可以发现这与前面计算 并不是直接用的 ,而是 ,这是为何呢?为什么会有
这个可以用欧拉定理证明:设 ,则有
由欧拉定理知道
于是有:
得证。
使用剩余定理优化的python实现参见 rsa.py 中的 decrypt_crt
函数。在我电脑上测试发现,优化后效率提升5倍左右。
2.6 如何破解 RSA?
从RSA加解密原理可知,如果要破解RSA,那就是知道 d 即可,由 可知,要知道 d,就需要 e 和 φ(n),e 是公开的,而 是未知的,想知道 φ(n) 就要知道 p 和 q 这两个大质数的值。我们知道 n 也是公开的,,所以只要能将 n 进行质数因式分解即可破解RSA算法。如果 N 是个小整数,那很好办,比如 N = 25777,知道 ,则可以从 161 开始找比它小的质数,然后判断是不是可以整除即可,很快可以得到 。
不过不要担心,大整数的质数因式分解还是比较难的。虽然除了暴力破解外,还有一些效率比较高的整数因式分解方法,如 二次筛分算法 和 普通数域筛选法(GNFS) 等,当你的RSA密钥位数在4096位以上,使用常规运算能力的电脑还是较难破解的。当然不排除某一天,数论研究出现了新的成果,若有一种通项公式可以分解大整数的话,那现有基于 RSA 的加密体系都会崩塌。此外,RSA 加密用的 e 不能太小,否则有潜在的风险,实际应用中常用 65537。
如果已知 N,e,d,则可以很高效地对 N 实现因式分解,原理就不赘述了,实现代码见 factor.py 。
在实际应用如HTTPS中,RSA 密钥因为性能原因以及没有前向安全性,通常并不直接用来加密数据,而是只用于在密钥交换时用于签名以认证身份,加密通常会基于 ECDHE 等算法协商一个密钥,然后再基于对称加密算法如 AES 等对数据加密传输。
3 RSA 密钥格式实例分析
本节对使用 openssl 等工具生成的 RSA 密钥格式进行简单分析,首先,使用 openssl 生成一对 RSA 密钥,如下:
# openssl genrsa -out rsa_demo.key 2048
# openssl rsa -in rsa_demo.key -pubout -out rsa_demo.pub
示例的私钥文件 rsa_demo.key,公钥文件 rsa_demo.pub。
最常用的 RSA 密钥的模式是 PKCS#1 中规定的模式,即公钥包括 N, e
,而私钥包括 N, e, d, p, q, dP, dQ, qInv
。查看公私钥文件内容,可以看到采用的是 DER(Distinguished Encoding Rules) 编码的,公钥解码后内容如下:
# grep -v '\-\-\-\-\-' rsa_demo.pub | tr -d '\n'|base64 -D|hexdump
0000000 30 82 01 22 30 0d 06 09 2a 86 48 86 f7 0d 01 01
0000010 01 05 00 03 82 01 0f 00 30 82 01 0a 02 82 01 01
0000020 00 bd e4 43 1a d0 02 1e e6 12 34 14 91 84 4d 65
......
0000120 b1 02 03 01 00 01
0000126
DER 编码是一种type-length-value编码,即包括:对象类型,数据长度域,数据域。示例的 RSA 公钥中, 0x30 是 Sequence
类型,0x03 是 Bit String
类型,而 0x02
是 Integer
类型,0x04
是字符串类型,0x06
是 Object ID
类型,0x05 00
表示NULL。长度如果>=128,则会先跟一个 0x81
或0x82
(0x81表示后面一个字节为长度,0x82表示后面两个字节为长度),然后是数据长度的值,没超过则就是长度值。整个结构是一个嵌套结构,公钥的 n 和 e 两个数值位于 BIT STRING
这块数据中,可以分析知道从 00000020
开始的 00bde4...b1
是 n,而从 0000123
开始的 010001
是 e,即 65537。另外,Object ID
是标识密钥的元信息的地方,对应的是 2a 86 48 86 f7 0d 01 01 01
这 9 个字节,这个代表什么含义呢?其实转义过来是 1.2.840.113549.1.1.1
,这个 OID 表示的是 RSA 加密系统的公钥。私钥格式类似,这里不再赘述,openssl 生成的私钥中包括了 p,q,dP,dQ,n,e,d,qInv
数据。
如果不想了解公私钥的格式,直接通过 openssl 的工具即可解析出公私钥的内容,容易验证,这些值正是基于前面的 RSA 原理计算得来的。
# openssl rsa -in rsa_demo.key -noout -text
Private-Key: (2048 bit)
modulus: # N
00:bd:e4:43:1a:d0:02:1e:e6:12:34:14:91:84:4d:
...
publicExponent: 65537 (0x10001) # e
privateExponent: # d
76:bb:8d:41:ec:a2:06:d3:f0:b9:e3:ca:81:21:2b:
...
prime1: # p
00:fa:99:34:b8:b3:97:dc:09:37:29:dd:df:de:86:
...
prime2: # q
00:c1:fc:13:f7:84:dd:f4:b5:05:2d:89:b9:a1:50:
...
exponent1: # dP
00:c9:7d:61:cc:98:6a:23:bb:2d:25:76:86:47:cf:
...
exponent2: # dQ
00:99:e8:43:7b:45:ea:c8:45:7b:57:37:07:95:ea:
...
coefficient: # qInv
0c:53:5f:0c:a6:7b:33:69:32:b6:b9:65:f8:31:5f:
...
# openssl rsa -pubin -in rsa_demo.pub -noout -text
Public-Key: (2048 bit)
Modulus: # N
00:bd:e4:43:1a:d0:02:1e:e6:12:34:14:91:84:4d:
...
Exponent: 65537 (0x10001) # e