RSA攻击

EXP6 RSA攻击

💡 参考实验:
实验四 RSA中公开的模数N_rsa 模数_chuxuezheerer的博客-CSDN博客
Stanford_Cryptography-I/factoring_challenge.py at main · galadd/Stanford_Cryptography-I

一、RSA陷门置换函数

  • 有两个任意的奇素数p和q,,其欧拉函数值 φ ( n ) = ( p − 1 ) ( q − 1 ) \varphi \left( n \right) =\left( p-1 \right) \left( q-1 \right) φ(n)=(p1)(q1)
  • 随机选一整数e(公钥), 1 ≤ e ≤ φ ( n ) , ( φ ( n ) , e ) = 1 (互素) 1\le e\le \varphi \left( n \right) ,\left( \varphi \left( n \right) ,e \right) =1\text{(互素)} 1eφ(n),(φ(n),e)=1(互素),在模 φ ( n ) \varphi \left(n \right) φ(n)下,e存在逆元: d e   m o d   φ ( n ) = 1 ⇒ d = e − 1   m o d   φ ( n ) de\ mod\ \varphi \left( n \right) =1 \Rightarrow d=e^{-1}\ mod\ \varphi \left( n \right) de mod φ(n)=1d=e1 mod φ(n) ,即可计算出私钥d。
  • n,e都可公开,私钥为d,销毁p,q不可泄露。
    • 加密变换: m → c = m e   m o d   n m\rightarrow c=m^e\ mod\ n mc=me mod n
    • 解密变换: m = c d   m o d   n m=c^d\ mod\ n m=cd mod n
    • 公钥加密,私钥解密

二、实验原理及运行结果

在 RSA 的密钥生成阶段,不同用户应该独立的生成模 N,即选取大素数 p 和 q相乘得到 N。但是某个项目中 RSA 具体实现过程如下:(1)首先随机选取一个随机数 R 然后选取距离 R 最近的素数 p,以及另外一个相近的素数 q。(2)p和 q 相乘得到 N。由于 p 和 q 为相近的两个素数,在此情况下存在对 N 的高效分解方法,导致方案不安全。

假设 N 的因子 p 和 q 满足:⭐ ∣ p − q ∣ < 2 N 1 4 |p-q|<2N^{\frac{1}{4}} pq<2N41,那么 N 的分解可由如下分析得出:

A = p + q 2 A=\frac{p+q}{2} A=2p+q,那么根据 p 和 q 之间的关系,能够得到 A − N < 1 A - \sqrt{N}<1 AN <1。因此可以根据 A = c e i l ( s q r t ( N ) ) A=ceil(sqrt(N)) A=ceil(sqrt(N))来计算得到A,其中ceil是取整函数。如下所示是 p , q , N , p + q 2 p,q,\sqrt{N},\frac{p+q}{2} p,q,N ,2p+q之间的关系:
在这里插入图片描述

由于 N = p q = ( A − x ) ( A + x ) = A 2 − x 2 N=pq=(A-x)(A+x)=A^2-x^2 N=pq=(Ax)(A+x)=A2x2,因此有 x = A 2 − N x=\sqrt{A^2-N} x=A2N 。当求得 x 以后,容易根据 p = A + x , q = A − x p=A+x,q=A-x p=A+x,q=Ax得到 N 的分解。
![外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传](https://img-home.csdnimg.cn/images/20230724024159.png?origin_url=EXP6%2520RSA%25E6%2594%25BB%25E5%2587%25BB%2520cbb9cb703f4a4dbc8631b0ef158001a8%2FUntitled%25201.png&pos_id=img-4iPgU03k-

分解挑战#1

如下的整数 N 是两个素数 p,q 的乘积,且满足 ∣ p − q ∣ < 2 N 1 4 |p-q|<2N^{\frac{1}{4}} pq<2N41,请分解整数N,并给出其十进制结果。

N = 17976931348623159077293051907890247336179769789423065727343008115 7732675805505620686985379449212982959585501387537164015710139858647833778606925583497541085196591615128057575940752635007475935288710823649949940771895617054361149474865046711015101563940680527540071584560878577663743040086340742855278549092581

由上方的数学关系即可得到x,p,q。

🥺 使用math.sqrt()开平方的结果:
在这里插入图片描述math.sqrt()函数只能处理float类型的值,因此当N超过其可处理的最大值时,会引发该错误。使用print(sys.float_info.max)查看Python中float类型可以表示的最大值为:1.7976931348623157e+308,N远大于该值,所以不能用math.sqrt()函数开根号。

😜 解决方法:
使用gmpy2中的函数解决。gmpy2是Python的一个第三方库,提供了高精度计算功能,包括大整数运算、大浮点数运算、素数测试和素数生成等。gmpy2底层使用GNU Multiple Precision Arithmetic Library (GMP)实现高精度计算,因此具有很高的效率和精度。
使用gmpy2库可以方便地处理超出Python内置类型范围的数值,例如非常大的整数、超出标准浮点数表示范围的浮点数等。此外,gmpy2还提供了一些高级的数学函数,例如椭圆曲线加密、计算Jacobi符号、计算模反演等。
在使用gmpy2库进行高精度计算时,常常需要将Python内置类型的整数转换为gmpy2库中的整数类型,以便利用gmpy2库提供的高精度运算功能。gmpy2.mpz()函数用于将Python内置类型的整数转换为gmpy2库中的整数类型mpz(多精度整数)。
但gmpy2中没有提供向上取整函数。
常用的开根号函数:

  1. gmpy2.isqrt(x):返回一个整数y,满足y^2 <= x < (y+1)^2,即x的平方根的最大整数。
  2. gmpy2.sqrt(x):返回一个浮点数y,满足y^2 ≈ x。
  3. gmpy2.c_sqrt(x):返回一个复数z,满足z^2 ≈ x。
  4. gmpy2.sqrtrem(x):返回一个元组(y, r),其中y是整数类型的平方根,r是余数,满足x = y^2 + r。
  5. gmpy2.isqrt_rem(x)函数返回一个元组(q, r),其中q是整数类型的平方根,r是余数,满足x = q^2 + r,且r < 2q + 1。

gmpy2.sqrtrem(x)函数使用了高精度浮点数的计算方法,因此在计算小数部分时会有一定的误差。由于gmpy2库支持高精度计算,因此这个误差相对较小,通常可以满足实际需求。
gmpy2.isqrt_rem(x)函数使用了二分查找的方法来计算平方根和余数,因此计算结果准确无误。由于该函数不需要进行高精度浮点数的计算,因此在计算速度上相对较快,特别适合处理大整数的开方运算。
因此,当需要精确计算一个数的平方根和余数时,可以选择gmpy2.isqrt_rem()函数;当对精度要求不高,但需要高效计算一个数的平方根和余数时,可以选择gmpy2.sqrtrem()函数。

def task1(N):  #  |p-q|<2N^{1/4}
    N = gmpy2.mpz(N)   # 转换为gmpy2中的整数类型
    A, rem = gmpy2.isqrt_rem(N)   # 结果和余数
    if rem > 0:
        A += 1
    x = gmpy2.isqrt(A**2 - N)
    p = A - x
    q = A + x
    if gmpy2.mul(p, q) == N:
        print("分解正确!")
    else:
        print("分解有误。")
    return p, q

print("分解挑战1的结果如下:")
    task1_p, task1_q = task1(179769313486231590772930519078902473361797697894230657273430081157732675805505620686985379449212982959585501387537164015710139858647833778606925583497541085196591615128057575940752635007475935288710823649949940771895617054361149474865046711015101563940680527540071584560878577663743040086340742855278549092581)
    print("p:", task1_p)
    print("q:", task1_q)

运行结果:

分解正确!
分解挑战1的结果如下:
p:

13407807929942597099574024998205846127479365820592393377723561443721764030073778560980348930557750569660049234002192590823085163940025485114449475265364281
q: 13407807929942597099574024998205846127479365820592393377723561443721764030073662768891111614362326998675040546094339320838419523375986027530441562135724301

分解挑战#2

如下的整数 N 是两个素数 p,q 的乘积,且满足 ∣ p − q ∣ < 2 11 N 1 4 |p-q|<2^{11}N^{\frac{1}{4}} pq<211N41,请分解整数N,并给出其十进制结果。

N=648455842808071669662824265346772278726343720706976263060439070378797308618081116462714015276061417569195587321840254520655424906719892428844841839353281972988531310511738648965962582821502504990264452100885281673303711142296421027840289307657458645233683357077834689715838646088239640236866252211790085787877

在这里插入图片描述

def task2(N):   #  |p-q|<2^{11}N^{1/4}
    N = gmpy2.mpz(N)
    A, rem = gmpy2.isqrt_rem(N)  # 结果和余数。A的最小值为ceil(sqrt(N))
    floor = A   # floor(√N)
    if rem > 0:
        A += 1
    p = q = 0
    while A < 2**19 + floor:
        x, rem = gmpy2.isqrt_rem(A**2-N)
        if rem == 0:    # 能够开方没有余数
            p, q = A-x, A+x
            if gmpy2.mul(p, q) == N:
                print("分解正确!")
                break
        A += 1
    if p == 0 and q == 0:
        print("分解错误!")
    return p, q

print("分解挑战2的结果如下:")
    task2_p, task2_q = task2(648455842808071669662824265346772278726343720706976263060439070378797308618081116462714015276061417569195587321840254520655424906719892428844841839353281972988531310511738648965962582821502504990264452100885281673303711142296421027840289307657458645233683357077834689715838646088239640236866252211790085787877)
    print("p:", task2_p)
    print("q:", task2_q)

运行结果:

分解挑战2的结果如下:
分解正确!
p: 25464796146996183438008816563973942229341454268524157846328581927885777969985222835143851073249573454107384461557193173304497244814071505790566593206419759
q: 25464796146996183438008816563973942229341454268524157846328581927885777970106398054491246526970814167632563509541784734741871379856682354747718346471375403

解密挑战#3

如下所示是使用分解挑战 1 中的模数 N 对一段消息进行加密得到的密文。其中加密指数 e=65537。明文消息使用 ASCII 进行编码后使用 PKCS v1.5 进行消息填充,需要指出的是,消息中使用’0x00’对消息和随机填充进行分割,而不是’0xFF’。请在实验报告中给出下列密文的解密结果。

22096451867410381776306561134883418017410069787892831071731839143676135600120538004282329650473509424343946219751512256465839967942889460764542040581564748988013734864120452325229320176487916666402997509188729971690526083222067771600019329260870009579993724077458967773697817571267229951148662959627934791540

RSA分块加密和填充
RSA加密长度限制问题:
明文长度需要小于密钥长度,而密文长度则等于密钥长度。因此当加密内容长度大于密钥长度时,有效的RSA加解密就需要对内容进行分段并且填充。填充后每组数据的长度与RSA公钥等长。同时可以避免Textbook RSA攻击,即若m较小时直接求e次方根得到m。
在这里插入图片描述
实际应用中的RSA公钥加密方法:
在这里插入图片描述
常见的填充方式:
在这里插入图片描述假设现有RSA算法密钥长度为1024bit,则三种填充模式的填充效果如下所述。

  • RSA_NO_PADDING填充模式
    当用户选择RSA_NO_PADDING填充模式时,如果明文不够128字节,加密的时候会在明文前面填充若干数据0,直至达到128字节。
    解密后的明文也会包括前面填充的零,用户需要注意把解密后的字段前向填充的零去掉,才是真正的明文。凌科芯安系列芯片支持RSA算法的芯片目前就是时钟这种填充方式。如果填充规则不符合要求,建议用户使用芯片加密前,先自行填充。
  • RSA_PKCS1_PADDING填充模式
    如果明文不够128字节,加密的时候会在明文中随机填充一些数据,所以会导致对同样的明文每次加密后的结果都不一样。
    对加密后的密文,用户使用相同的填充方式都能解密。解密后的明文也就是之前加密的明文。
    例如:EB = 00 || BT || PS || 00 || D ,其中D为消息,BT为The block type块类型,PS为The padding string填充字符串。
    BT(The block type块类型):
    BT=00 or 01 (私钥运算时)
    BT=02 (公钥运算时)
    PS(The padding string填充字符串):
    BT=00时,PS由00组成;
    BT=01时,PS由FF组成;
    BT=02时,PS由伪随机生成,且非零,以增加填充的随机性和安全性;
    则PS长度为Len(EB) - 3 - Len(D),最少是8字节。
    由填充形式可得明文数据D即为第二个”0x00”之后的数据。
  • RSA_PKCS1_OAEP_PADDING填充模式
    RSA_PKCS1_OAEP_PADDING填充模式是PKCS#1推出的新填充方式,安全性最高,和前面RSA_PKCS1_PADDING的区别就是加密前的编码方式不一样。
    最后将加密后的结果拼接在一起,形成最终的密文。
  • PKCS v1.5和RSA_PKCS1_PADDING的关系
    PKCS#1 v1.5是RSA加密填充方案的一种,RSA_PKCS1_PADDING是OpenSSL库中对PKCS#1 v1.5填充方案的实现方式之一。在OpenSSL中,使用RSA_PKCS1_PADDING填充模式时,会对原始明文数据进行PKCS#1 v1.5填充,然后再进行RSA加密,生成最终的密文。

🙈 RSA加密字符串:
可以将字符串视为一个字节数组,并将其视为一个数据块来进行加密。通常需要将字符串转换为字节数组,并将字节数组填充到指定的长度,然后对填充后的数据块进行RSA加密。

def task3(p, q, c, e):    # RSA解密
    N = gmpy2.mul(p, q)
    fi = (p - 1) * (q - 1)
    c = gmpy2.mpz(c)
    e = gmpy2.mpz(e)
    d = gmpy2.invert(e, fi)    # gmpy2库中的一个数论函数,用于计算在模b意义下a的逆元。也就是求一个整数x,使得 (a * x) % b = 1。

    # m = c ^ d mod n
    m = gmpy2.powmod(c, d, N)   # gmpy2库中的一个数论函数,用于计算在模modulus意义下的幂运算。也就是计算 (base**exponent) % modulus 的值。
    # 使用快速幂算法来计算幂运算,因此对于大数幂运算,它的效率比简单的幂运算更高。
    # 去除填充→EB = 00 || BT || PS || 00 || D
    m = gmpy2.digits(m, 16).split("00")[1]   # digits(x, base)用于将整数n表示为指定进制的数字字符串
    # .split()以字符串'00'为分隔符将该字符串分割成若干段,并选择其中第一个分段即原始数据D
    return bytes.fromhex(m)   # 以字节形式返回解密得到的明文

运行结果:
在这里插入图片描述

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值