likes
comments
collection
share

分享picoCTF中一道数据加解密题目

作者站长头像
站长
· 阅读数 40

分享picoCTF中一道数据加解密题目

最近开始刷 picoCTF 2024,给自己定了个小目标:不论难易每天解出1题,如果查看提示或别人的答案,则不算数,并且还要加罚1题。

picoCTF比较适合新手入门,题目难度是循序渐进的,picoCTF2024也不例外,前几道题都比较简单,没什么好说的。今天分享一下第15题,这道题不算太难但是有点意思。该题的解出人数是前15题里最少的,但是点赞率(Liked)并不低,说明它得到了CTFer们的认可,有兴趣的同学可以自己先试着解一下。

1. 题目

这道题是关于数据加解密的,题中给出了密文数据(enc_flag)以及加密代码(custom_encryption.py),CTFer需要通过编写解密代码来获取Flag,分值100分。 分享picoCTF中一道数据加解密题目

2. 分析

首先阅读加密代码,可以得出核心的加密过程为:

  1. 输入为一串明文和一个text_key,其中text_key在代码中硬编码为"trudeau"
  2. 通过一系列的运算得到一个整数型的shared_key
  3. 通过dynamic_xor_encrypt函数用text_key将原始明文加密成一串半密文
  4. 通过encrypt函数用shared_key将半密文加密成最终密文

上述加密过程可以描述为下图: 分享picoCTF中一道数据加解密题目

容易想到,逆转上述过程即可实现解密,过程如下图所示: 分享picoCTF中一道数据加解密题目 其中,decrypt函数为encrypt函数的逆操作,dynamic_xor_decrypt函数为dynamic_xor_encrypt函数的逆操作,text_key和shared_key既可用于加密也可用于解密(对称密钥)。

3. 解答

根据上节的分析可知,解密有4个关键要素:

  • text_key:为已知
  • shared_key:需破解
  • decrypt函数:可根据encrypt函数实现
  • dynamic_xor_decrypt函数:可根据dynamic_xor_encrypt函数实现

破解shared_key

在test函数中,shared_key是通过一系列的计算得到的整数,其中用到两个中间随机数a和b。在enc_flag文件中记录了a和b,因此只需要在源码中对a和b直接赋值,运行代码即可获得shared_key = 12。

def test(plain_text, text_key):
    p = 97
    g = 31
    if not is_prime(p) and not is_prime(g):
        print("Enter prime numbers")
        return
    a = randint(p-10, p)
    b = randint(g-10, g)
    print(f"a = {a}")
    print(f"b = {b}")
    u = generator(g, a, p)
    v = generator(g, b, p)
    key = generator(v, a, p)
    b_key = generator(u, b, p)
    shared_key = None
    if key == b_key:
        shared_key = key
    else:
        print("Invalid key")
        return
    ......

实现decrypt函数

encrypt函数是将输入字符串的每个字符转换成unicode编码后乘以key*311,将该数学运算逆向后很容易得到decrypt函数。

def encrypt(plaintext, key):
    cipher = []
    for char in plaintext:
        cipher.append(((ord(char) * key*311)))
    return cipher
    
def decrypt(cipher, key):
    plain = ""
    for code in cipher:
        plain += chr(int(code/311/key))
    return plain

实现dynamic_xor_decrypt函数

dynamic_xor_encrypt函数是将输入字符串中每个字符的编码与text_key字符串中对应字符的编码进行异或运算,再转换成新的字符。因异或运算本身是可逆的(若A^k=A',则A=A'^k),推测该函数可以直接用于解密。

def dynamic_xor_encrypt(plaintext, text_key):
    cipher_text = ""
    key_length = len(text_key)
    for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        cipher_text += encrypted_char
    return cipher_text
    
def dynamic_xor_decrypt(cipher, text_key):
    return dynamic_xor_encrypt(cipher, text_key)

获取Flag

综合以上,用Python写出解密代码如下:

if __name__ == "__main__":
    # 待解密的密文,来自enc_flag文件
    cipher = [33588, 276168, ..., 63444]
    # 解密成半密文
    shared_key = 12
    semi_cipher = decrypt(cipher, shared_key)
    # 解密成明文
    text_key = "trudeau"
    plain = dynamic_xor_decrypt(semi_cipher, text_key)
    # 打印明文
    print(f"plain is: {plain}")

运行上述Python代码得到一串字符:

plain is: e~r~TAFntdbczmJs#re%pa!uNsv4,.$"/h

WTF,怎么回事?picoCTF的Flag应该是picoCTF{......}这种形式的,很明显答案不正确。

4. 修正

那么问题出在哪里呢?仔细分析发现,dynamic_xor_encrypt函数在进行异或运算前先对明文做了一次反转操作(plaintext[::-1]),关键代码如下。当直接用该函数进行解密时,会出现异或运算所用的key_char与加密时的不对应,解密结果当然不正确。

for i, char in enumerate(plaintext[::-1]):
        key_char = text_key[i % key_length]
        encrypted_char = chr(ord(char) ^ ord(key_char))
        ......

为了便于理解这个过程,我重新画了一个加密过程的图,图中虚线箭头的方向为执行异或运算的顺序:

分享picoCTF中一道数据加解密题目

为保证加解密的异或运算所用的key_char保持一致,dynamic_xor_decrypt函数需要做些调整:在异或运算前不要对半密文(c'b'a')做反转操作(cipher[::]),而在异或运算完成后对明文做一次反转操作(plain[::-1])。

def dynamic_xor_decrypt(cipher, text_key):
    plain = ""
    key_length = len(text_key)
    for i, char in enumerate(cipher[::]):
        key_char = text_key[i % key_length]
        decrypted_char = chr(ord(char) ^ ord(key_char))
        plain += decrypted_char
    return plain[::-1]

修正dynamic_xor_decrypt函数后再次执行解密,便可得到正确答案:

plain is: picoCTF{custom_d2cr0pt6d_dc499538}

这算是题目的一个小坑,容易被忽视。

5. 总结

picoCTF比较适合新手入门,题目难度是循序渐进的,本文分享了picoCTF-2024中一道数据加解密相关的问题,这道题不算太难,但是容易踩坑,值得CTFer动手做一下。

转载自:https://juejin.cn/post/7375532797383393289
评论
请登录