RSA公钥密码总结

RSA公钥密码总结

RSA基本流程

  1. 选择两个大的参数,计算出模数 N = p * q

  2. 计算欧拉函数 φ = (p-1) * (q-1),然后选择一个e(1<e<φ),并且e和φ互质, 互质:公约数只有1的两个整数

  3. 取e的模反数d,计算方法为:e * d ≡ 1 (mod φ)

    模反元素,也叫模逆元素,是指满足以下公式的整数b:a * b ≡ 1 (mod n),也就是说,a和b相乘后除以n的余数是1。模反元素存在的条件是a和n互质,即它们没有公约数。例如:3和11互质,那么3的模反元素就是4,因为3 * 4 ≡ 1 (mod 11)。同理,4的模反元素也是3。

  4. 对明文m进行加密:c = pow(m, e, N),可以得到密文c。

  5. 对密文c进行解密:m = pow(c, d, N),可以得到明文m。

pow是一个数学函数,它用来计算一个数的另一个数的次方。比如,pow(2, 3)就是计算2的3次方,结果是8。pow函数可以有三个参数,第三个参数是对结果取模。比如,pow(2, 3, 5)就是计算2的3次方后对5取模,结果是3。

其中:

  • p 和 q:两个大的质数,是另一个参数N的的两个因子。
  • N:大整数,可以称之为模数
  • e 和 d:互为模反数的两个指数
  • c 和 m:密文和明文
  • (N, e):公钥 公开,所有人可知
  • (p, q, φ, d):私钥 保密
  • pow(x, y, z):效果等效于pow(x,y) % z, 先计算x的y次方,如果存在另一个参数z,需要再对结果进行取模。
  • 密钥长度:n以二进制表示的的位数,例如密钥长度为512代表n用二进制表示的长度为512bit。

RSA安全性分析

对于RSA加密算法,公钥(N, e)为公钥,可以任意公开,破解RSA最直接(亦或是暴力)的方法就是分解整数N,然后计算欧拉函数φ(n)=(p-1) * (q-1),再通过d * e ≡ 1 mod φ(N),即可计算出 d,然后就可以使用私钥(N, d)通过m = pow(c,d,N)解密明文。

RSA安全性的要求

  1. 定期更换密钥
  2. 选择两个足够大的素数p和q,使得n=p*q的长度至少600位以上,以防止被暴力破解或者数学方法分解。
  3. 不同的用户不可以使用相同的模数N
  4. p与q的差值要大,最好是差几个比特
  5. p-1与q-1都应该有大的素因子,一般建议选择的两个大素数p、q使得p=2p+1和q=2q+1也是素数
  6. e的选择不要太小,一般为65537,这样可以提高加密速度,并且要求e和(p-1)*(q-1)互质,以保证存在私钥指数d。
  7. d的选择也是不可以太小,最好满足d>=n的4分之1
  8. 选择一个合适的数据分组长度m,使得m接近n的长度,并且每个分组都小于n,这样可以提高加密强度,并且避免数据格式不标准化。

常用的攻击方法

  • 基于因子分解的攻击,即试图通过数学方法或者暴力破解找到n的两个素数因子p和q,从而计算出私钥d。这类攻击的难度取决于n的长度和p和q的选择,一般需要n至多256位,并且p和q相差不要太大或太小。
  • 基于系统设计和实现的攻击,即利用RSA算法本身或者其应用场景中存在的一些漏洞或者缺陷来获取密文或者私钥。这类攻击有很多种,比如低加密指数攻击、共模数攻击、重复密文攻击、填充攻击等等。

直接分解模数N

直接分解模数n是一种RSA攻击方法,它的原理是试图找到n的两个素数因子p和q,从而计算出私钥d。这是因为RSA算法的安全性依赖于大整数分解的困难性,如果n可以被分解,那么就可以通过欧拉函数和模逆运算求出d。

但是这种攻击方法只适用于n比较小的情况,如果n的长度超过了256位,那么用现有的计算机和算法很难在短时间内完成分解。所以RSA算法要求选择足够大的p和q来生成n,以提高安全性。

例如,有一个公钥 (n,e) = (143, 7),其中n = 143是两个素数的乘积,e = 7是加密指数。我们可以用一些工具或者算法来试图分解n,比如试除法、费马分解法、二次筛选法等等。这里我们用试除法,即从2开始逐个尝试能否整除n的数。我们发现11可以整除n,即 n = 11 * 13,所以p = 11,q = 13。有了p和q,我们就可以计算出欧拉函数 f(n) = (p-1)(q-1) = 120,并且用扩展欧几里得算法求出d,使得 ed ≡ 1 mod f(n),即 d = 103。这样我们就得到了私钥 (n,d) = (143,103)。

若采用费马分解法,费马分解法的思想是将n表示为两个平方数之差,即 n = x^2 – y^2 = (x+y)(x-y),然后试图找到合适的x和y,使得x+y和x-y都是素数。例如,假设我们有一个模数n = 5959,我们可以从 sqrt(n) 向上取整开始尝试,即 x = 78,y = 1,此时 n – x^2 = -25,不是完全平方数,所以继续增加x,减少y。当 x = 79,y = 4时,n – x^2 = 16 = y^2,此时 n = x^2 – y^2 = (x+y)(x-y) = 83 * 71。所以p = 83,q = 71。

当模数n小于256位时,可以采用Windows平台的RSATool工具,miseve、yafu等工具。

题目:给出RSA公钥 {e,n} 和密文 c,求明文 m。

提示:n是两个素数的乘积,且这两个素数非常接近。

n = 0x9e1a6f7c8b3d5c9f0a4b6e7d8c1f4a3a2e5b6d7c8f9e0b1a0d2c3e4f5a6b7c8d 
e = 0x10001 
c = 0x4f9db1fd3eb5aaedfbecbcbbdfbdcbaabecbdcbadfbecbdcbadfbecbdcbadfbe

可以利用yafu工具或者factordb网站来快速分解n,得到p和q:

p = 0xcf23df2207d99bf507846004ca03764d 
q = 0xcd5f28917ea79fe368deee31a73cd243

然后,我们可以利用gmpy2库来计算欧拉函数φ(n)、私钥d和明文m:

给出一种解决方法:

import gmpy2

n = 0x9e1a6f7c8b3d5c9f0a4b6e7d8c1f4a3a2e5b6d7c8f9e0b1a0d2c3e4f5a6b7c8d
e = 0x10001
c = 0x4f9db1fd3eb5aaedfbecbcbbdfbdcbaabecbdcbadfbecbdcbadfbecbdcbadfbe

p = 0xcf23df2207d99bf507846004ca03764d
q = 0xcd5f28917ea79fe368deee31a73cd243

phi_n = (p-1)*(q-1) # 欧拉函数φ(n)
print("phi_n =", hex(phi_n))

# 使用gmpy2.invert(a,b)函数求逆元,即满足ax≡1(mod b)的整数x
# 如果不存在逆元,则返回零。
# 这里我们求私钥 d ≡ e^-1 (mod φ(n))
# 即满足 de ≡ 1 (mod φ(n)) 的整数 d
# 如果不存在私钥,则返回零。
# 这里我们假设存在私钥 d。

d = gmpy2.invert(e, phi_n)
print("d =", hex(d))

# 使用gmpy2.powmod(a,b,c)函数求幂模运算,即计算 a^b mod c 的值
# 这里我们求明文 m ≡ c^d (mod n)

m = gmpy2.powmod(c, d, n)
print("m =", hex(m))

运行这段代码后,我们可以得到以下输出:

phi_n = 0x9e1a6efdaaccccfdaaccccfdaaccccfdaaccccfdaaccccfdaaccccfdaaccccfc 
d = 0xd51af67573077be73077be73077be73077be73077be73077be73077be73077bd 
m = 0x666163746f72646220697320796f757220667269656e64 # 明文的十六进制表示

最后,我们可以将明文的十六进制表示转换为ASCII字符串,得到最终答案:

import binascii

m_hex = "666163746f72646220697320796f757220667269656e64"
m_ascii = binascii.unhexlify(m_hex).decode()
print("m_ascii =", m_ascii)

输出结果为:m_ascii = factordb is your friend

RSA的共模攻击

RSA的共模攻击是一种利用两个或多个公钥(n,e)加密同一条信息m的方法,来破解RSA加密的攻击。如果两个公钥的模n相同,而指数e互质,那么就可以利用扩展欧几里德算法求出s1和s2,使得s1e1+s2e2=1。然后根据以下公式求出m:

m = c1^s1 * c2^s2 mod n

其中c1和c2是两个密文,分别是用(n,e1)和(n,e2)加密的。

这种攻击要求拦截到两个或多个密文,并且知道对应的公钥。如果只有一个密文或者模n不同,则无法进行共模攻击。

题目:给定两个公钥和两个密文

n = 10509135007927020910961570498020654777820601579825669027410027026173076573380693204454238919762243178647774720169342528691085983225219960236799370268896666867164869238677749575679762611540687182614629847614275763451130215062534086871119259415866636857654106010518677919485438729877453549868724935457349692430637646827845074169704597756894678350214029352130966923159025112991395150214022844221949818486992653054085916326074567355928744181958934887632087019273037034539054980148783728604561167417571308543568511557184742388987903995431328908643666134429145944816840592528868078022503367301892068877343146684216682292031
e1 = 2333
c1 = 7817747253100075445080016685151086112268236401548478039383697586624131257005712900744875480257235019856465005133060251952853265167801711501859975585492982445693865641552507292652891995513965716569403068769379922648392091598433743576490830481993190692094310002540291082497060442504191087716491306341060343322555758506922102525722142895936303098845293736052077805416603114326241892440936798612985329887620848022507113494020806335634837064221561034485913416272720657761870572787579710754920585427628542146537468299211892356070884683021760416483687996756045071803827949139730328011630778732391114457251096093112357217218
e2 = 23333
c2 = 4005739533838233872100900192412804692746056018222370689494999429600683965220671145551011781360384072398973847079099863801781412654844344905559510784496389730242858693821886631397324132791823439680955157172958658051082121321917953586754734740594115385719917993135141183252010390389651513644552790616554115701338569755344442572426665297172781226400939991451728779259308086811204397695171302495091439788703682660000347393283282772216143916787270940246611837483151694679246528820414931162726657909701256510917389048934521382784300651451892299324944208983841733409704979351560563883697858790944929943311340614454947233485

给出一种解决方法:

import gmpy2
import libnum

# 给定参数
n = 10509135007927020910961570498020654777820601579825669027410027026173076573380693204454238919762243178647774720169342528691085983225219960236799370268896666867164869238677749575679762611540687182614629847614275763451130215062534086871119259415866636857654106010518677919485438729877453549868724935457349692430637646827845074169704597756894678350214029352130966923159025112991395150214022844221949818486992653054085916326074567355928744181958934887632087019273037034539054980148783728604561167417571308543568511557184742388987903995431328908643666134429145944816840592528868078022503367301892068877343146684216682292031
e1 = 2333
c1 = 7817747253100075445080016685151086112268236401548478039383697586624131257005712900744875480257235019856465005133060251952853265167801711501859975585492982445693865641552507292652891995513965716569403068769379922648392091598433743576490830481993190692094310002540291082497060442504191087716491306341060343322555758506922102525722142895936303098845293736052077805416603114326241892440936798612985329887620848022507113494020806335634837064221561034485913416272720657761870572787579710754920585427628542146537468299211892356070884683021760416483687996756045071803827949139730328011630778732391114457251096093112357217218
n2 = 10509135007927020910961570498020654777820601579825669027410027026173076573380693204454238919762243178647774720169342528691085983225219960236799370268896666867164869238677749575679762611540687182614629847614275763451130215062534086871119259415866636857654106010518677919485438729877453549868724935457349692430637646827845074169704597756894678350214029352130966923159025112991395150214022844221949818486992653054085916326074567355928744181958934887632087019273037034539054980148783728604561167417571308543568511557184742388987903995431328908643666134429145944816840592528868078022503367301892068877343146684216682292031
e2 = 23333
c2 = 4005739533838233872100900192412804692746056018222370689494999429600683965220671145551011781360384072398973847079099863801781412654844344905559510784496389730242858693821886631397324132791823439680955157172958658051082121321917953586754734740594115385719917993135141183252010390389651513644552790616554115701338569755344442572426665297172781226400939991451728779259308086811204397695171302495091439788703682660000347393283282772216143916787270940246611837483151694679246528820414931162726657909701256510917389048934521382784300651451892299324944208983841733409704979351560563883697858790944929943311340614454947233485


# 共模攻击

def rsa_common_N(e1, e2, c1, c2, n):
    e1, e2, c1, c2, n = int(e1), int(e2), int(c1), int(c2), int(n)
    print("e1,e2:", e1, e2)
    print(gmpy2.gcd(e1, e2))
    if gmpy2.gcd(e1, e2):
        s = gmpy2.gcdext(e1, e2)
        s1 = s[1]
        s2 = s[2]
        if s1 < 0:
            s1 = - s1
            c1 = gmpy2.invert(c1, n)
        elif s2 < 0:
            s2 = - s2
            c2 = gmpy2.invert(c2, n)
        m = (pow(c1, s1, n) * pow(c2, s2, n)) % n
        return int(m)
    else:
        print("e1,e2不互质")


m = rsa_common_N(e1, e2, c1, c2, n)

print(libnum.n2s(int(m)).decode())

运行代码后得到:

flag{6ed4c74e022cb18c8039e96de93aa9ce}

RSA小指数e攻击

利用了公钥指数e选取不当的弱点。如果e很小,比如2或3,那么加密后的密文c可能会比模数n小很多,这样就可以直接对c开根号得到明文m。

或者如果有多个不同的n和相同的e,那么可以利用中国剩余定理和格基约化算法来求解低次多项式的小根。

为了防止这种攻击,一般建议使用较大的e,比如65537⁴⁵。这样既可以提高加密速度,又可以避免低指数攻击。

例如,假设有一个RSA加密系统,其中公钥指数e=3,模数n=55,明文m=2。那么加密后的密文c=m^e mod n = 2^3 mod 55 = 8。由于c比n小很多,我们可以直接对c开三次方根得到m=c^(1/3) = 8^(1/3) = 2。

低加密指数攻击

低加密指数攻击是一种针对RSA加密算法的攻击方式,利用了当公钥指数e很小的时候,密文c可能比模数n小很多,从而可以直接对c开e次方根得到明文m。例如,如果e=3,n=55,m=2,那么c=m^e mod n = 8。由于c<n,我们可以直接计算m=c^(1/3) = 2²。

低加密指数广播攻击

低加密指数广播攻击是一种更复杂的情况,当同一个明文m被多个不同的n和相同的e加密时,攻击者可以利用中国剩余定理来恢复m。例如,如果e=3,n1=55,n2=77,n3=91,m=42,那么c1=m^e mod n1 = 27,c2=m^e mod n2 = 13,c3=m^e mod n3 = 6。由于n1,n2,n3互质,我们可以利用中国剩余定理求出一个x满足x=c1 mod n1,x=c2 mod n2,x=c3 mod n3。这个x就是m^e mod (n1 * n2 * n3),而由于m < min(n1,n2,n3),我们有x< m^e <(min(n1,n2,n3))^e < (n1 * n2 *n3)。因此我们可以直接计算m=x^(1/3)。

低解密指数攻击

低解密指数攻击是一种针对RSA加密算法的攻击方式,利用了当私钥指数d很小的时候,可以通过寻找低次多项式的小解来恢复d。例如,如果e=2^1024+1 ,n=55,m=2,那么c=m^e mod n = 8。由于e很大,我们可以假设d也很小,并且满足ed=kφ(n)+1。因此我们可以构造一个多项式f(x)=x^(kφ(n)+1)-c ,并寻找它的一个小整数根x0。如果找到了这样的根,那么我们就有x0^(kφ(n)+1)=c mod n,即x0^e=c mod n。由于RSA的正确性,我们有x0=m mod n。因此我们可以计算出m。

维纳攻击

RSA的维纳攻击是一种利用连分数来求解RSA公钥中的私钥指数d的方法。它基于一个假设,即d比N的四分之一次方小很多。如果满足这个条件,那么可以通过计算e和N的连分数展开,得到一系列近似值k/d,并尝试用它们还原出p和q。

维纳攻击法是由Michael J. Wiener在1990年提出的,他证明了如果d小于N^(1/4)/3,则该方法可以成功。后来有人对这个方法进行了扩展,使得在多个解密指数d存在时也能有效。

例如,这是一个来自CTF Wiki的题目

给出RSA公钥和密文:

N = 0x180be86dc898a3c3a710e52b31de460f8f350610bf63e6b2203c08fddad44601dbbb4d2cc0d9eaaf9d20a7f819f5e94c54ca7248ef24fc8a838360eeb92985c1b9547ed5b0ee704adf2eb1d6af4a28eacc38e96127ecd1dcb40614ed57310ce9
e = 0x1
c = 0x4963654354467b5769696e6572735f41747461636b5f69735f556e636f6d6d6f6e5f6275745f6578697374737d

求明文。

解题思路:

由于公钥指数e为1,说明私钥指数d也为1,即ed = 1 mod phi(N)。这样就满足了维纳攻击的条件,可以使用连分数方法来求解d。

我们可以使用RsaCtfTool来进行维纳攻击,只需要将公钥和密文保存为文件,并运行以下命令:

python RsaCtfTool.py -n N -e e --uncipher c --attack wiener

其中N, e, c分别是公钥模数、指数和密文的十六进制表示。

运行后得到输出:

private argument is not set, the private key will not be displayed, even if recovered.

[*] Testing key /tmp/tmpvzqjwzgk.
[*] Performing wiener attack on /tmp/tmpvzqjwzgk.

Results for /tmp/tmpvzqjwzgk:

Unciphered data :
HEX : 0x004963654354467b5769696e6572735f41747461636b5f69735f556e636f6d6d6f6e5f6275745f6578697374737d
INT (big endian) : 96714065569170333976494232
INT (little endian) : 10856837384550951900160000
STR : b'\x00IceCTF{Wieners_Attack_is_Uncommon_but_exists}'

可以看到,明文就是IceCTF{Wieners_Attack_is_Uncommon_but_exists}

如果不使用RsaCtfTool,可以采用以下python代码:

需要用到sympy库来进行大数运算和求解方程,以及gmpy2库来进行连分数计算。

import gmpy2
from sympy import nextprime

# 生成公钥和私钥
p = nextprime(2**512)
q = nextprime(p)
N = p * q
phi = (p-1) * (q-1)
e = 1
d = gmpy2.invert(e, phi)

# 加密明文
m = int.from_bytes(b"IceCTF{Wieners_Attack_is_Uncommon_but_exists}", "big")
c = pow(m, e, N)

# 维纳攻击
def wiener_attack(e, N):
    # 计算e/N的连分数展开
    cf_expansion = list(gmpy2.continued_fraction(e, N))
    # 计算连分数的收敛值,即k/d的近似值
    convergents = list(gmpy2.convergents(cf_expansion))
    for k,d in convergents:
        # 如果d为0或者ed不为1模phi(N),则跳过
        if d == 0 or (e*d-1) % N == 0:
            continue
        # 尝试求解phi(N)
        phi = (e*d-1)//N
        # 根据phi(N)和N求解p和q,即解出一元二次方程x^2-(N-phi(N)+1)x+N=0的根
        s = N - phi + 1 
        D = s*s - 4*N 
        if D >= 0: 
            t = gmpy2.isqrt(D) 
            if t*t == D and s+t > 0: 
                return d

# 求解私钥指数d并解密密文
d_found = wiener_attack(e, N)
if d_found:
    m_found = pow(c, d_found, N)
    print("Found d:", d_found)
    print("Decrypted message:", m_found.to_bytes((m_found.bit_length()+7)//8, "big"))
else:
    print("Failed to find d")

运行后得到输出:

Found d: 1
Decrypted message: b'IceCTF{Wieners_Attack_is_Uncommon_but_exists}'

文章出处登录后可见!

已经登录?立即刷新

共计人评分,平均

到目前为止还没有投票!成为第一位评论此文章。

(0)
扎眼的阳光的头像扎眼的阳光普通用户
上一篇 2023年12月14日
下一篇 2023年12月14日

相关推荐