【philippica】萌萌哒弱受RSA和强攻wiener

【philippica】萌萌哒弱受RSA和强攻wiener

RSA作为一种吃瓜群众最为耳熟能详的公钥加密算法,其本身的思想以及构造方法也是非常简单的。我将尝试着尽量用通俗易懂的语言来描述这件本来就很简单的事。

什么时候需要公钥加密?想像这样一个场景,一堆情侣,每天早上alice站在东边的山头,bob找在西边的山头,隔着大山用扩音喇叭说着羞羞脸的情话。这时候两座山之间的山谷中总会坐着一个吃瓜群众philippica,听着两个人的小秘密。alice和bob当然不喜欢被他听到他们的情话,于是机智的alice就大声的说:"bob,以后每次说话我们都把说的字母按着字母表顺序往后数8个字母,也就是用凯撒密码移动8位,这样philippica就不知道我们在说什么了!"

接着bob想说:“I love u”,他按着字母表abcd的顺序把字母移动了8位,变成了q twdm c,然后他大声地喊出了这些字母,只见alice拿出小本将凯撒密码解开后热泪迎眶,一对情侣泪眼朦胧,在看山谷里的吃瓜八卦者philippica,它也听到了alice说凯撒密码移动8位,于是他放下西瓜,也拿出了笔记本算了一下,然后露出了意味深长的笑容,接着继续吃瓜。

“这根本不起任何作用,我们说的任何加密方式philippica也会听到,他只要用听到的密钥解开我们的密码,那我们做啥都是徒劳的。”bob绝望的说。

alice说:“没关系,我们有rsa,现在我告诉你一组公钥(3233, 17) 你现在脑子里想一个0~25的数字作为凯撒密码的移动位数,假设这个数字是m,那么你现在把m_{}^{17} Mod 3233这个结果告诉我。”

bob觉得现在的alice很6,所以他选择移动位数为6,他会快速幂的方法,所以很快就把6^17 mod 3233的结果算了出来结果是824,他大声喊出了这个数字。

alice听到后很开心,她这里有一组私钥:(3233,2753),她用类似的方法计算824^2753 mod 3233,结果发现是6!于是她很开心的和bob用密钥是6的凯撒说情话了。

再看pilippica这边,他也知道rsa密码的流程,也听到了两人报的数字,他知道Bob想了一个数字m,并且m^17 mod 3233 = 824,可他没有高效的方法去解这个方程,只能看着alice和Bob媚眼传情,共诉情话而自己在一边郁闷的吃瓜了。

因此rsa的本质就是找这么三个数,(e, d, n)使得对于任意的数字(在这里是指明文)m,(m^e)^d mod n = m,接着把(e, n)公开作为公钥,自己留着d,别人把要加密的数自乘e次后mod n,告诉自己,自己只要把发来的结果再自乘d次mod n就是原来的明文了。

---------------------------------------------以下内容需要一些数论基础-------------------------------

那如何找这个三个数呢,rsa给出了很简介的步骤,首先现要找到两个超大的素数p,q。这里p,q如此之大以至于即使线性的欧拉筛也肯定筛个十天半个月才能找到了,所以一般的方法是随机生成一段超大区间的奇数(为啥是奇数?因为大于2的偶数绝对不可能是素数),然后挨个检测每个奇数是否是素数,判素数通常用米勒罗宾等算法,全都不是素数就重新生成一段区间,事实上rsa算法最消耗时间的就是找素数这个环节了。

找到两个素数p,q后把它们相乘,就是三元组(e,d,n)中的n了,也就是n = q * p;接着计算n的欧拉函数φ(n),我们知道φ(n)的含义是小于n中与n互质的数的个数,并且对于n = q * p, 它的欧拉函数的值就是(p - 1) * (q - 1),所以接下来e的选取就是1到(p - 1) * (q - 1)中和(p - 1) * (q - 1)互质的数中的任何一个,而e随便选了,d也就定下来了,是e在mod φ(n)下的逆元,换句话说,e * d mod φ(n) = 1,利用扩展欧几里德算法,我们可以很容易的求出d,这样我们就轻松的把e , d , n求了出来。

简单的证明一下这样选取的(e,d,n)对于任何m,满足(m^e)^d mod n = m吧:

因为 e * d mod φ(n) = 1

即存在整数c使得e * d = φ(n) * c + 1

那么(m^{e}) ^{d}\equiv m^{ed}\equiv m^{\phi (n) * c + 1}\equiv m * (m^{\phi (n)})^{c} MOD n

那么由欧拉定理很快就能得到右边的这坨等于1

(m^{e}) ^{d}\equiv m MOD n

QED

---------------------------------------------以上内容需要一些数论基础-------------------------------

rsa思想很简单,它的安全性在于对于大整数分解的困难(当然,rsa的安全性略小于它),想要直接暴力破解rsa或许等到量子计算机的出现可以解决(shor算法),但是如果你参数选择的不对,猪队友神仙都保不住你,在上文中我们知道,我们要在1~φ(n)中随便找个和φ(n)互素的数,如果这个数选的不当,那么这个rsa的安全性很有可能没你想象的那么安全,wiener发现了,如果d非常小的时候,在小于n^(1/4)/3时,wiener攻击能够奏效!

在说wiener's attack之前,先介绍下连分数,对于任意一个数都能展开成连分数,


如果你尝试做一遍你会发现,在你对一个分数展开成连分数的时候,事实上就是在对分子分母不断做辗转相除法,因此你很快得出一个结论:对于任意有理数(两个整数的比)的连分数永远是有限的。这很简单,对于两个数辗转相除,最终会停在两个数的最大公约数上,对于无理数也能连分数展开,例如著名的无理数π,e,无理数连分数展开是无限的。

对于连分数,我们观察每一个分母,它后面加的那一项都小于1,所以相比ai是一个非常小的数,如果我们把第i个分母后面的分数全部略去,我们称这个分数为这个连分数第i个渐进分数,显然i越大离x越接近,并且由于约去了分母前n-1个渐进分数都是小于x的。比如祖冲之发现圆周率的疏率和密率22 / 7与355 / 113 就是π连分数展开的两个渐进分数。


介绍完连分数,我们回来看RSA,首先N = pq; φ(n) = pq - (p + q) + 1 = N - (p + q) + 1

可以发现由于p,q非常大,pq是远大于p+q的,因此φ(n)约等于N

上面我们知道e d对于φ(n)互为逆元,因此我们可以列出式子:ed-1=k*φ(n)

这个式子非常重要我们将它变个型,两边同除以d*φ(n)可以得到:

\frac{e}{\phi (n)} -\frac{k}{d} =\frac{1}{d\phi (n)}

由于φ(n)约等于N,上式我们可以写成\frac{e}{N} -\frac{k}{d} =\frac{1}{d\phi (n)}

显然d*φ(n)是一个很大的数,因此可以说e/N 略大于k/d

为啥要这么写呢,因为e和N是我们是知道的,公钥中给我们的,所以我们计算出e/N后,比它略小的k/d怎么出来呢,计算e/N的连分数展开,依次算出这个分数每一个渐进分数,由于e/N 略大于k/d,wiener证明了,该攻击能精确的覆盖k/d

我们来举个例子,现在有一个rsa, e = 42667, N = 64741,我们来求。第一步,我们把分数e/N连分数展开,以此求出每一个渐进分数:0,1, 1/2, 2/3 ....

我们用1/2举例子,现在我们说,k/d=1/2非常可能成立,假设它成立,我们把k=1,d=2代入上面的ed-1=k*φ(n)中(为何原来比值相等的可以直接把分子分母分别带进去?想一想为什么),显然e,d,k都有了,φ(n)也就有了,我们知道φ(n)有啥用呢?我们知道 φ(n) = pq - (p + q) + 1 = N - (p + q) + 1,N = pq作为公钥我们是知道的,所以知道了φ(n) 我们只要算出N- φ(n)+1就是(p + q)的值,好回到初三,现在知道了pq,和p+q的值,我们如何快速求出p和q的值呢?很简单,利用伟大定理,我们可以轻松构造出方程x^2 - (p + q)*x + pq = 0,这个方程的两个根就是我们要求的p,q,至此rsa中所有的参数都被我们求了出来,让我们干杯!

顺手把wiener attack的算法写了一遍



import math
# Return the continued fractions expansions of x / y
def continuedFraction(x, y):
	ret = []
	while y:
		ret.append(x / y)
		x, y = y, x % y
	return ret

def expand(ctnf):
	_ctnf = ctnf
	_ctnf.reverse()
	numerator = 0
	denominator = 1
	for x in _ctnf:
		numerator, denominator = denominator, x * denominator + numerator
	return (numerator, denominator)
		


# Return the list of n progressive fraction 
def progressiveFraction(x, y):
	cfe = continuedFraction(x, y)
	cfeL = len(cfe)
	ret = []
	for i in xrange(1, cfeL):
		ret.append(expand(cfe[0 : i]))
	return ret
		
# Solve the equation: ax^2 +bx + c = 0
def solve(a, b, c):
	par = math.sqrt(b * b - 4 * a * c)
	return (-b + par) / (2 * a), (-b - par) / (2 * a)


def wienerAttack(e, n):
	 res = progressiveFraction(e, n)
	 for (d, k) in res:
		 if k == 0 : continue
		 if (e * d - 1) % k != 0:continue
		 phi = (e * d - 1) / k
		 p, q = solve(1, n - phi + 1, n)
		 if p * q == n:
		 	print "find it"
		 	return



wienerAttack(17993, 90581)






reference:

en.wikipedia.org/wiki/R

编辑于 2016-08-10