XOR加密的已知明文攻击深度解析

admin 2026-01-14 23:37:12 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 文档解析了CTF中XOR加密的已知明文攻击,分析了已知前缀、零填充及密钥循环复用等缺陷。文章演示了利用已知明文恢复密钥并解密Flag的过程,阐述了XOR原理与攻击策略,并给出了使用标准算法、避免密钥重用及安全填充的建议。 综合评分: 100 文章分类: CTF,漏洞分析,代码审计


cover_image

XOR加密的已知明文攻击深度解析

原创

破镜安全

破镜安全

2026年1月14日 08:00 四川

XOR加密的已知明文攻击深度解析

前言

在CTF密码学竞赛中,XOR加密是一个经典的考点。虽然XOR运算本身非常简单,但如果使用不当,会留下严重的安全漏洞。本文将通过一道实战题目,深入剖析XOR加密的工作原理、安全缺陷以及如何利用已知明文攻击来破解密文。

题目分析

题目文件

题目提供了一个Python加密脚本task.py:

from secret import flag
from os import urandom

assert flag.startswith(b'XMan{' ) and flag.endswith(b'}')
key=urandom(16)

def padding(m):
    l = 16 - (len(m) % 16)
    return m+(b'\x00'*l)

flag=padding(flag)+urandom(10)

enc=b''
for i in range(len(flag)):
    enc+=(key[i%16]^flag[i]).to_bytes(1,'big')
print(enc.hex())

# d8db4398596f9123f9b70d6847ad6e14e1ef5ff6220cfe4f96c5520731c81c78a645cdd1aa9b95e54468

初步观察

从代码中我们可以提取以下关键信息:

  1. flag格式约束:flag必须以XMan{开头,以}结尾
  2. 密钥生成:使用urandom(16)生成16字节的随机密钥
  3. 填充机制:使用\x00字节将flag填充到16字节的整数倍
  4. 随机数据:在填充后的flag末尾添加10字节随机数据
  5. 加密方式:使用16字节密钥循环与明文进行XOR运算
  6. 密文输出:十六进制格式的密文字符串

密文结构分析

给定的密文为:

d8db4398596f9123f9b70d6847ad6e14e1ef5ff6220cfe4f96c5520731c81c78a645cdd1aa9b95e54468

让我们先分析密文的长度和结构:

enc_hex = "d8db4398596f9123f9b70d6847ad6e14e1ef5ff6220cfe4f96c5520731c81c78a645cdd1aa9b95e54468"
enc = bytearray.fromhex(enc_hex)
print(f"密文总长度: {len(enc)} 字节")
# 输出: 密文总长度: 42 字节

42字节的密文可以分为三个部分:

  • 第一部分(0-15字节):前16字节,对应第一个密钥循环周期
  • 第二部分(16-31字节):中间16字节,对应第二个密钥循环周期
  • 第三部分(32-41字节):最后10字节,这是添加的随机数据

通过分析可知,实际需要解密的内容是前32字节(两个完整的16字节块),最后10字节是随机数据,不影响flag的恢复。

XOR加密原理

XOR运算基础

XOR(异或)是一种位运算,符号为。其真值表如下:

| A | B | A ⊕ B | | — | — | — | | 0 | 0 | 0 | | 0 | 1 | 1 | | 1 | 0 | 1 | | 1 | 1 | 0 |

简单来说:相同为0,不同为1

XOR运算的重要性质

XOR运算具有以下数学性质,这些性质是密码分析的关键:

  1. 可逆性A ⊕ B = C 则 A ⊕ C = B 且 B ⊕ C = A
  2. 自反性A ⊕ A = 0
  3. 恒等性A ⊕ 0 = A
  4. 交换律A ⊕ B = B ⊕ A
  5. 结合律(A ⊕ B) ⊕ C = A ⊕ (B ⊕ C)

让我们用Python验证这些性质:

# 可逆性验证
A, B = 0x58, 0x80  # 'X' 和某个密钥字节
C = A ^ B          # 加密
print(f"A ^ B = {C:#x}")
print(f"C ^ B = {C ^ B:#x} (恢复A)")
print(f"C ^ A = {C ^ A:#x} (恢复B)")

# 恒等性验证
print(f"A ^ 0 = {A ^ 0:#x} (等于A)")

本题的加密流程

让我们详细分析task.py中的加密流程:

步骤1:生成密钥

key = urandom(16)  # 生成16字节随机密钥

步骤2:填充明文

def padding(m):
    l = 16 - (len(m) % 16)
    return m + (b'\x00' * l)

这个函数计算需要填充的字节数,使明文长度成为16的倍数。

例如,如果flag长度为19字节,那么:

l = 16 - (19 % 16) = 16 - 3 = 13
# 需要填充13个\x00字节,使总长度为32字节

步骤3:添加随机数据

flag = padding(flag) + urandom(10)

在填充后的flag末尾再添加10字节随机数据。

步骤4:XOR加密

enc = b''
for i in range(len(flag)):
    enc += (key[i%16] ^ flag[i]).to_bytes(1, 'big')

这是核心加密逻辑。对于每个明文字节:

  • 使用i % 16来循环使用16字节密钥
  • 将密钥字节与明文字节进行XOR运算
  • 将结果转换为单字节并添加到密文中

加密过程示意图:

位置:    0    1    2    3    4    5   ...  15   16   17   18  ...
明文:   'X'  'M'  'a'  'n'  '{'  ...  ...  ... 'a'  'y'  '}' ...
密钥:   k0   k1   k2   k3   k4   k5  ...  k15  k0   k1   k2  ...
密文:   c0   c1   c2   c3   c4   c5  ...  c15  c16  c17  c18 ...

可以看到,密钥每16字节循环一次。

安全漏洞分析

漏洞1:已知明文前缀

题目代码中有一个关键的断言:

assert flag.startswith(b'XMan{')

这意味着flag的前5个字节是已知的:XMan{。这是一个严重的安全问题。

根据XOR的可逆性,如果我们知道明文和密文,就能计算出密钥:

密钥[i] = 明文[i] ⊕ 密文[i]

因此,我们可以直接恢复密钥的前5个字节:

密钥[0] = 'X' ⊕ 密文[0]
密钥[1] = 'M' ⊕ 密文[1]
密钥[2] = 'a' ⊕ 密文[2]
密钥[3] = 'n' ⊕ 密文[3]
密钥[4] = '{' ⊕ 密文[4]

漏洞2:可预测的填充

填充函数使用固定的\x00字节进行填充:

return m + (b'\x00' * l)

这是另一个致命漏洞。根据XOR的恒等性:

密钥 ⊕ 0x00 = 密钥

这意味着,当明文是\x00时,密文直接等于密钥本身。如果我们能找到密文中哪些位置对应填充的\x00,就能直接读取密钥。

漏洞3:密钥循环使用

加密代码中使用i % 16来循环使用密钥:

enc += (key[i%16] ^ flag[i]).to_bytes(1, 'big')

这意味着:

  • 位置0和位置16使用相同的密钥字节k0
  • 位置1和位置17使用相同的密钥字节k1
  • 以此类推

密钥循环使用扩大了攻击面。一旦我们恢复了密钥的某个字节,就能解密所有使用该密钥字节的位置。

攻击策略设计

综合以上三个漏洞,我们可以设计出完整的攻击方案。

关键观察

首先,我们需要理解明文的结构。密文长度为42字节,减去最后10字节随机数,实际加密的明文是32字节。

这32字节正好是两个16字节块,说明flag经过padding后的长度是32字节。

明文结构分析:

[0-15字节]:第一块,包含 XMan{ + flag的一部分
[16-31字节]:第二块,包含 flag的剩余部分 + } + 填充的\x00
[32-41字节]:随机数据(不需要解密)

攻击步骤

步骤1:恢复密钥的前5字节

利用已知明文XMan{,通过XOR运算恢复密钥的前5字节。

步骤2:恢复密钥的后11字节

关键推理:第二块(16-31字节)中,后半部分很可能是填充的\x00。由于密钥 ⊕ 0x00 = 密钥,我们可以从密文中直接读取密钥的后11字节。

具体来说,密钥的索引5-15对应密文的索引21-31(16+5到16+15)。

步骤3:使用完整密钥解密

有了完整的16字节密钥,我们就可以解密前32字节的密文,恢复出完整的flag。

完整解题过程

实战演练

让我们按照攻击策略,一步步实现解密过程。

第一步:读取并解析密文

enc = bytearray.fromhex("d8db4398596f9123f9b70d6847ad6e14e1ef5ff6220cfe4f96c5520731c81c78a645cdd1aa9b95e54468")
print(f"密文总长度: {len(enc)} 字节")
print(f"前16字节: {enc[0:16].hex()}")
print(f"中16字节: {enc[16:32].hex()}")
print(f"后10字节: {enc[32:42].hex()}")

输出结果:

密文总长度: 42 字节
前16字节: d8db4398596f9123f9b70d6847ad6e14
中16字节: e1ef5ff6220cfe4f96c5520731c81c78
后10字节: a645cdd1aa9b95e54468

第二步:恢复密钥的前5字节

known_prefix = b'XMan{'
key = [0] * 16

for i in range(len(known_prefix)):
    key[i] = enc[i] ^ known_prefix[i]
    print(f"密钥[{i}] = 0x{enc[i]:02x} ^ 0x{known_prefix[i]:02x} = 0x{key[i]:02x}")

输出结果:

密钥[0] = 0xd8 ^ 0x58 = 0x80
密钥[1] = 0xdb ^ 0x4d = 0x96
密钥[2] = 0x43 ^ 0x61 = 0x22
密钥[3] = 0x98 ^ 0x6e = 0xf6
密钥[4] = 0x59 ^ 0x7b = 0x22

第三步:恢复密钥的后11字节

for i in range(5, 16):
    key[i] = enc[16 + i]
    print(f"密钥[{i}] = 密文[{16+i}] = 0x{key[i]:02x}")

输出结果:

密钥[5] = 密文[21] = 0x0c
密钥[6] = 密文[22] = 0xfe
密钥[7] = 密文[23] = 0x4f
密钥[8] = 密文[24] = 0x96
密钥[9] = 密文[25] = 0xc5
密钥[10] = 密文[26] = 0x52
密钥[11] = 密文[27] = 0x07
密钥[12] = 密文[28] = 0x31
密钥[13] = 密文[29] = 0xc8
密钥[14] = 密文[30] = 0x1c
密钥[15] = 密文[31] = 0x78

第四步:查看完整密钥

print(f"完整密钥: {bytes(key).hex()}")

输出:

完整密钥: 809622f6220cfe4f96c5520731c81c78

第五步:解密前32字节

flag = b''
for i in range(32):
    plaintext_byte = key[i % 16] ^ enc[i]
    flag += plaintext_byte.to_bytes(1, 'big')

print(f"解密结果: {flag}")

输出:

解密结果: b'XMan{color_overlay}\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00'

第六步:去除填充并输出flag

flag_clean = flag.rstrip(b'\x00')
print(f"Flag: {flag_clean.decode()}")

输出:

Flag: XMan{color_overlay}

成功获取flag!

简洁的解题脚本

基于以上分析,我们可以编写一个更简洁的解题脚本:

with open('task.py', 'r') as f:
    for _ in range(17): f.readline()
    enc = bytearray.fromhex(f.readline()[2:])

flag, flag0, n = b'', b'XMan{', 16
key = list(flag0 + enc[len(flag0) + n:n * 2])

for i in range(len(flag0)):
    key[i] ^= enc[i]

for i in range(n * 2):
    flag += (key[i % n] ^ enc[i]).to_bytes()

print(flag.rstrip(b'\0').decode())

代码解析

这个简洁脚本的核心思想:

构造密钥数组

key = list(flag0 + enc[len(flag0) + n:n * 2])

这行代码巧妙地构造了一个初始密钥数组:

  • 前5字节:已知明文XMan{
  • 后11字节:密文的索引21-31(enc[21:32]

修正密钥前5字节

for i in range(len(flag0)):
    key[i] ^= enc[i]

将前5字节的明文与密文异或,得到真实的密钥。

技术总结

已知明文攻击(Known-Plaintext Attack)

已知明文攻击是密码分析中的经典方法。攻击者拥有一些明文-密文对,通过分析这些对应关系来推导密钥。

在XOR加密中,这种攻击特别有效,因为:

密钥 = 明文 ⊕ 密文

只要知道任意位置的明文和密文,就能直接计算出该位置的密钥。

填充预言(Padding Oracle)

本题虽然不是典型的Padding Oracle攻击,但利用了类似的思想:通过已知的填充模式获取额外信息。

当明文是\x00时,密文直接等于密钥。这使得我们可以从密文中直接读取密钥的部分字节。

密钥流重用问题

密钥循环使用是本题的核心漏洞。16字节密钥重复使用意味着:

  • 位置0和位置16使用相同的密钥字节
  • 一旦恢复某个密钥字节,就能解密所有使用该密钥字节的位置

安全建议

1. 使用标准加密算法

不要自己实现加密算法,应使用经过验证的标准算法:

  • 对称加密:AES-256-GCM、ChaCha20-Poly1305
  • 非对称加密:RSA-2048/4096、ECC

2. 避免密钥重用

如果必须使用XOR加密:

  • 密钥长度应至少等于明文长度(一次一密)
  • 每个密钥只使用一次
  • 使用密码学安全的随机数生成器(CSPRNG)

3. 使用安全的填充方案

  • 使用PKCS#7等标准填充方案
  • 或使用不需要填充的加密模式(如CTR模式、GCM模式)
  • 避免使用固定字节(如\x00)进行填充

4. 添加消息认证

加密不等于认证,应使用认证加密(AEAD)方案:

  • 使用HMAC对密文进行签名
  • 或使用集成认证的加密模式(如AES-GCM)
  • 防止密文被篡改或伪造

5. 避免可预测的明文格式

  • 不要使用固定的flag格式
  • 在明文前添加随机盐值
  • 使用随机化的初始向量(IV)

总结

本题是一道经典的XOR加密已知明文攻击题目。通过深入分析,我们发现了三个关键漏洞:

  1. 已知明文前缀:flag格式XMan{提供了5字节已知明文
  2. 可预测的填充:使用\x00填充,使得密文直接暴露密钥
  3. 密钥循环使用:16字节密钥重复使用,扩大了攻击面

利用这些漏洞,我们成功恢复了完整的16字节密钥,并解密出flag:XMan{color_overlay}

这道题目深刻展示了密码学中的一个重要原则:加密算法的安全性不仅取决于算法本身,更取决于实现方式和使用场景。

即使是简单的XOR加密,如果使用得当(如一次一密),也可以达到理论上不可破解的安全性。但如果使用不当,就会留下致命的安全漏洞。

对于CTF密码学题目的学习者来说,本题提供了一个很好的入门案例,展示了如何:

  • 分析加密算法的实现细节
  • 识别潜在的安全漏洞
  • 利用已知信息进行密码分析
  • 编写Python脚本实现攻击

希望本文能帮助读者更好地理解XOR加密的原理和安全性问题,在今后的CTF比赛和实际应用中能够识别和防范类似的安全风险。


免责声明:

本文所载程序、技术方法仅面向合法合规的安全研究与教学场景,旨在提升网络安全防护能力,具有明确的技术研究属性。

任何单位或个人未经授权,将本文内容用于攻击、破坏等非法用途的,由此引发的全部法律责任、民事赔偿及连带责任,均由行为人独立承担,本站不承担任何连带责任。

本站内容均为技术交流与知识分享目的发布,若存在版权侵权或其他异议,请通过邮件联系处理,具体联系方式可点击页面上方的联系我

本文转载自:破镜安全 破镜安全《XOR加密的已知明文攻击深度解析》

2026年网络安全十大趋势 网络安全文章

2026年网络安全十大趋势

文章总结: 本文档概述了奇安信虎符智库发布的2026年网络安全十大趋势,旨在预测未来行业技术演进与风险挑战。内容通过图片形式展示具体观点,涵盖AI安全、数据保护
评论:0   参与:  0