RSA算法的简单实现
前言
加密算法有两大类:对称加密和非对称加密
而非对称加密中应用最广泛的就是RSA算法,非对称加密可以用来解决互联网中的非接触式加密问题,即用以加密的密钥需要通过不安全的链路来传输,因此需要一种算法使中间人即使得到加密的密钥也无法解密,更无法伪造信息
算法逻辑
算法步骤
RSA算法安全性源于大数拆解因子的复杂度,公钥私钥加解密的实现和同余原理有关,算法实现的具体步骤如下:
-
产生两个大素数p,q
-
求N = p * q,和N的欧拉函数: Z = (p - 1) * (q - 1)
-
求公钥E,满足两个条件:1 < E < Z, gcd( E, Z) = 1
-
gcd表示欧几里得算法,用来求两个整数的最大公约数
-
gcd( E, Z) = 1,即E和Z的最大公约数为1,两者互质,例如3和17
-
-
求私钥D,满足两个条件:1 < D < Z, (E * D) mod Z = 1
-
简单实现可以用遍历来求,实际使用扩展欧几里得算法求
-
-
公钥和私钥满足:ED = 1(mod Z )
-
加解密表达式:
-
密文 =(明文 ^ E) mod N
-
明文 =(密文 ^ D) mod N
-
欧拉函数φ(n)
https://baike.baidu.com/item/%E6%AC%A7%E6%8B%89%E5%87%BD%E6%95%B0/1944850
φ(n) = 小于等于n的正整数中与n互质的数的个数
欧拉函数还有几个特性:
-
乘积的欧拉函数等于欧拉函数的乘积,即:φ(pq)=φ(p) * φ(q)
-
质数的欧拉函数是其自身减一,即:φ(p)=p-1
-
因此n= p*q有:φ(n)=φ(p) * φ(q)=(p-1) * (q-1)
如φ(7) = 6,φ(35)=φ(5) * φ(7)=(5-1) * (7-1) = 24
欧几里得和扩展欧几里得算法
https://blog.csdn.net/u014634338/article/details/40210435
欧几里得算法也叫辗转相除法,用来求两个数的最大公约数
function gcd(int $a, int $b): int
{
if ($b === 0)
return $a;
return gcd($b, $a % $b);
}
而扩展欧几里得算法主要用来:
-
求解不定方程;
-
求解模的逆元;
-
求解模线性方程(线性同余方程);
在RSA中的应用是求解模的逆元,选出公钥e后使用扩展欧几里得算法求出私钥d
/**
* 扩展欧几里得算法,用来求模的逆元;密钥符合(E * D) mod Z = 1
* 计算 ax + by = gcd(a,b) = 1中x,y的整数解(a与b互质)
*/
function extGcd(int $a, int $b): array
{
if ($b === 0)
// 返回[x,y]
return [1, 0];
}
[$x1, $y1] = extGcd($b, $a % $b);
$x = $y1;
$y = $x1 - intdiv($a, $b) * $y1;
return [$x, $y];
}
简单实现的话可以通过遍历求私钥d,其中$e和$z互质
function getSecretKey($p, $q, $e)
{
$z = ($p - 1) * ($q - 1);
$i = intdiv($z, $e);
while (($i * $e) % $z !== 1) {
$i++;
}
return $i;
}
注意要点:
-
由于加解密的表达式取决于指数,因此可以使用公钥加密私钥解密,或者私钥加密公钥解密两种方式
-
取幂次方和取余时,如果数很大不应直接计算,如 10240000^65537 mod 1553679,可以参考蒙哥马利算法求解大整数幂求模
-
生产环境中公钥指数e常取e=65537(费马数)
-
明文要求小于密钥n,因为加解密时需要mod n;如下例中的密钥n=35,则明文 m < 35(若长度表示bit也成立)
代码实现
简单版
https://blog.csdn.net/a745233700/article/details/102341542
https://blog.csdn.net/bian_h_f612701198412/article/details/79358771
https://www.zhihu.com/question/304030251/answer/543201982
<?php
declare(strict_types=1);
/**
* 扩展欧几里得算法,用来求模的逆元
* 计算 ax + by = gcd(a,b) = 1中x,y的整数解(a与b互质)
*/
function extGcd(int $a, int $b): array
{
if ($b === 0) {
// 返回[x,y]
return [1, 0];
}
[$x1, $y1] = extGcd($b, $a % $b);
$x = $y1;
$y = $x1 - intdiv($a, $b) * $y1;
return [$x, $y];
}
/**
* 获取密钥对,(n, e, d)
*/
function getKey(int $p, int $q): array
{
// 公钥,这里为简便取5,实际生产环境中常用65537(费马数)
$e = 5;
$n = $p * $q;
// n的欧拉函数,φ(n) = 小于等于n的正整数中与n互质的数的个数;可以由下式得出
$z = ($p - 1) * ($q - 1);
[$x, $y] = extGcd($e, $z);
$d = ($x < 0) ? ($x + $z) : $x;
return [$n, $e, $d];
}
/**
* RSA加密解密;当指数为公钥时定义为加密,私钥时定义为解密
* 先求指数再取余的方式有很大局限性,指数运算后很容易超出整型范围
*/
function RSA(int $m, int $n, int $exponent): int
{
return pow($m, $exponent) % $n;
}
[$n, $e, $d] = getKey(5, 7);
print_r([$n, $e, $d]);
for ($i = 0; $i < 12; $i++) {
$secret[$i] = RSA($i, $n, $d);
$message[$i] = RSA($secret[$i], $n, $d);
}
var_dump($secret, $message);
// 输出
Array
(
[0] => 35
[1] => 5
[2] => 5
)
(
[0] => 0
[1] => 1
[2] => 32
[3] => 33
[4] => 9
[5] => 10
[6] => 6
[7] => 7
[8] => 8
[9] => 4
[10] => 5
[11] => 16
)
Array
(
[0] => 0
[1] => 1
[2] => 2
[3] => 3
[4] => 4
[5] => 5
[6] => 6
[7] => 7
[8] => 8
[9] => 9
[10] => 10
[11] => 11
)
小结
-
以上就是RSA算法的原理以及简单实现,而实际应用RSA算法时,密钥长度n往往较大,取1024bit以上,因为太短的密钥不够安全
-
我们常说的签名和加密其实本质运算是一样的,根据指数用公钥e还是私钥d来决定;
-
加密指的是使用对方的公钥进行RSA运算,保证只有对方的私钥能够解密,确保信息不会泄露;
-
签名指的是使用自身的私钥进行RSA运算,保证只有自己的公钥能够解密,确保信息不被伪造;
-
我们平时使用的RSA密钥都是转码成base64格式保存的,这是为了方便传输和阅读,可以使用以下命令查看RSA私钥的格式,如下图所示的私钥,采用的是ASN.1格式保存,密钥文本保存的格式将在下一篇中讲解
openssl rsa -text -noout < ~/.ssh/id_rsa