RSA 是公钥密码体制,至今也仍在众多协议如 TLS1.2 等中使用。因此其算法本身仍具有一定的安全强度。但是,实际应用中却存在诸多误用情况,从而导致被 RSA 加密的机密信息被破解、泄露。
不过在介绍 RSA 各种攻击场景之前,我们还是要简单了解一下RSA公钥加密体制。
RSA公钥加密体制
我们假设接受消息的人是Alice,发送消息的人是Bob
参数生成
Alice 随机生成两个大素数 (大于等于1024比特),计算 ,然后选择公钥 (一般为 65537),需满足 ,并生成相应的私钥 。总结一下,分别是
随后,Alice 将公钥对发送给 Bob,用于加密计算。
加密计算
Bob 将消息 编码成数字 ,随后使用公钥对 计算密文 ,然后 Bob 将密文 发送给 Alice。
解密计算
Alice接收到密文 后,使用私钥对 计算明文
解密算法正确性的证明
等式左边为
等式右边为 ∵
∴
∵
∴
∴
∵
∴
∴
∵ $m <n$< p=””></n$<>
∴
∴
应用示例
Alice 选择了两个素数 (实际上Alice应该选择更大的素数,但这里为了方便举例,我们选择了两个很小的素数),选择了公钥 ,满足 ,计算私钥 。因此有公钥对 ,私钥对
Bob 加密信息 ,于是他计算 ,得到密文并发送给Alice
Alice 使用私钥解密,计算 ,成功解密获得明文。
分解模数
我们注意到,想要解密一段密文的关键是获得私钥 ,而计算私钥 依赖式子 ,由于公钥是公开已知的,因此我们只要知悉 即可求取私钥的。而 ,其中模数 ,并且由于 均为素数,因此 有唯一的因式分解(除去 1 和 n 本身) 。于是,解密 RSA 密文 这个问题就可以规约到如何分解模数 这个问题上来。
当模数 较小时,我们可以通过蛮力爆破的方式,遍历小于 的所有数字来寻找 可能的因子。然而,当模数变得很大时(例如有两千比特,大约为 ),即使用已知最快的分解 的算法在有限时间内也是无法求解。RSA 正是基于这样的 大整数分解问题 来保证其安全性。
不过,准确的来说,大整数分解问题其实是一个平均困难问题 ,即大多数时候, 的分解时困难的,但是不免在有些“极端”情况 的分解是简单的。比如我们前面提到的 足够小,或者 有特殊的形式时。那么这这一篇文章主要就是给大家介绍一下一些模数 可以被分解的场景以及对应的算法。
模数因子相差过小
一般情况下,参数 都是随机生成,然后一同乘积得到 ,然而,如果 之间相差的过于小,甚至更极限, 是两个相邻的素数,那么 就很容易遭到分解。我们用一个简单的不等式就能说明:
因此,我们只需要对 进行开根,就能够得到 之间的一个数。根据素数定理,两个素数之间不会间隔的很大,于是我们从这个数分别往前遍历和往后遍历就能够找到素数 和素数 。
两个模数有公因子
如果存在两个模数 ,设有三个素数 ,并且 。如果 的长度足够,那么这两个模数单独取出来均是无法分解的。但如果它们同时出现,那么我们利用欧几里得辗转相除法( )即可在线性时间内得到其公约数 ,即分解了 。
使用python我们可以使用简洁的代码就编写出 函数,下面给出示例
def gcd (a,b) : while b != 0 : a,b = b,a%b return a
已知公私钥分解模模数
这是一个很经典的关系,在知道 RSA 公钥的前提下,计算出RSA 的私钥 和 知道模数 的分解 是充要条件。即如果知道 的分解,我们按照正常参数生成的流程就能够利用公钥计算出私钥;而如果我们知道私钥 ,我们也能够利用以下的方法,分解模数 。
当给定私钥 ,我们首先计算 。根据 和 的定义我们知道 是 的倍数。由于 肯定是偶数,则其可以写为 ,其中 为奇数且 。根据欧拉定理,对于每个 ? 都会有 ,因此 就是模乘单位元 模 的平方根。根据中国剩余定理, 在模 下会有四个平方根。其中两个平方根是 ,另外两个是 ,而 则分别满足 和 或者 和 。因此当结果不为 时,我们可以通过计算 来揭示 的因式分解。一个直截了当的论证表明,如果从 ? 中随机选择 ,那么序列中的一个元素 不为 的概率大约为 。而序列中的所有元素可以在时间 内有效地计算,其中 。
from random import *def gcd (a,b) : while b != 0 : a,b = b,a%b return adef factor_n_with_ed (n,e,d) : p = 1 q = 1 while p == 1 and q == 1 : k = e*d - 1 g = randint(0 ,n) while p==1 and q==1 and k % 2 == 0 : k //= 2 y = pow(g,k,n) if y!=1 and gcd(y-1 ,n)>1 : p = gcd(y-1 ,n) q = n//p return p,q
接下来,我们介绍一种当模数 的一个因子 具有特殊构造(即 光滑)时的一种分解 的算法。
Pollard’s p−1 method
我们有一个整数 ,假设通过运气、爆破或其他方法,我们找到了一个具有以下性质的整数 ,
考虑一下如果我们随机选择一个整数 并计算 会发生什么?根据费马小定理,我们知道
由于指数 ,所以 不太可能在模 下与 1 同余。因此随机选择一个整数 ,我们有很大的可能发现
这就意味着我们用简单的 算法就可以分解出 来:(可以意识到 算法在整数分解方面的用处很广)
这一切都很顺利,但你可能会问,我们在哪里可以找到这样一个指数 ,它可以被 整除,而不被 整除。 Pollard 观察到,如果 是由一系列小素数乘积而来,那么它就能整除 (主要针对那些不太大的 ,否则也算不出它的阶乘)。那么想法出现,对于 ,我们随机选择一个整数 ,并计算
(实际操作中我们通常简单的固定 )如果 算法的结果是 ,那么我们继续用下一个 ,如果 gcd 的结果是 ,那么我们就不太幸运了,不过兴许换一个 就能够成功。如果我们得到了一个大于 小于 的整数,那么就意味着我们找到了 的一个非平凡因子,算法结束。
在我们准备用代码实现 Pollard‘s p-1method ’之前,这里有两个需要注意的点。
第一,我们需要关注 值的大小。尽管 ,我们也选一个大小适中的 ,我们直接计算 的值也是不太适合。因为 的十进制位长高达 ,这比宇宙中已知基本粒子的数量还要多。幸运的是,我们并不需要准确的计算出它,这里我们只关注 和 的最大公因子,因此计算 就足够了,然后我们再计算其与 的 gcd。因此我们实际上不需要处理比 大的数字。
第二,我们甚至不需要去计算指数 。假设我们在上一步已经计算好了 ,那么我们可以通过以下的式子计算下一个值
应用示例
我们使用 Pollard’s p−1 method 去分解 ,从 开始,然后连续增大指数,计算阶乘,我们发现
之所以我们在 成功,是因为这里我们 可以分解为如下小素数的乘积:
对于另外一个因子有 ,并不是由小(对于这里来说)因子乘积而来。
williams p+1 algorithm
选择一个大于 2 的整数 ,以下是Lucas序列:
然后,当 是 的倍数时,任何奇素数 都会整除 ,其中 是雅各比符号。
我们要求 = ,也就是说, 是模 的二次非剩余。但是由于我们事先不知道 ,因此在找到解之前可能我们需要不止尝试一个 值。如果 ,则该算法退化为慢版本 Pollard p − 1算法。
因此,对于不同的 ,我们计算 ,当结果不等于 或 时,我们则计算出了 的非平凡因子。
所使用的 的值是连续的阶乘,并且 是由 生成的序列的第 个值。
为了找到序列的第 个元素,我们以类似于从左到右求幂的方式进行,下面给出伪代码
x := B y := (B ^ 2 − 2) mod N for each bit of M to the right of the most significant bit do if the bit is 1 then x := (x × y − B) mod N y := (y ^ 2 − 2) mod N else y := (x × y − B) mod N x := (x ^ 2 − 2) mod N V := x
应用示例
V1 of seq(5) = V_1! of seq(5) = 5 V2 of seq(5) = V_2! of seq(5) = 23 V3 of seq(23) = V_3! of seq(5) = 12098 V4 of seq(12098) = V_4! of seq(5) = 87680 V5 of seq(87680) = V_5! of seq(5) = 53242 V6 of seq(53242) = V_6! of seq(5) = 27666 V7 of seq(27666) = V_7! of seq(5) = 110229.
小结
上面我们大致介绍了几种可以分解模数 的场景,大部分都是针对具有特殊非平凡因子的模数 。但其实分解模数的算法还远远不止于此,还有费马分解、已知最快的筛法,包括二次筛法、数域筛法等,详情可以阅读我们公众号【山石网科安全研究院】的密码学板块文章 《29. 3.7 Smooth Numbers Sieves Building Relations for Factorization》
原文始发于微信公众号(山石网科安全技术研究院):RSA专题——模数分解