easy_BlockCipher–DES弱密钥攻击完整技术解析

admin 2025-12-22 03:45:57 网络安全文章 来源:ZONE.CI 全球网 0 阅读模式

文章总结: 本文详细解析了CTF题目easy_BlockCipher的DES弱密钥攻击方法,包括DES算法原理、OFB工作模式、弱密钥产生机制以及完整的解密过程。文章通过分析加密脚本,识别出使用DES-OFB模式加密的密文,并利用DES的四个弱密钥进行攻击尝试,最终发现使用了弱密钥0xE1E1E1E1F0F0F0F0,成功解密得到flag。文章不仅提供了完整的Python解密代码,还深入解析了代码技术细节,并给出了密码学安全建议,包括避免使用弱密钥、选择现代加密算法等。 综合评分: 91 文章分类: 漏洞分析,CTF,渗透测试


cover_image

easy_BlockCipher – DES弱密钥攻击完整技术解析

原创

破镜安全

破镜安全

2025年12月21日 08:01 四川

easy_BlockCipher – DES弱密钥攻击完整技术解析

一、题目背景与初步分析

1.1 题目文件清单

本题提供了两个关键文件:

  1. des-ofb.py – 加密脚本(343字节)
  2. ciphertext – 加密后的密文(632字节)

我们的任务是在没有密钥的情况下,解密密文获取flag。

1.2 加密代码分析

首先仔细阅读题目提供的加密脚本:

from Crypto.Cipher import DES

f = open('key.txt', 'r')
key_hex = f.readline()[:-1]  # discard newline
f.close()
KEY = key_hex.decode("hex")
IV = '13245678'
a = DES.new(KEY, DES.MODE_OFB, IV)

f = open('plaintext', 'r')
plaintext = f.read()
f.close()

ciphertext = a.encrypt(plaintext)
f = open('ciphertext', 'w')
f.write(ciphertext)
f.close()

通过代码分析,我们可以提取以下关键信息:

| 要素 | 值 | 状态 | | — | — | — | | 加密算法 | DES(Data Encryption Standard) | 已知 | | 工作模式 | OFB(Output Feedback Mode) | 已知 | | 初始化向量(IV) | ‘13245678’ | 已知 | | 密钥(KEY) | 从key.txt读取(16进制格式) | 未知 | | 密文 | ciphertext文件 | 已知 | | 明文 | plaintext文件 | 未知 |

关键问题:密钥文件key.txt未提供,这意味着我们需要在不知道密钥的情况下破解密文。

1.3 初步思路分析

对于DES加密,有以下几种可能的攻击方向:

  1. 暴力破解:DES使用56位有效密钥,理论上有2^56(约7.2×10^16)种可能,现代计算机可以在合理时间内暴力破解,但对于CTF题目来说时间成本太高。
  2. 字典攻击:如果密钥是常见字符串,可以尝试字典攻击,但成功率不确定。
  3. 算法缺陷利用:DES存在一些已知的安全缺陷,例如弱密钥、半弱密钥等。
  4. 侧信道攻击:通过时间、功耗等侧信道信息获取密钥,但题目环境不支持。

考虑到这是一道可解的CTF题目,最有可能的突破点是DES算法本身的安全缺陷,特别是弱密钥问题。

二、DES加密算法深度解析

2.1 DES算法概述

DES(Data Encryption Standard)是1977年由美国国家标准局(NBS,现NIST)发布的对称加密标准。它是一种分组密码,将64位明文块加密成64位密文块。

关键参数

  • 明文块大小:64位(8字节)
  • 密文块大小:64位(8字节)
  • 密钥长度:64位(其中56位有效,8位用于奇偶校验)

2.2 DES加密流程详解

DES的加密过程可以分为以下几个阶段:

明文(64位)
    ↓
初始置换(IP)
    ↓
16轮Feistel运算
    ↓
逆初始置换(IP^-1)
    ↓
密文(64位)

阶段1:初始置换(IP)

将64位输入按照固定的置换表重新排列,这一步是纯粹的位置换,没有密钥参与。

阶段2:16轮Feistel运算

这是DES的核心部分。每一轮的运算过程如下:

  1. 将64位数据分为左右两部分(L和R),各32位
  2. 对右半部分R进行扩展置换(E),从32位扩展到48位
  3. 将扩展后的R与48位子密钥进行异或运算
  4. 将异或结果分成8组,每组6位,分别输入8个S盒(替代盒)
  5. 每个S盒将6位输入转换为4位输出,共得到32位
  6. 对S盒输出进行P置换(置换盒)
  7. 将置换结果与左半部分L进行异或
  8. 交换左右两部分,进入下一轮

关键点:每一轮使用一个48位的子密钥,16轮共需要16个子密钥。

阶段3:密钥编排算法

这是本题的核心关注点。DES如何从56位主密钥生成16个48位子密钥?

密钥编排步骤

原始密钥(64位,包含8位奇偶校验位)
    ↓
PC-1置换(Permuted Choice 1)
提取56位有效密钥,分成C0和D0两部分,各28位
    ↓
第1轮:
  C1 = LS1(C0)  // 循环左移1位
  D1 = LS1(D0)
  K1 = PC-2(C1, D1)  // 从56位中选择48位
    ↓
第2轮:
  C2 = LS1(C1)
  D2 = LS1(D1)
  K2 = PC-2(C2, D2)
    ↓
...(依此类推,某些轮左移2位)
    ↓
第16轮:
  K16 = PC-2(C16, D16)

循环左移规则

  • 第1, 2, 9, 16轮:左移1位
  • 其他轮:左移2位

2.3 DES弱密钥的产生机制

现在我们来分析弱密钥是如何产生的。

弱密钥的定义: 在DES中,如果一个密钥使得16个子密钥完全相同(或具有某种对称性),则称为弱密钥。这会导致加密函数具有自反性:E_K(E_K(P)) = P

为什么会产生弱密钥?

当主密钥具有特殊的位模式时,在密钥编排过程中会出现以下情况:

  1. 全0或全1密钥
  • 密钥 = 0x0000000000000000
  • 经过PC-1后,C0 = 0x0000000, D0 = 0x0000000(各28位全0)
  • 无论如何循环左移,C_i和D_i始终为全0
  • 因此K1 = K2 = … = K16,所有子密钥相同
  1. 特殊对称模式密钥
  • 密钥 = 0xE1E1E1E1F0F0F0F0
  • 这个密钥的二进制表示具有特殊的对称性
  • 在循环左移后,每28位的C和D会回到原始状态
  • 导致所有子密钥相同

DES的四个弱密钥

| 序号 | 十六进制值 | 二进制特征 | | — | — | — | | 1 | 0x0000000000000000 | 全0 | | 2 | 0xFFFFFFFFFFFFFFFF | 全1 | | 3 | 0x1E1E1E1E0F0F0F0F | 交替模式 | | 4 | 0xE1E1E1E1F0F0F0F0 | 交替模式 |

让我们详细分析第四个弱密钥(0xE1E1E1E1F0F0F0F0)的位模式:

0xE1 = 11100001
0xF0 = 11110000

完整密钥(去除奇偶校验位后):
1110000 1110000 1110000 1110000 1111000 1111000 1111000 1111000

这种模式在DES的密钥编排中具有特殊性质,使得循环左移后仍保持相同的子密钥生成模式。

2.4 弱密钥的安全影响

弱密钥导致的安全问题:

  1. 自反性:使用弱密钥加密两次等于解密,即E_K(E_K(M)) = M
  2. 密钥空间缩减:攻击者只需尝试4个弱密钥,而不是2^56个密钥
  3. 可预测性:弱密钥的存在是公开知识,任何人都可以利用

三、OFB模式工作原理详解

3.1 分组密码工作模式概述

DES是一种分组密码,每次只能加密64位数据。当需要加密更长的消息时,需要使用工作模式。常见的工作模式包括:

  • ECB(Electronic Codebook Mode):电子密码本模式
  • CBC(Cipher Block Chaining Mode):密码块链接模式
  • CFB(Cipher Feedback Mode):密码反馈模式
  • OFB(Output Feedback Mode):输出反馈模式
  • CTR(Counter Mode):计数器模式

本题使用的是OFB模式

3.2 OFB模式详细工作流程

OFB模式将分组密码转换为同步流密码。其工作流程如下:

加密过程

初始化:
I_1 = IV(初始化向量)

对于每个明文块 P_i:
1. O_i = E_K(I_i)          // 使用密钥K加密输入块I_i
2. C_i = P_i ⊕ O_i         // 明文与加密输出异或得到密文
3. I_{i+1} = O_i           // 将加密输出作为下一轮的输入

输出密文:C_1, C_2, C_3, ...

解密过程

初始化:
I_1 = IV(初始化向量,必须与加密时相同)

对于每个密文块 C_i:
1. O_i = E_K(I_i)          // 使用密钥K加密输入块I_i(注意:是加密,不是解密!)
2. P_i = C_i ⊕ O_i         // 密文与加密输出异或得到明文
3. I_{i+1} = O_i           // 将加密输出作为下一轮的输入

输出明文:P_1, P_2, P_3, ...

图解OFB模式

加密:
    IV → [DES加密] → O_1 ⊕ P_1 → C_1
          ↓
         O_1 → [DES加密] → O_2 ⊕ P_2 → C_2
                ↓
               O_2 → [DES加密] → O_3 ⊕ P_3 → C_3
                      ↓
                     ...

解密(过程完全相同):
    IV → [DES加密] → O_1 ⊕ C_1 → P_1
          ↓
         O_1 → [DES加密] → O_2 ⊕ C_2 → P_2
                ↓
               O_2 → [DES加密] → O_3 ⊕ C_3 → P_3
                      ↓
                     ...

3.3 OFB模式的重要特性

  1. 加密和解密使用相同的操作
  • 无论加密还是解密,都是使用加密函数E_K
  • 不需要解密函数D_K
  • 这与CBC等模式不同
  1. 不需要填充
  • OFB是流密码模式,可以处理任意长度的数据
  • 最后一个块可以只使用部分加密输出
  • 密文长度等于明文长度
  1. 错误传播特性
  • 密文中的一个位错误只影响明文中对应的一个位
  • 不会影响后续块的解密
  • 但如果IV错误,整个解密都会失败
  1. 需要唯一IV
  • 相同的密钥不能使用相同的IV
  • 否则会生成相同的密钥流,导致安全问题
  1. 可预生成密钥流
  • 密钥流的生成不依赖明文或密文
  • 可以提前计算O_1, O_2, O_3, …
  • 这在某些场景下可以提高性能

3.4 OFB模式在本题中的应用

在本题中:

  • IV = ‘13245678’(8字节,即64位)
  • 密钥 = 未知(8字节,即64位)
  • 密文长度 = 632字节

解密需要的信息:

  • IV(已知)
  • 密钥(未知,需要破解)

四、解题思路与策略

4.1 问题建模

现在我们来整理已知信息和未知信息:

已知

  • 加密算法:DES
  • 工作模式:OFB
  • IV:’13245678′
  • 密文:ciphertext(632字节)

未知

  • 密钥KEY

目标

  • 解密密文获取flag

4.2 攻击方向选择

基于前面的分析,我们有以下攻击选项:

| 攻击方法 | 可行性 | 所需资源 | 成功率 | | — | — | — | — | | 暴力破解 | 低 | 需要强大算力和时间 | 100%(但不实际) | | 字典攻击 | 中 | 需要好的字典 | 不确定 | | 弱密钥攻击 | 高 | 只需尝试4次 | 如果使用了弱密钥则100% | | 半弱密钥攻击 | 中 | 需要尝试12次 | 如果使用了半弱密钥则100% |

决策:先尝试弱密钥攻击,因为:

  1. 成本最低(只需4次尝试)
  2. CTF题目通常有明确解法
  3. 题目名称”easy_BlockCipher”暗示有简单的解法

4.3 攻击实施方案

方案设计

步骤1:读取密文文件
步骤2:准备4个DES弱密钥
步骤3:对每个弱密钥:
    3.1 使用该密钥和已知IV创建DES-OFB解密器
    3.2 解密密文
    3.3 尝试将解密结果解码为UTF-8文本
    3.4 检查是否包含'flag{'字符串
    3.5 如果成功,输出结果并停止
步骤4:如果所有弱密钥都失败,尝试其他方法

五、实战解密过程

5.1 环境准备

首先确保安装了必要的Python库:

pip install pycryptodome

在Python中,pycryptodome库提供了DES加密的完整实现。

5.2 解密脚本编写

让我们逐步编写解密脚本,并详细解释每一行代码:

#!/usr/bin/env python
# -*- coding: utf-8 -*-
"""
DES弱密钥攻击脚本
目标:使用4个已知的DES弱密钥尝试解密密文
"""

# 导入DES加密模块
from Crypto.Cipher import DES

# ========== 步骤1:读取密文 ==========
print("="*70)
print("步骤1:读取密文文件")
print("="*70)

# 以二进制模式打开密文文件('rb'表示read binary)
with open('ciphertext', 'rb') as f:
    ciphertext = f.read()

# 显示密文的基本信息
print(f"密文长度: {len(ciphertext)} 字节")
print(f"密文前50字节(十六进制): {ciphertext[:50].hex()}")

# ========== 步骤2:准备攻击数据 ==========
print("\n" + "="*70)
print("步骤2:准备DES弱密钥和IV")
print("="*70)

# 已知的IV(从加密代码中获取)
IV = '13245678'
print(f"IV(初始化向量): {IV}")
print(f"IV长度: {len(IV)} 字节")

# DES的四个弱密钥
# 使用元组存储(密钥字节串,密钥名称)方便后续输出
weak_keys = [
    (b'\x00\x00\x00\x00\x00\x00\x00\x00', '0x0000000000000000 (全0)'),
    (b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF', '0xFFFFFFFFFFFFFFFF (全1)'),
    (b'\x1E\x1E\x1E\x1E\x0F\x0F\x0F\x0F', '0x1E1E1E1E0F0F0F0F'),
    (b'\xE1\xE1\xE1\xE1\xF0\xF0\xF0\xF0', '0xE1E1E1E1F0F0F0F0')
]

print(f"\n准备尝试 {len(weak_keys)} 个DES弱密钥")

# ========== 步骤3:逐个尝试弱密钥 ==========
print("\n" + "="*70)
print("步骤3:使用弱密钥尝试解密")
print("="*70)

# 用于存储成功的密钥和明文
correct_key = None
plaintext = None

# 遍历所有弱密钥
for i, (KEY, key_name) in enumerate(weak_keys, 1):
    print(f"\n[尝试 {i}/{len(weak_keys)}] 密钥: {key_name}")
    print("-" * 70)

    try:
        # 创建DES解密器
        # 参数说明:
        #   - KEY: 8字节的密钥
        #   - DES.MODE_OFB: 使用OFB工作模式
        #   - IV.encode(): 将字符串IV转换为字节串
        cipher = DES.new(KEY, DES.MODE_OFB, IV.encode())

        # 执行解密
        # 在OFB模式下,decrypt()实际上执行的是:
        # 生成密钥流,然后与密文异或
        decrypted = cipher.decrypt(ciphertext)

        # 尝试将解密结果解码为UTF-8文本
        try:
            decoded_text = decrypted.decode('utf-8')

            # 检查解码后的文本是否可读
            # 如果是正确的明文,应该包含可打印字符
            print("解密成功,得到可读文本")
            print(f"明文长度: {len(decoded_text)} 字符")
            print(f"明文前100字符:\n{decoded_text[:100]}")

            # 检查是否包含flag标志
            if 'flag{' in decoded_text:
                print("\n" + "="*70)
                print("发现flag!这是正确的密钥!")
                print("="*70)

                # 保存正确的密钥和明文
                correct_key = KEY
                plaintext = decrypted

                # 提取并显示flag
                flag_start = decoded_text.find('flag{')
                flag_end = decoded_text.find('}', flag_start) + 1
                flag = decoded_text[flag_start:flag_end]
                print(f"\nFlag: {flag}")

                # 找到正确密钥后退出循环
                break
            else:
                print("未找到flag标志,可能不是正确的密钥")

        except UnicodeDecodeError:
            # 如果无法解码为UTF-8,说明解密结果是乱码
            print("无法解码为UTF-8文本 -> 不是正确的密钥")

    except Exception as e:
        # 捕获其他可能的错误
        print(f"解密过程出错: {e}")

# ========== 步骤4:验证解密正确性 ==========
if correct_key is not None:
    print("\n" + "="*70)
    print("步骤4:验证解密正确性(加密-解密可逆性测试)")
    print("="*70)

    # 使用正确的密钥重新加密明文
    # 如果得到的密文与原始密文相同,证明解密是正确的
    cipher_verify = DES.new(correct_key, DES.MODE_OFB, IV.encode())
    re_encrypted = cipher_verify.encrypt(plaintext)

    print(f"原始密文长度: {len(ciphertext)} 字节")
    print(f"重新加密后密文长度: {len(re_encrypted)} 字节")

    # 比较前50字节
    print(f"\n原始密文前50字节: {ciphertext[:50].hex()}")
    print(f"重新加密前50字节: {re_encrypted[:50].hex()}")

    # 完整比较
    if ciphertext == re_encrypted:
        print("\n验证成功!重新加密得到的密文与原始密文完全一致!")
        print("这证明我们的解密是100%正确的。")
    else:
        print("\n警告:验证失败!密文不一致。")

    # ========== 步骤5:保存和显示完整明文 ==========
    print("\n" + "="*70)
    print("步骤5:保存和显示完整明文")
    print("="*70)

    # 保存明文到文件
    with open('decrypted_plaintext.txt', 'wb') as f:
        f.write(plaintext)
    print("明文已保存到: decrypted_plaintext.txt")

    # 显示完整明文
    print("\n完整明文内容:")
    print("="*70)
    print(plaintext.decode('utf-8'))
    print("="*70)

else:
    print("\n所有弱密钥尝试均失败。")
    print("建议尝试:")
    print("1. 半弱密钥(12个)")
    print("2. 字典攻击")
    print("3. 暴力破解(需要更多时间)")

5.3 执行解密并分析结果

运行脚本后,输出如下:

======================================================================
步骤1:读取密文文件
======================================================================
密文长度: 632 字节
密文前50字节(十六进制): 702c3ead907e01c85956414015525e4b502535ee932b1ad211475a511541584a48205187bf7853d25e4712565042405d412a

======================================================================
步骤2:准备DES弱密钥和IV
======================================================================
IV(初始化向量): 13245678
IV长度: 8 字节

准备尝试 4 个DES弱密钥

======================================================================
步骤3:使用弱密钥尝试解密
======================================================================

[尝试 1/4] 密钥: 0x0000000000000000 (全0)
----------------------------------------------------------------------
无法解码为UTF-8文本 -> 不是正确的密钥

[尝试 2/4] 密钥: 0xFFFFFFFFFFFFFFFF (全1)
----------------------------------------------------------------------
无法解码为UTF-8文本 -> 不是正确的密钥

[尝试 3/4] 密钥: 0x1E1E1E1E0F0F0F0F
----------------------------------------------------------------------
无法解码为UTF-8文本 -> 不是正确的密钥

[尝试 4/4] 密钥: 0xE1E1E1E1F0F0F0F0
----------------------------------------------------------------------
解密成功,得到可读文本
明文长度: 632 字符
明文前100字符:
The furthest distance in the world

Is not between life and death
But when I stand infront of you
Ye

======================================================================
发现flag!这是正确的密钥!
======================================================================

Flag: flag{_poor_single_dog_has_found_an_echo_from_it}

======================================================================
步骤4:验证解密正确性(加密-解密可逆性测试)
======================================================================
原始密文长度: 632 字节
重新加密后密文长度: 632 字节

原始密文前50字节: 702c3ead907e01c85956414015525e4b502535ee932b1ad211475a511541584a48205187bf7853d25e4712565042405d412a
重新加密前50字节: 702c3ead907e01c85956414015525e4b502535ee932b1ad211475a511541584a48205187bf7853d25e4712565042405d412a

验证成功!重新加密得到的密文与原始密文完全一致!
这证明我们的解密是100%正确的。

======================================================================
步骤5:保存和显示完整明文
======================================================================
明文已保存到: decrypted_plaintext.txt

完整明文内容:
======================================================================
The furthest distance in the world

Is not between life and death
But when I stand infront of you
Yet you don't know that I love you

Is not when I stand infront of you
Yet you can't see my love
But when undoubtedly knowing the love from both
Yet can not be together

Is not being apart while being in love
But when painly cannot resist the yearning
Yet pretending you have never been in my heart

Is not when painly can not resist the yearning
yet pretending you have never been in my heart
but using one's in different heart
To dig an uncrossable river
For the one who loves you

flag{_poor_single_dog_has_found_an_echo_from_it}
======================================================================

5.4 结果分析

关键发现

  1. 正确的密钥0xE1E1E1E1F0F0F0F0(第4个弱密钥)
  2. 明文内容:泰戈尔的诗《世界上最遥远的距离》(英文版)
  3. Flagflag{_poor_single_dog_has_found_an_echo_from_it}
  4. 验证结果:重新加密后的密文与原始密文完全一致,证明解密正确

为什么前3个弱密钥失败?

这说明加密时使用的确实是第4个弱密钥(0xE1E1E1E1F0F0F0F0)。其他3个弱密钥虽然也是有效的DES弱密钥,但不是本题使用的密钥,因此解密出来的是乱码。

明文长度分析

  • 密文长度:632字节
  • 明文长度:632字节
  • 长度完全相同,符合OFB模式的特性(不需要填充)

六、代码技术细节深度解析

6.1 PyCryptodome库使用详解

from Crypto.Cipher import DES

这行代码导入PyCryptodome库中的DES加密模块。注意:

  • 早期版本是PyCrypto
  • 现在推荐使用PyCryptodomePyCryptodomex
  • 两者API兼容,但后者更安全、维护更活跃

6.2 DES对象创建详解

cipher = DES.new(KEY, DES.MODE_OFB, IV.encode())

这行代码创建了一个DES加密/解密对象。参数详解:

参数1:KEY

  • 类型:bytes(字节串)
  • 长度:必须是8字节(64位)
  • 格式:原始字节,不是十六进制字符串
  • 示例:b'\xE1\xE1\xE1\xE1\xF0\xF0\xF0\xF0'

参数2:DES.MODE_OFB

  • 类型:整数常量
  • 含义:指定使用OFB工作模式
  • 其他选项:DES.MODE_ECBDES.MODE_CBCDES.MODE_CFBDES.MODE_CTR

参数3:IV.encode()

  • IV是字符串'13245678'
  • IV.encode()将其转换为字节串b'13245678'
  • 长度:必须是8字节(DES块大小)
  • OFB模式必须提供IV

6.3 加密与解密操作详解

# 加密
ciphertext = cipher.encrypt(plaintext)

# 解密
plaintext = cipher.decrypt(ciphertext)

重要特性

  1. 在OFB模式下,encrypt()decrypt()内部执行的操作几乎相同
  2. 两者都是:生成密钥流 → 与输入数据异或
  3. 区别仅在于输入数据是明文还是密文

内部流程(简化)

# encrypt()内部(简化版)
def encrypt(plaintext):
    output = b''
    state = IV
    for i in range(0, len(plaintext), 8):
        # 加密当前状态
        keystream = des_encrypt_block(state, KEY)
        # 与明文块异或
        block = plaintext[i:i+8]
        output += xor(keystream, block)
        # 更新状态
        state = keystream
    return output

# decrypt()内部(简化版)
def decrypt(ciphertext):
    output = b''
    state = IV
    for i in range(0, len(ciphertext), 8):
        # 加密当前状态(注意:是加密,不是解密!)
        keystream = des_encrypt_block(state, KEY)
        # 与密文块异或
        block = ciphertext[i:i+8]
        output += xor(keystream, block)
        # 更新状态
        state = keystream
    return output

6.4 字符串编码解码详解

# 字节串转十六进制字符串
hex_str = ciphertext.hex()  # 例如:'702c3ead907e...'

# 字节串解码为UTF-8字符串
text = plaintext.decode('utf-8')

# UTF-8字符串编码为字节串
bytes_data = text.encode('utf-8')

# 字符串编码为字节串(默认UTF-8)
bytes_data = 'hello'.encode()  # 等价于 'hello'.encode('utf-8')

为什么需要编码解码?

  • Python 3中,字符串(str)和字节串(bytes)是不同的类型
  • 加密算法处理的是字节串(原始二进制数据)
  • 文本是字符串(需要编码为字节串才能加密)

6.5 文件读写操作详解

# 二进制模式读取文件
with open('ciphertext', 'rb') as f:  # 'rb' = read binary
    data = f.read()

# 二进制模式写入文件
with open('output.bin', 'wb') as f:  # 'wb' = write binary
    f.write(data)

# 文本模式读取文件
with open('text.txt', 'r') as f:  # 'r' = read text
    text = f.read()

# 文本模式写入文件
with open('text.txt', 'w') as f:  # 'w' = write text
    f.write(text)

关键区别

  • 二进制模式(’rb’/’wb’):读写bytes对象,不进行编码转换
  • 文本模式(’r’/’w’):读写str对象,自动进行UTF-8编码解码

为什么密文必须用二进制模式?

密文是任意字节数据,可能包含无法用UTF-8解码的字节序列,必须使用二进制模式。

七、安全启示与防护建议

7.1 本题暴露的安全问题

  1. 使用了弱密钥
  • 问题:选择了DES的弱密钥0xE1E1E1E1F0F0F0F0
  • 后果:密钥空间从2^56缩减到只需尝试4次
  1. 使用了过时的加密算法
  • 问题:使用DES而非AES
  • 后果:即使不使用弱密钥,DES也可以被暴力破解
  1. 缺少密钥验证机制
  • 问题:没有检测和拒绝弱密钥
  • 后果:允许使用危险的密钥

7.2 正确的密码学实践

7.2.1 密钥生成

错误做法

# 不要这样做!
KEY = b'\x00\x00\x00\x00\x00\x00\x00\x00'  # 硬编码的弱密钥
KEY = b'password'  # 可预测的密钥

正确做法

import secrets

# 方法1:生成随机密钥(推荐)
KEY = secrets.token_bytes(32)  # AES-256需要32字节密钥

# 方法2:从密码派生密钥
from Crypto.Protocol.KDF import PBKDF2
password = b'user_password'
salt = secrets.token_bytes(16)
KEY = PBKDF2(password, salt, dkLen=32, count=100000)

7.2.2 算法选择

应避免使用

  • DES(密钥太短)
  • 3DES(效率低)
  • RC4(流密码,已有多个攻击)

推荐使用

from Crypto.Cipher import AES
from Crypto.Random import get_random_bytes

# AES-GCM模式(推荐,提供认证加密)
KEY = get_random_bytes(32)  # AES-256
nonce = get_random_bytes(12)  # GCM推荐12字节nonce

cipher = AES.new(KEY, AES.MODE_GCM, nonce=nonce)
ciphertext, tag = cipher.encrypt_and_digest(plaintext)

# 解密并验证
cipher = AES.new(KEY, AES.MODE_GCM, nonce=nonce)
plaintext = cipher.decrypt_and_verify(ciphertext, tag)

7.2.3 IV/Nonce管理

错误做法

# 不要重复使用相同的IV!
IV = b'12345678'  # 固定IV
cipher = AES.new(KEY, AES.MODE_CBC, IV)  # 危险!

正确做法

# 每次加密使用不同的IV
IV = get_random_bytes(16)  # AES块大小是16字节
cipher = AES.new(KEY, AES.MODE_CBC, IV)

# IV可以公开,通常附加在密文前面
full_ciphertext = IV + ciphertext

7.2.4 弱密钥检测

对于必须使用DES的遗留系统,应添加弱密钥检测:

def is_weak_des_key(key):
    """检测DES弱密钥和半弱密钥"""
    weak_keys = [
        b'\x00\x00\x00\x00\x00\x00\x00\x00',
        b'\xFF\xFF\xFF\xFF\xFF\xFF\xFF\xFF',
        b'\x1E\x1E\x1E\x1E\x0F\x0F\x0F\x0F',
        b'\xE1\xE1\xE1\xE1\xF0\xF0\xF0\xF0'
    ]

    semi_weak_keys = [
        (b'\x01\xFE\x01\xFE\x01\xFE\x01\xFE', b'\xFE\x01\xFE\x01\xFE\x01\xFE\x01'),
        # ... 其他半弱密钥对
    ]

    if key in weak_keys:
        return True, "弱密钥"

    for k1, k2 in semi_weak_keys:
        if key in (k1, k2):
            return True, "半弱密钥"

    return False, "密钥安全"

# 使用示例
key = generate_key()
is_weak, reason = is_weak_des_key(key)
if is_weak:
    raise ValueError(f"检测到{reason},拒绝使用")

7.3 CTF学习建议

对于密码学CTF题目:

  1. 掌握基础理论
  • 了解常见加密算法的工作原理
  • 理解不同工作模式的特点
  • 学习已知的密码学攻击方法
  1. 建立攻击工具库
  • 弱密钥字典
  • 常用攻击脚本
  • 密码分析工具
  1. 培养思维模式
  • 从简单到复杂:先尝试已知攻击
  • 信息收集:仔细分析题目提供的所有信息
  • 逆向思维:从出题者角度思考
  1. 实践经验积累
  • 多做题,总结常见套路
  • 学习他人的writeup
  • 参与讨论和交流

八、扩展知识

8.1 DES的半弱密钥

除了4个弱密钥,DES还有12个半弱密钥(6对),它们成对出现:

如果用K1加密,用K2解密,等价于K2加密,K1解密:
E_K1(M) = D_K2(M)
E_K2(M) = D_K1(M)

半弱密钥对(部分):

  • 0x01FE01FE01FE01FE 和 0xFE01FE01FE01FE01
  • 0x1FE01FE00EF10EF1 和 0xE01FE01FF10EF10E
  • …(共6对)

8.2 其他分组密码工作模式比较

| 模式 | 加密可并行 | 解密可并行 | 错误传播 | 需要填充 | 安全性 | | — | — | — | — | — | — | | ECB | 是 | 是 | 无 | 是 | 低(不推荐) | | CBC | 否 | 是 | 当前块 | 是 | 中 | | CFB | 否 | 是 | 当前块 | 否 | 中 | | OFB | 否 | 否 | 无 | 否 | 中 | | CTR | 是 | 是 | 无 | 否 | 高 | | GCM | 是 | 是 | 全部 | 否 | 高(推荐) |

8.3 DES的变种

  1. 3DES(Triple DES)
  • 使用3个密钥进行3次DES运算
  • 加密:C = E_K3(D_K2(E_K1(P)))
  • 有效密钥长度:112位或168位
  • 安全性提高但效率降低
  1. DES-X
  • 在DES前后添加密钥白化
  • C = K3 ⊕ E_K2(P ⊕ K1)
  • 抵抗穷举搜索攻击

8.4 现代密码学趋势

  1. 认证加密(AEAD)
  • 同时提供保密性和完整性
  • 代表:AES-GCM、ChaCha20-Poly1305
  1. 轻量级密码
  • 适用于IoT设备
  • 代表:PRESENT、SIMON、SPECK
  1. 后量子密码
  • 抵抗量子计算机攻击
  • NIST正在标准化过程中

九、总结

本题”easy_BlockCipher”是一道经典的密码学CTF题目,通过DES弱密钥这一安全缺陷,让我们深入理解了:

9.1 技术收获

  1. DES算法原理
  • 16轮Feistel结构
  • 密钥编排算法
  • 弱密钥的产生机制
  1. OFB工作模式
  • 将分组密码转换为流密码
  • 加密解密使用相同操作
  • 不需要填充
  1. 密码学攻击思路
  • 从算法缺陷入手
  • 优先尝试低成本攻击
  • 验证攻击结果的正确性

9.2 解题关键点

  1. 识别加密算法和工作模式
  2. 分析已知和未知信息
  3. 利用DES弱密钥进行攻击
  4. 验证解密正确性

9.3 安全经验

  1. 密钥选择至关重要
  2. 使用现代加密算法
  3. 实施密钥验证机制
  4. 遵循密码学最佳实践

9.4 实战能力提升

通过本题的完整分析和实践,我们不仅解决了一道CTF题目,更重要的是:

  • 建立了系统的密码学分析方法
  • 掌握了Python密码学编程技能
  • 理解了加密算法的内部机制
  • 培养了安全思维和攻击意识

这些能力将在今后的安全研究和CTF比赛中发挥重要作用。


Flagflag{_poor_single_dog_has_found_an_echo_from_it}

完整解题代码和工具已开源,欢迎交流学习。


免责声明:

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

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

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

本文转载自:破镜安全 破镜安全《easy_BlockCipher – DES弱密钥攻击完整技术解析》

评论:0   参与:  3