文章目录
RSA数学原理
获取两个不相等的质数pq
质数又称素数,在自然数中,除了1和自身外,不能被其他自然数整除。比如10以内的质数有:1,2,3,5,7。
把p和q相乘,得到n
比如:n=61*53=3233,用二进制表示为:110010100001。
我们常说的RSA算法中的多少位,就是n用二进制表示后的位数,在我们例子就是12位。目前商用中一般都是2048位。
![](https://raw.githubusercontent.com/1345414527/-/master/assets/image-20220911155815334.png)
计算出小于n的自然数中,有多少数与n互质(欧拉函数)
如果两个数的最大公约数为1,那么我们说这两个数互质,记:GCD(a,b)=1。其中GCD表示两个数的最大公约数。
使用欧拉函数来判断是否互质:
-
情况1:如果n=1,那么与n互质的自然数只有1
φ ( n ) = 1 φ(n)=1 φ(n)=1 -
情况2:如果n是质数,那么与n互质的自然数有n-1个,
φ ( n ) = n − 1 φ(n)=n-1 φ(n)=n−1
例:φ(7)=6例:φ(7)=6 -
情况3:如果n可以因式分解为两个互质数的乘积,则:
φ ( n ) = φ ( p ) × φ ( q ) = ( p − 1 ) × ( q − 1 ) 例: φ ( 56 ) = φ ( 7 ) ∗ φ ( 8 ) = 6 ∗ 7 = 42 φ(n)=φ(p)\times φ(q)=(p-1)\times (q-1) \\ 例:φ(56)=φ(7)*φ(8) = 6 * 7 = 42 φ(n)=φ(p)×φ(q)=(p−1)×(q−1)例:φ(56)=φ(7)∗φ(8)=6∗7=42 -
情况4:如果n可以写成某个数的质数次幂(其中k为质数),则:
φ ( n ) = φ ( p k ) = p k − p k − 1 = p k ( 1 − 1 p ) 例: φ ( 49 ) = φ ( 7 2 ) = 7 2 − 7 1 = 42 φ(n)=φ(p^k)=p^k-p^{k-1}=p^k(1-\frac 1p) \\ 例:φ(49)=φ(7^2)=7^2 - 7^1 = 42 φ(n)=φ(pk)=pk−pk−1=pk(1−p1)例:φ(49)=φ(72)=72−71=42 -
情况5:根据以上规律,总结出一个通用的公式:
n
=
p
1
k
1
×
p
2
k
2
…
p
r
k
r
注:任意一个整数,都可以写成两个质数的乘积
\qquad n=p_1^{k^1} \times p_2^{k^2} \ldots p_r^{k^r} \quad 注:任意一个整数,都可以写成两个质数的乘积
n=p1k1×p2k2…prkr注:任意一个整数,都可以写成两个质数的乘积
⇓
\qquad \Downarrow
⇓
φ
(
n
)
=
φ
(
p
1
k
1
)
×
φ
(
p
2
k
2
)
…
φ
(
p
r
k
r
)
\qquad φ(n)=φ(p_1^{k^1}) \times φ(p_2^{k^2})\ldots φ(p_r^{k^r})
φ(n)=φ(p1k1)×φ(p2k2)…φ(prkr)
⇓
\qquad \Downarrow
⇓
φ
(
n
)
=
p
1
k
1
(
1
−
1
p
1
)
×
p
2
k
2
(
1
−
1
p
2
)
…
p
r
k
r
(
1
−
1
p
r
)
\qquad φ(n)= p_1^{k^1}(1- \frac 1p_1) \times p_2^{k^2}(1- \frac 1p_2) \ldots p_r^{k^r}(1- \frac 1p_r)
φ(n)=p1k1(1−p11)×p2k2(1−p12)…prkr(1−p1r)
⇓
\qquad \Downarrow
⇓
φ
(
n
)
=
p
1
k
1
×
p
2
k
2
…
p
r
k
r
×
(
1
−
1
p
1
)
×
(
1
−
1
p
2
)
…
(
1
−
1
p
n
)
\qquad φ(n)=p_1^{k^1} \times p_2^{k^2}\ldots p_r^{k^r}\times (1- \frac 1p_1)\times(1- \frac 1p_2)\ldots(1- \frac 1p_n)
φ(n)=p1k1×p2k2…prkr×(1−p11)×(1−p12)…(1−p1n)
⇓
\qquad \Downarrow
⇓
φ
(
n
)
=
n
×
(
1
−
1
p
1
)
×
(
1
−
1
p
2
)
…
(
1
−
1
p
n
)
\qquad φ(n)=n\times(1- \frac 1p_1)\times(1- \frac 1p_2)\ldots(1- \frac 1p_n)
φ(n)=n×(1−p11)×(1−p12)…(1−p1n)
- 总结:通过欧拉函数最后的通式,我们发现最后的结果只和n和p有关,和p的幂无关,这点很重要,在我们用程序实现时,能够大大的简化我们的逻辑代码。
回到算法中,我们需要计算与n互质的个数,也就是求φ(n),根据欧拉函数,计算过程如下:
φ
(
n
)
=
φ
(
3233
)
=
φ
(
61
)
×
φ
(
53
)
=
60
×
52
=
3120
φ(n)=φ(3233)=φ(61)×φ(53)=60×52=3120
φ(n)=φ(3233)=φ(61)×φ(53)=60×52=3120
在1和φ(n)之间,选取一个随机质数e
即在1~3120中选取e=17。
求e和φ(n)的模反元素d(裴蜀定理、扩展欧几里得算法)
模反元素也称为模倒数
,可以写成
a
−
1
≡
b
(
m
o
d
n
)
a^{−1}≡b \ (mod \ n)
a−1≡b (mod n) 或
a
b
≡
1
(
m
o
d
n
)
ab≡1 \ (mod \ n)
ab≡1 (mod n)。(ab和1关于n同模)
如果两个正整数a和n互质,那么一定可以找到整数b,使得a*b与n相除,余数为1,记作: ( a × b ) % n = 1 ⇒ ( a × b ) − 1 n = 1 (a \times b) \% n = 1 \Rightarrow \frac {(a \times b) - 1} n = 1 (a×b)%n=1⇒n(a×b)−1=1。
例:求3和11的模反元素
(
3
×
b
)
−
1
11
=
1
=
>
b
=
4
\frac {(3 \times b) - 1} {11} =1 => b = 4
11(3×b)−1=1=>b=4
- 通过上面的运算,我们可以算出一些简单数的模反元素,对于较大的数来说,我们需要引入新的计算工具:裴蜀定理,通过它,我们可以通过一个二元一次方程来得出模反元素。在求模反元素时,给予二个整数a、b,必存在整数x、y使得ax + by = gcd(a,b),而我们真正要获取的是x的值。
- 定义:如果a与b互质,即GCD(a, b) = 1,二元一次方程 a x + b y = 1 ax + by = 1 ax+by=1有一对正整数解,其中x即为a、b的模反元素。同理,上面的例子我们可以化简成这样:3x + 11y = 1
- 疑惑:对于二元一次方程,好像不可解(也可以说有无穷多个解),我们之前都是解方程组。即然定理已经解决,两个互素(质)数组成的二元一次方程有一对整数解,那肯定是能解,解法我们需要引入另一个数学工具:扩展欧几里得算法
扩展欧几里得算法这个算法其实就是上面我们求最大公约数时,用到的辗转相除法+它的逆运算,我们看个例子就明白是什么意思了
例1:求 47 x + 30 y = 1 47x + 30y = 1 47x+30y=1的解
解:使用辗转相除法,我们可以得到:
47
=
30
×
1
+
17
30
=
17
×
1
+
13
17
=
13
×
1
+
4
13
=
4
×
3
+
1
4
=
1
×
4
+
0
47 = 30 \times 1 + 17 \ \\ 30 = 17 \times 1 + 13 \ \\ 17 = 13 \times 1 + 4 \ \ \ \\ 13 = 4 \times 3 + 1 \ \ \ \ \ \\ 4 = 1 \times 4 + 0 \ \ \ \ \ \
47=30×1+17 30=17×1+13 17=13×1+4 13=4×3+1 4=1×4+0
我们可以得到GCD(47, 30) = 1,对到处第二行,我们移项处理:
1
=
13
−
4
×
3
4
=
17
−
13
×
1
13
=
30
−
17
×
1
17
=
47
−
30
×
1
1 = 13 - 4 \times 3 \\ 4 = 17 - 13 \times 1 \\ 13 = 30 - 17 \times 1 \\ 17 = 47 - 30 \times 1 \\
1=13−4×34=17−13×113=30−17×117=47−30×1
我们把第二行代入第一行中:
1
=
13
−
(
17
−
13
×
1
)
⏞
4
×
3
⇓
1
=
4
×
13
−
3
×
17
⇓
1
=
4
×
(
30
−
17
)
⏞
13
−
3
×
s
17
⇓
…
1
=
(
−
7
)
×
47
+
11
×
30
解得:
x
=
−
7
(
即为我们要求的模反元素
d
)
y
=
11
1 = 13 - \overbrace{(17 - 13 \times 1)}^4 \times 3 \\ \Downarrow \\ 1 = 4 \times 13 - 3 \times 17 \\ \Downarrow \\ 1 = 4 \times \overbrace{(30 - 17)}^{13} - 3 \times s17 \\ \Downarrow \\ \ldots \\ 1 = (-7) \times 47 + 11 \times 30 \\ 解得:x=-7(即为我们要求的模反元素d) \quad y = 11
1=13−(17−13×1)
4×3⇓1=4×13−3×17⇓1=4×(30−17)
13−3×s17⇓…1=(−7)×47+11×30解得:x=−7(即为我们要求的模反元素d)y=11
回到算法中,我们根据e=17和φ(n)=3120,得到一个二元一次方程:
17
x
+
3120
y
=
1
17x + 3120y = 1
17x+3120y=1,再根据扩展欧几里得算法,我们可以得到方程的解:
x
=
2753
即:
d
=
2753
x = 2753 \quad 即:d = 2753
x=2753即:d=2753。
把n和e封装成公钥,n和d封装成私钥
- 公钥: ( 3233 , 17 ) (3233, 17) (3233,17)
- 私钥: ( 3233 , 2753 ) (3233, 2753) (3233,2753)
实例
私钥和公钥获取:
-
取p=47, q=71;
-
则n = p * q = 3337, Φ(n)=(p-1)(q-1)=3220;
-
随机选择加密密钥e,e与Φ(n)互素,取e=79;
-
79 * d=1(mod n) =》 d = 10 1 9 101^9 1019;
-
公钥为(3337,79),私钥为(3379,1019)
假设要加密的明文是m=6882326879666683:
- 首先,根据n的大小将m进行分组,这里我们把明文m分成六个组,即:m1=688, m2=232, m3=687, m4=966, m5=668, m6=003;
- 接着分别对各个分组进行加密运算,第一个分组加密为c1= 68 8 79 688^{79} 68879(mod 3337) = 1570,类似的,对其余各个分组分别进行加密运算,得到如下密文:c=1570 2756 2091 2276 2423 158;
- 解密时用私钥1019分别对明文进行解密运算,即:m1= 157 0 1019 1570^{1019} 15701019(mod 3337) = 688,对其余的密文用同样的计算方法就可以把密文恢复出来,即得到密文。
RSA参数的选择
p和q的选择:
-
p和q要足够大
-
p和q应为强素数。如p满足以下三个条件,即为强素数:
(1)P-1有大素数因子r
(2)P+1有大素数因子s
(3)R-1有大素数因子t
-
p和q的差不能太小
-
p-1和q-1的最大公因数应很小
公钥e的选择:e不能太小;最好选择e为modΦ(n)的阶数,意思就是要使i的值尽量大才能使得ei≡ (mod Φ(n))成立。i等于(p-1)(q-1)/2是最好的。一般建议取e= 2 16 2^{16} 216+1=65537
代码
(1)质数判断
import Gcd from './Gcd'
/**
* 质数类
*/
class Prime {
private static pNumArr: Array<number> = []
/**
* 判断一个数是否为质数
* @param num 转入值
*/
public static isPrimeNum(num: number): boolean {
// 1和2都是质数
if (num < 3) {
return true
}
// i取开方,效率要比/2高出许多
for (let i = 2; i <= Math.sqrt(num); i++) {
if (num % i === 0) {
return false
}
}
return true
}
/**
* 1. 任意两个质数构成互质关系,比如13和61。
2. 一个数是质数,另一个数只要不是前者的倍数,两者就构成互质关系,比如3和10。
3. 如果两个数之中,较大的那个数是质数,则两者构成互质关系,比如97和57。
4. 1和任意一个自然数是都是互质关系,比如1和99。
5. p是大于1的整数,则p和p-1构成互质关系,比如57和56。
6. p是大于1的奇数,则p和p-2构成互质关系,比如17和15。
*/
public static coprime(n1: number, n2: number): boolean {
// 保证n1比n2要大
if (n2 > n1) {
let tmp = n1
n1 = n2
n2 = tmp
}
// 两个数中, 有一个是1, 则两数互质
if (n1 === 1 || n2 === 1) {
return true
}
// 如果两数相邻, 则两数互质
if (n1 - n2 === 1) {
return true
}
// 如果大数为奇数, 则n1-n2 = 2, 则两数互质
if (n1 % 2 === 1 && n1 - n2 === 2) {
return true
}
// 如果较大的数为质数, 则两数互质
if (this.isPrimeNum(n1)) {
return true
}
// 有一个是质数, 别一个不为此数的倍数, 则两数互质
if ((this.isPrimeNum(n1) || this.isPrimeNum(n2)) && n1 % n2 !== 0) {
return true
}
// 如果两个数都为质数, 则两个数互质
if (this.isPrimeNum(n1) && this.isPrimeNum(n2)) {
return true
}
// 如果两个数的最大公约数为1, 则两个数互质
if (Gcd.gcd(n1, n2) === 1) {
return true
}
return false
}
/**
* 获取该数下所有的质数
* @param num 转入的数
*/
public static getAllPrimeNum(num: number) {
let arr = []
for (let i = 1; i <= num; i++) {
if (this.isPrimeNum(i)) {
arr.push(i)
}
}
return arr
}
/**
* 把一个正整数分解为两个质数的乘积
* @param num
*/
public static break(num: number) {
this.getBreakArr(num)
return this.pNumArr
}
/**
* 把一个正整数分解为两个质数的乘积(中间步骤)
* @param num
*/
private static getBreakArr(num: number) {
// 获取该数/2的所有质数
let arr = this.getAllPrimeNum(Math.floor(num / 2))
// 能被这个数整除的最大质数
let maxNum = 1
for (let i = 0; i < arr.length; i++) {
if (num % arr[i] === 0) {
maxNum = arr[i]
}
}
// 除数
let divNum = num / maxNum
if (maxNum !== 1) {
this.pNumArr.push(maxNum)
return this.break(divNum)
} else {
this.pNumArr.push(divNum)
return maxNum
}
}
}
export default Prime
(2)欧拉函数计算
import Prime from './Prime'
import Util from './Util'
/**
* 欧拉函数
*/
class Euler {
/**
* 根据欧拉函数取与此数互质的个数
* @param num 参数
*/
public static getEulerNum(num: number) {
Prime.break(num)
let arr = Util.uniqueArr(Prime.break(num))
let result = num
for (let i = 0; i < arr.length; i++) {
result *= 1 - 1 / arr[i]
}
return Math.floor(result)
}
}
export default Euler
(3)最大公约数计算-欧几里得算法-辗转相除法
/**
* 最大公约数
*/
class Gcd {
/**
* 计算2个数的最大公约数
* @param n1 数1
* @param n2 数2
*/
public static gcd(n1: number, n2: number): number {
let s = Math.floor(n1 / n2)
let y = n1 % n2
// console.log(n1 + ' = ' + n2 + '*' + s + ' + ' + y)
if (y === 0) {
return n2
} else {
return this.gcd(n2, y)
}
}
/**
* 扩展欧几里德算法 - 求方程的解 (详细注释版)
* 我们要求:47x + 30y = 1的解,下面为辗转相除法的步骤:
* 47 = 30*1 + 17
* 30 = 17*1 + 13
* 17 = 13*1 + 4
* 13 = 4*3 + 1
*
*
* @param num1 数1
* @param num2 数2
*/
public static gcdEx(n1: number, n2: number): Array<number> {
// 两数相除的商
let q = Math.floor(n1 / n2)
// 两数相除的余数
let r = n1 % n2
// 当到 13 = 4*3 + 1 即4 % 1 = 0时 返回
// 返回值为:1 | -4 即:x=1 y=-4
if (n2 % r === 0) {
// 转化为:1 = 13 + 4*(-3)
// 所以返回:x=1 y=q*(-1)=-3
return [1, q * -1]
} else {
let ret = this.gcdEx(n2, r)
// x和y为上一步返回的值
let x = ret[0]
let y = ret[1]
// x1和y1为当前步骤的值
let x1 = 1
let y1 = -q
// console.log('----', x, y, x1, y1)
// x2和y2为当前步骤计算得到的值
// 1 = 13 + 4*(-3)
// 4 = 17 + 13*(-1)
// 代入得:1 = 13 + (17 + 13*(-1)) * (-3)
// 计算得:1 = 17*(-3) + 13*4 ==> x=-3 | y=4
let x2 = y * x1
let y2 = y * y1 + x
ret = [x2, y2]
return ret
}
}
}
export default Gcd
(4)裴蜀定理的求解-扩展欧几里得算法
/**
* 最大公约数
*/
class Gcd {
/**
* 计算2个数的最大公约数
* @param n1 数1
* @param n2 数2
*/
public static gcd(n1: number, n2: number): number {
let s = Math.floor(n1 / n2)
let y = n1 % n2
// console.log(n1 + ' = ' + n2 + '*' + s + ' + ' + y)
if (y === 0) {
return n2
} else {
return this.gcd(n2, y)
}
}
/**
* 扩展欧几里德算法 - 求方程的解 (详细注释版)
* 我们要求:47x + 30y = 1的解,下面为辗转相除法的步骤:
* 47 = 30*1 + 17
* 30 = 17*1 + 13
* 17 = 13*1 + 4
* 13 = 4*3 + 1
*
*
* @param num1 数1
* @param num2 数2
*/
public static gcdEx(n1: number, n2: number): Array<number> {
// 两数相除的商
let q = Math.floor(n1 / n2)
// 两数相除的余数
let r = n1 % n2
// 当到 13 = 4*3 + 1 即4 % 1 = 0时 返回
// 返回值为:1 | -4 即:x=1 y=-4
if (n2 % r === 0) {
// 转化为:1 = 13 + 4*(-3)
// 所以返回:x=1 y=q*(-1)=-3
return [1, q * -1]
} else {
let ret = this.gcdEx(n2, r)
// x和y为上一步返回的值
let x = ret[0]
let y = ret[1]
// x1和y1为当前步骤的值
let x1 = 1
let y1 = -q
// console.log('----', x, y, x1, y1)
// x2和y2为当前步骤计算得到的值
// 1 = 13 + 4*(-3)
// 4 = 17 + 13*(-1)
// 代入得:1 = 13 + (17 + 13*(-1)) * (-3)
// 计算得:1 = 17*(-3) + 13*4 ==> x=-3 | y=4
let x2 = y * x1
let y2 = y * y1 + x
ret = [x2, y2]
return ret
}
}
}
export default Gcd