前往小程序,Get更优阅读体验!
立即前往
首页
学习
活动
专区
工具
TVP
发布
社区首页 >专栏 >一文搞懂RSA算法原理及简单实现

一文搞懂RSA算法原理及简单实现

作者头像
BennuCTech
发布2021-12-17 19:05:51
1.5K0
发布2021-12-17 19:05:51
举报
文章被收录于专栏:BennuCTechBennuCTech

前言

RSA算法是最重要的算法之一,它是一种非对称加密,是目前最有影响力的加密方式之一。这篇文章我们通过实现一种简单的RSA加密来探究它的原理。

计算公钥和私钥

RSA中的公钥和私钥需要结合在一起工作。公钥用来对数据块加密,之后 ,只有对应的私钥才能用来解密。生成密钥时,需要遵循几个步骤以确保公钥和私钥的这种关系能够正常工作。这些步骤也确保没有实际方法能够从一个密钥推出另一个。

开始前,首先要选择两个大的素数,记为p和q。根据当今求解大数因子的技术水平,这两个数应该至少有200位,这们在实践中才可以认为是安全的。

然后,开始计算n:

n = pq

接下来,选择一个小的奇数e,它将成为公钥的一部分。选择e最需要考虑的重点是它与(p-1)(q-1)不能有相同的因子。换句话说,e与(p-1)(q-1)是互为素数关系的。比如,如果p=11而q=19,那么n=11 X 19=209。这里选择e=17,因为(p-1)(q-1)=10 X 18 =180,而17和180没有相同的因子。通常选择3、17、65、537作为e的值。使用这些值不会对RSA的安全性造成影响,因为解密数据还需要用到私钥。

一旦为e选择了一个值,接下来开始计算相对应的值d,d将成为私钥的一部分。d的值就是计算e的倒数对(p-1)(q-1)的取模结果,公式如下:

d = e-1 mod (p-1)(q-1)

这里d和e是模乘法逆元的关系。

思考一下这个问题:当d为多少时可以满足ed mod (p-1)(q-1) = 1 ?比如在等式 17d mod 180 = 1中,d的一个可能值是53。其他的可能值是233、413、593等。在实践中,可以利用欧几里德算法来计算模乘法逆元。这里就不再展开。

现在有了e和d的值,将(e,n)作为公钥P,将(d,n)作为私钥S并保持其不可见。

如何计算d?

上面p、q、e需要预设三个素数,n很容易求出来,但是d的计算就涉及到模的运算了

什么是模、取模和模运算? 取模:https://baike.baidu.com/item/%E5%8F%96%E6%A8%A1%E8%BF%90%E7%AE%97/10739384?fr=aladdin 模运算:https://baike.baidu.com/item/%E6%A8%A1%E8%BF%90%E7%AE%97/4376110 具体就不细说了,但是要注意取模和取余的区别

这里d = e-1 mod (p-1)(q-1)

简化为:

d = e-1 % m

这是乘法逆元的问题。我们对上面的进行处理

d * e = e-1 % m * e

(d * e) % m = (e-1 % m * e) %m

根据模运算的结合率 (a%p * b)%p=(a * b)%p

(d * e) % m = (e-1 % m * e) %m = (e-1 * e) % m = 1 % m

所以我们最后得到

(d * e) % m = 1 % m

这里由于n说我们自己定义的,一定是正数,所以1%n=1

所以最后变为计算

(d * e) % m = 1

并且e和d一定有一组解满足他们都小于m。我们只需求这组解即可。

根据费马小定理,如果a和b互质,则

ab-1 % b = 1

那么已经要求e与m互质,所以

(e * em-2) % m = 1

所以d的一个解是em-2,但是这个很可能比m大,则可以表示为m + k,那么

(e * (m + k)) % m = 1

根据模的加法运算规则

(em % m + e*k % m) % m = 1

因为em % m一定是0,所以上面的可以转为

e*k % m = 1

如果k还大于m,则重复上面的步骤直到k小于m。这时k就是d。

因为e小于m,所以d一定有一个小于m的解使 (d * e) % m = 1成立

代码

简单的算法是遍历找到d,代码:

代码语言:javascript
复制
var q = 13;
var p = 17;
var n = q * p;
var e = 7;
var tmp = (q - 1) * (p - 1);
var d;
for (d = 1; ; d++) {
    if (e * d % tmp === 1) {
        break
    }
}

这里还需要进行优化,因为一般n都是超大数,而e则比较小,所以d也会很大,这里就需要大量的循环,优化后如下:

代码语言:javascript
复制
    var d;
    for (var j = 1; j < e; j++) {
        if ((tmp * j + 1) % e === 0) {
            d = (tmp * j + 1) / e;
            break;
        }
    }

因为 (d * e) % m = 1也就是

d * e = k * m + 1

而且d也需要小于m,所以k一定小于e,而e是比较小的值,所以我们将循环改成k即可减少大量的计算。

加密和解密数据分组

要使用RSA算法对数据进行加密和解密,首先要确定分组的大小。为了实现这一步,必须确保该分组可以保存的最大数值要小于n的位数。比如,如果p和q都是200位数字的素数,则n的结果将小于400位。因而,所选择的分组所能保存的最大值应该要以是接近于400。在实践中,通常选择的位数都是比n小的2的整数次幂。比如,如果n是209,要选择的分组大小就是7位,因为27 = 128比209小,但28 = 256又大于209。

要从缓冲区M中加密第(i)组明文Mi ,使用公钥(e,n)来获取M的数值,对其求e次幂,然后再对n取模。这将产生一组密文Ci。对n的取模操作确保了Ci将和明文的分组大小保持一致。因而,要加密明文分组有:

Ci = Mie mod n

之前提到过,欧拉函数是采用幂模运算来加密数据的基础,根据欧拉函数及其推导式,能够将密文解密回原文。

要对缓冲区中C中的第(i)组密文进行解密,使用私钥(d,n)来获取Ci的数值部分,对其求d次幂,然后再对n取模。这将产生一组明文Mi。因此,要解密密文分组有:

Mi = Cid mod n

计算过程及优化

这里加密解密的算法一样,只不过key值不同而已,涉及的是模的幂运算

以加密为例

Ci = Mie mod n

因为需要考虑大数的问题,所以模的幂运算不能直接运算,比如如果我先直接计算Mie,由于Mi有可能是很大的数,这样它的e次幂就会是一个超级数字,计算机无法计算和存储

所以这里我们就需要对模幂运算进行优化,就涉及到了蒙哥马利算法 参考 https://blog.csdn.net/zgzczzw/article/details/52712980 https://blog.csdn.net/linraise/article/details/17490769)

蒙哥马利算法比较复杂,包含三个算法进行优化。

我们先设计一个简单的算法,先将模幂运算转化为模乘运算

关于模运算,有如下几个公式:

代码语言:javascript
复制
结合律

(a%p*b)%p=(a*b)%p

同理((a*b) % p * c)% p = (a*b*c) % p 


四则运算

(a * b) % p = (a % p * b % p) % p 

(a^b) % p = ((a % p)^b) % p

我们先利用上面两个结合律,所以:

a2%n = (a * a) %n = (a%n * a) %n

因为a%n取模一定比a小,所以a%n*a就要比a2小很多,类推

a3%n = (a2 * a) %n = ((a2%n)*a)%n

得出

an%n = ((an-1%n)*a)%n

这是一个简单递归算法,通过这个算法,每次乘完都会做一个取模运算,运算的数据就会小很多。

代码:

代码语言:javascript
复制
function encode(x, e, n) {
    var result = x % n;
    for (var i = 1; i < e ; i++) {
        result = (result * x) % n;
    }
    return result
}

function decode(x, d, n) {
    var result = x % n;
    for (var i = 1; i < d ; i++) {
        result = (result * x) % n;
    }
    return result
}

当然,上面仅仅是简单例子,因为如果幂数较大比如d就会是一个超大数,这样循环次数就会很多,计算时间很长。

根据模的运算法则:

(a % p * b) % p = (a * b) % p

(a * b) % p = (a % p * b % p) % p

我们可以得出,当指数(假设为e)是偶数时

se % n = ((se/2 % n) * (se/2 % n)) % n

当为奇数时则可以先转成偶数

se % n = ((se - 1 % n) * e) % n

这样就可以用二分法和位运算来优化算法。如下:

代码语言:javascript
复制
function modpow(x, p, m) {
    if(p === 1){
        return mod(x, m)
    }
    var mid;
    if((p & 1) === 0){
        mid = (p >> 1);
        var tmp1 =  modpow(x, mid, m);
        return mod(tmp1 * tmp1, m);
    }
    else{
        return mod(modpow(x, p - 1, m) * x, m)
    }
}

1、利用递归二分。因为开方所以每个节点的两个子节点都相等,所以计算其中一个就可以,这样我们只需计算二叉树的一条路径就可以了,整体复杂度只有O(log2n)。比如2n只需要计算n + x次(最多坏情况每次都是奇数则是2n),比2n次计算节省大量的时间,而且数据越大节省时间越多。

2、位运算。在判断奇偶数时,没有使用除法,因为除法运算复杂度很大,耗时比其他运算长很多。这里使用位运算,只需判断最低位是否为0即可,而除2运算则可以用右移一位代替。因为计算机中位运算最快,所以这样会节省大量的时间。

正确性验证

加密我们可以理解,因为运算中有模参与,所以不可逆。但是加密后为什么通过私钥就可以解密,解密一定正确么?

首先加密

k = se % n

然后对k解密

r = ((se % n)d) %n

根据(a^b) % p = ((a % p)^b) % p可得

r = (se)d % n = se*d % n

通过之前的计算可知 (d * e) % m = 1,而m是(q-1)(p-1),所以

d * e = k(q-1)(p-1) + 1 (k未知)

而且n=pq,所以

r = sk(q-1)(p-1) + 1 % (pq)

根据费马小定理,如果a和b互质,则

ab-1 = 1 mod b

所以考虑两种情况:

1、s与n即pq互质

因为p和q是两个大质数,所以s与n互质就相当于s分别于p和q互质

所以根据费马小定理

sp-1 = 1 mod p

sp-1 % p = 1

所以根据幂模的运算法则(a^b) % p = ((a % p)^b) % p

sk(q-1)(p-1) % p = (sp-1 % p)k(q-1) % p

因为sp-1 % p = 1,所以

sk(q-1)(p-1) % p = 1k(q-1) % p = 1

同理可以得出

sk(q-1)(p-1) % q = 1

所以sk(q-1)(p-1) - 1可以被p和q都整除,得出

sk(q-1)(p-1) % (pq) = 1

回到之前

r = sk(q-1)(p-1) + 1 % (pq) = (sk(q-1)(p-1) * s) % (pq)

根据模的结合率(a%p * b)%p=(a * b)%p

r = ( (sk(q-1)(p-1) % (pq)) * s ) % (pq)

上面推出sk(q-1)(p-1) % (pq) = 1,所以

r = s % (pq)

因为pq = n所以最终

r = s % n

根据上面RSA算法要求,可知s一定是小于n的数,所以s对n取模结果也是s

所以 r = s 验证了RSA的正确性。

2、s于n不互质

由于RSA算法要求,s一定小于n,且n是p和q这两个大质数的乘积,所以s一定是c * p或c * q

两种情况是一样的,我们验证一种即可,s = c * p

r = sk(q-1)(p-1) + 1 % (pq) = (c * p)k(q-1)(p-1) + 1 % (pq)

因为s小于n即pq,所以c小于q,又因为q是大质数,所以c与q一定互质

先抛开上面的等式,我们先看

(c * p)k(q-1)(p-1) + 1 % q

= ((c * p) * (c * p)k(q-1)(p-1)) % q

= ( ((c * p)k(q-1)(p-1) % q) * cp ) % q (根据模运算结合率)

到这一步,我们知道c和p都与q互质,那么cp也与q互质,再根据费马小定理和之前的推论可知

(c * p)k(q-1)(p-1) % q = 1

所以

(c * p)k(q-1)(p-1) + 1 % q = (c * p) % q

可得

(c * p)k(q-1)(p-1) + 1 = c * p + t * q

为什么?假设 (c * p)k(q-1)(p-1) + 1 % q = b (c * p) % q = b 那么 (c * p)k(q-1)(p-1) + 1 = x * q + b (c * p) = y * q + b 所以 (c * p)k(q-1)(p-1) + 1 - x * q = (c * p) - y * q (c * p)k(q-1)(p-1) + 1 = (c * p) + (x - y) * q = c * p + t * q

然后

(c * p)k(q-1)(p-1) + 1 % p

= (c * p + t * q) % p

= ( (c * p) % p + (t * q) % p ) % p (模的四则运算)

(c * p) % p一定为0。而且等式前面是0,因为(c * p)k(q-1)(p-1) + 1一定是p的倍数,所以

((t * q) % p ) % p = 0

(t * q) % p = 0 (因为(t * q) % p一定小于p)

因为p和q互质,所以t = r * p才能保证上面等式成立

所以

(c * p)k(q-1)(p-1) + 1 = c * p + t * q = c * p + r * p * q

那么回到开始

r = (c * p)k(q-1)(p-1) + 1 % (pq)

= (c * p + r * p * q) % (pq)

= ( (c * p) % (pq) + (r * p * q) % (pq) ) % (pq)

因为(r * p * q) % (pq) 一定等于 0,所以

r = ( (c * p) % (pq) ) % (pq) = ( (c * p) % n ) % n

因为c * p就是s,所以

r = (s % n) % n = s % n

这样就与上一种情况一样了,可以推出r = s,所以验证正确。

总结

从上面的分析可以看出,在RSA算法中模运算占据着重要的位置,整个过程中包含了模乘法逆元、费马小定理、蒙哥马利算法、欧拉函数等等数学知识,可以说非常复杂繁琐。好在目前都有现成的工具可以使用,大家不必了解其中原理就可以轻松生成密钥进行加解密,不过简单学习一下相关知识还是很有启发的。

本文参与 腾讯云自媒体同步曝光计划,分享自微信公众号。
原始发表:2021-12-17,如有侵权请联系 cloudcommunity@tencent.com 删除

本文分享自 BennuCTech 微信公众号,前往查看

如有侵权,请联系 cloudcommunity@tencent.com 删除。

本文参与 腾讯云自媒体同步曝光计划  ,欢迎热爱写作的你一起参与!

评论
登录后参与评论
0 条评论
热度
最新
推荐阅读
目录
  • 前言
  • 计算公钥和私钥
    • 如何计算d?
      • 代码
      • 加密和解密数据分组
        • 计算过程及优化
        • 正确性验证
          • 1、s与n即pq互质
            • 2、s于n不互质
            • 总结
            领券
            问题归档专栏文章快讯文章归档关键词归档开发者手册归档开发者手册 Section 归档