密码学-实验三

摘要

1. 实验要求

现有一个RSA加解密软件,给出了它的算法描述和可以被截获的数据,要利用对RSA的多种攻击方式,从加密数据中恢复通关密语及RSA体制参数。

2. 实验目的

在对RSA的破解实战中,深入体会RSA的参数选取和加解密过程,加深对公共模数攻击、Pollard攻击、低加密指数攻击、因数碰撞攻击等常见攻击方法的理解。

题目描述

现有一个RSA加解密软件,每次最多加密8个明文字符,超过8则要分片。分片明文要按指定规则填充,在填充过程中需要加入通信序号。我们可以截获加密数据帧,数据帧包括模数N、加密指数e和密文c。我们要仅利用加密数据恢复出明文和RSA体制系数。

Alice发送了21个加密数据帧,但有的明文分片被重复加密,有的参数选取不当,我们可以针对这些缺陷进行攻击。

过程

1. 数据提取

先从数据帧中提取出模数N,加密指数e和密文c。

    N_list = []
    c_list = []
    e_list = []
    for i in range(21):
        with open("frame_set/Frame" + str(i), "r") as f:
            tmp = f.read()
            N_list.append(tmp[0:256])
            e_list.append(tmp[256:512])
            c_list.append(tmp[512:768])

可以看到加密指数e的取值有以下几种

当e较小时,存在广播攻击,经过尝试,发现当e=5的时候存在广播攻击,对应的帧为Frame3,Frame8,Frame12,Frame16和Frame20。

2. 广播攻击

原理如下

在RSA中e被称为加密指数。由于e是可以随意选取的,选取小一点的e可以缩短加密时间,但是如果选取不当,就会带来安全问题。

 代码如下

def chinese_remainder_theorem(items):
    N = 1
    for a, n in items:
        N *= n
        result = 0
    for a, n in items:
        m = N // n
        d, r, s = gcd(n, m)
        if d != 1:
            N = N // n
            continue
        result += a * s * m
return result % N, N

def small_e_5():
    sessions = [{"c": int(c_list[3], 16), "n": int(N_list[3], 16)},
                {"c": int(c_list[8], 16), "n": int(N_list[8], 16)},
                {"c": int(c_list[12], 16), "n": int(N_list[12], 16)},
                {"c": int(c_list[16], 16), "n": int(N_list[16], 16)},
                {"c": int(c_list[20], 16), "n": int(N_list[20], 16)}]
    data = []
    for session in sessions:
        data = data + [(session['c'], session['n'])]
    x, y = chinese_remainder_theorem(data)
    # 直接开五次方根
    plaintext3_8_12_16_20 = gmpy2.iroot(gmpy2.mpz(x), 5)
    return binascii.a2b_hex(hex(plaintext3_8_12_16_20[0])[2:])

结果为

3. 公共模数攻击

进一步分析,发现Frame0和Frame4的模数N相同,根据题意我们假设它们是对同一明文分片的重复加密。

原理如下

 代码如下

# 公共模数攻击
def same_modulus():
    # 寻找公共模数
    index1 = 0
    index2 = 0
    for i in range(21):
        for j in range(i + 1, 21):
            if N_list[i] == N_list[j]:
                print('Same modulus found!' + str((N_list[i], N_list[j])))
                index1, index2 = i, j
    e1 = int(e_list[index1], 16)
    e2 = int(e_list[index2], 16)
    n = int(N_list[index1], 16)
    c1 = int(c_list[index1], 16)
    c2 = int(c_list[index2], 16)
    s = gcd(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
    print(m)
    print(binascii.a2b_hex(hex(m)[2:]))
    result = binascii.a2b_hex(hex(m)[2:])
    return result

结果为

4. 因数碰撞攻击

之后剩余的均是e=65537的数据帧,要安全很多。继续分析模数N。

在不同的加密过程中,选择的p,q不能一样,否则利用最大公约数可以轻易分解n。在这里Frame1和Frame18的模数N具有公共因子,可以通过因数碰撞法恢复明文。

代码如下

def same_factor():
    plaintext = []
    index = []
    for i in range(21):
        for j in range(i + 1, 21):
            if int(N_list[i], 16) == int(N_list[j], 16):
                continue
            prime = gmpy2.gcd(int(N_list[i], 16), int(N_list[j], 16))
            if prime != 1:
                print((N_list[i], N_list[j]))
                print((i, j))
                index.append(i)
                index.append(j)
                p_of_frame = prime
    q_of_frame1 = int(N_list[index[0]], 16) // p_of_frame
    q_of_frame18 = int(N_list[index[1]], 16) // p_of_frame
    print(p_of_frame)
    print(q_of_frame1, q_of_frame18)

    phi_of_frame1 = (p_of_frame - 1) * (q_of_frame1 - 1)
    phi_of_frame18 = (p_of_frame - 1) * (q_of_frame18 - 1)

    d_of_frame1 = gmpy2.invert(int(e_list[index[0]], 16), phi_of_frame1)
    d_of_frame18 = gmpy2.invert(int(e_list[index[1]], 16), phi_of_frame18)

    plaintext_of_frame1 = gmpy2.powmod(int(c_list[index[0]], 16), d_of_frame1, int(N_list[index[0]], 16))
    plaintext_of_frame18 = gmpy2.powmod(int(c_list[index[1]], 16), d_of_frame18, int(N_list[index[1]], 16))

    final_plain_of_frame1 = binascii.a2b_hex(hex(plaintext_of_frame1)[2:])
    final_plain_of_frame18 = binascii.a2b_hex(hex(plaintext_of_frame18)[2:])

    plaintext.append(final_plain_of_frame1)
    plaintext.append(final_plain_of_frame18)

    return plaintext

结果为

5. Fermat攻击

不仅仅是在加密过程中选择的p,q不能一样,还有p,q的取值不能相差太近或太大,否则利用Pollard p-1算法可以快速分解出p,q相差太大的n,利用Fermat算法可以快速分解p,q相差太近的n。

原理如下

 代码如下

def pq(n):
    B = math.factorial(2 ** 14)
    u = 0;
    v = 0;
    i = 0
    u0 = gmpy2.iroot(n, 2)[0] + 1
    while (i <= (B - 1)):
        u = (u0 + i) * (u0 + i) - n
        if gmpy2.is_square(u):
            v = gmpy2.isqrt(u)
            break
        i = i + 1
    p = u0 + i + v
    return p

def fermat_resolve():
    for i in range(10, 14):
        N = int(N_list[i], 16)
        p = pq(N)
        print(p)

def get_content_of_frame10():
    p = 9686924917554805418937638872796017160525664579857640590160320300805115443578184985934338583303180178582009591634321755204008394655858254980766008932978699
    n = int(N_list[10], 16)
    c = int(c_list[10], 16)
    e = int(e_list[10], 16)
    q = n // p
    phi_of_frame10 = (p - 1) * (q - 1)
    d = gmpy2.invert(e, phi_of_frame10)
    m = gmpy2.powmod(c, d, n)
    final_plain = binascii.a2b_hex(hex(m)[2:])
    return final_plain

 结果为

 6. Pollard p-1分解法

正如上文所说利用Pollard p-1算法可以快速分解出p,q相差太大的n。

原理如下

给定:整数n(已知是合数)

目标:找到一个因子d|n

步骤:

 代码如下

def pp1(n):
    B = 2 ** 20
    a = 2
    for i in range(2, B + 1):
        a = pow(a, i, n)
        d = gmpy2.gcd(a - 1, n)
        if (d >= 2) and (d <= (n - 1)):
            q = n // d
            n = q * d
    return d

def pollard_resolve():
    index_list = [2, 6, 19]
    plaintext = []
    for i in range(3):
        N = int(N_list[index_list[i]], 16)
        c = int(c_list[index_list[i]], 16)
        e = int(e_list[index_list[i]], 16)
        p = pp1(N)
        print("p of " + str(index_list[i]) + " is : " + str(p))
        q = N // p
        phi_of_frame = (p - 1) * (q - 1)
        d = gmpy2.invert(e, phi_of_frame)
        m = gmpy2.powmod(c, d, N)
        plaintext.append(binascii.a2b_hex(hex(m)[2:]))
    return plaintext

结果为

至此,还剩下几个数据帧没有恢复出明文。可以利用暴力破解,猜测攻击,字典攻击等手段解决。事实上这是一句名言,我们可以根据语言本身的意义得到通关密钥。

明文如下

"My secret is a famous saying of Albert Einstein. That is \"Logic will get you from A to B. Imagination will take you everywhere.\""

总结

在对RSA的破解实战中,深入体会了RSA的参数选取和加解密过程,加深了对公共模数攻击、Pollard攻击、低加密指数攻击、因数碰撞攻击等常见攻击方法的理解。认识到了即使是RSA也是有缺陷的,我们要严格选取RSA体制参数,提高安全性。

参考文献

RSA 大礼包 - Tr0y's Blog

【技术分享】CTF中RSA的常见攻击方法 - 安全客,安全资讯平台

RSA Breaking | B1ank

 


版权声明:本文为TWELVEPEERSS原创文章,遵循CC 4.0 BY-SA版权协议,转载请附上原文出处链接和本声明。