
1. 项目概述当经典密码学遇上现代脚本1993年密码学界发生了一件震动整个行业的大事日本学者松井充Mitsuru Matsui成功利用线性密码分析Linear Cryptanalysis在理论上攻破了当时作为全球金融与通信安全基石的DES算法。这不仅仅是宣告了一个特定算法的“退休”更重要的是它向世界展示了一种全新的、系统性的密码分析思路。对于很多刚接触密码学的朋友来说线性密码分析听起来高深莫测充满了复杂的数学公式和概率统计。但它的核心思想其实可以用一个更生活化的视角来理解在一场看似完全随机的抛硬币游戏中寻找那一点点微乎其微的“偏好”。这个项目就是带你亲手用Python这把“手术刀”去解剖这个经典的密码学案例。我们不会停留在理论公式的推导上而是通过一行行可运行的代码去模拟松井教授当年的破译思路。你会发现所谓的“攻破”并不是像电影里演的那样瞬间破解密码而是一个基于大量已知明密文对通过统计分析一点点“撬开”算法内部秘密钥匙密钥的过程。DES算法虽然如今已不再安全但其结构清晰是学习分组密码和密码分析的绝佳教具。通过复现针对它的攻击你能深刻理解现代密码算法设计为何要抵抗线性分析以及像AES这样的新算法在哪些关键点上做了加固。无论你是信息安全专业的学生对密码学充满好奇的开发者还是希望深入理解“攻击视角”以更好地进行防御的安全工程师这个项目都提供了一个绝佳的动手实践机会。你不需要是数学天才但需要具备基本的Python编程能力和对逻辑推理的热情。我们将从DES的基本结构讲起一步步构建线性逼近表达式用Python处理海量数据最终计算出密钥的部分比特。整个过程就像完成一次精密的科学实验数据会告诉你答案。2. 核心思路拆解线性密码分析究竟在分析什么在深入代码之前我们必须先抛开畏惧搞清楚线性密码分析到底想干什么。DES算法是一个对称分组密码它把64位的明文通过一个56位的密钥加密成64位的密文。这个过程中包含了初始置换、16轮Feistel结构迭代、最终置换等步骤内部还有著名的S盒进行非线性替换。从设计上看整个加密过程应该是高度复杂和非线性的理论上密文的每一位与明文的每一位、密钥的每一位之间的关系应该是随机的就像完美的抛硬币正反面概率各50%。线性密码分析的终极目标就是在这看似完美的随机性中寻找一种不完美的、可被利用的“线性关系”。这种关系不是确定性的等式比如ABC而是一种概率性的倾向。具体来说它试图找到明文某些比特、密文某些比特和密钥某些比特的一个线性组合即对这些比特进行异或XOR运算使得这个组合成立的概率显著地偏离1/2比如是0.5 ε其中ε是一个很小的值称为偏差。2.1 理解“线性逼近”与S盒的脆弱性为什么会有这种偏差呢根源在于DES的核心非线性组件——S盒。每个S盒是一个6位输入、4位输出的查找表。虽然S盒整体是非线性的但密码学家发现对于某些特定的输入比特组合和输出比特组合它们之间异或结果为0或为1的概率并不是严格的50%而是存在微弱的偏差。例如对于DES的第一个S盒S1可能有这样的统计规律输入的第1位与第3位的异或值等于输出的第2位与第4位的异或值这个事件发生的概率是12/64 ≈ 0.1875而不是0.5。这个偏差0.5 - 0.1875 0.3125就相当大了。注意这里提到的具体比特位置和概率值是示例实际分析中需要查阅DES的S盒线性逼近表Linear Approximation Table, LAT来获取所有高偏差的线性逼近。这个表是通过穷举S盒所有可能的输入64种统计所有可能的输入/输出线性掩码组合计算出来的是线性分析的“弹药库”。一轮DES的线性关系非常微弱但DES有16轮。线性密码分析的精妙之处在于可以将每一轮S盒的微弱线性关系通过密钥加和置换操作“链接”起来贯穿多轮最终形成一个连接了部分明文比特、部分密文比特和部分轮密钥比特的线性表达式。这个贯穿多轮的表达式其偏差会随着轮数的增加而急剧减小大致按每轮偏差的乘积衰减但通过选择偏差最大的路径我们仍然能得到一个可观测的、非随机的概率。2.2 松井Matsui算法的两步走策略松井教授提出的实际攻击方法是一个分为两步的、非常务实的策略第一步数据收集与统计分析攻击者需要收集大量的“已知明密文对”Known Plaintext-Ciphertext Pairs。也就是说你知道某一组明文输入是什么也知道用目标密钥加密后对应的密文输出是什么但你不知道密钥本身。对于每一对明密文代入我们事先找好的、那个贯穿多轮的最终线性逼近表达式。这个表达式里包含了一些密钥比特称为“相关密钥比特”。由于我们不知道密钥表达式可能成立也可能不成立。但是如果我们猜测这几位相关密钥比特的值比如总共6位密钥比特有2^664种可能那么对于每一种猜测我们都可以重新计算这个表达式是否成立。关键来了如果我们的密钥猜测是错误的那么由于密钥加错了相当于在表达式中引入了额外的随机变量这会使得整个表达式成立的概率非常接近1/2。如果我们收集了N对明密文那么表达式成立的次数应该大致在N/2附近波动。如果我们的密钥猜测是正确的那么表达式成立的概率就应该等于我们理论计算出的那个有偏差的概率P比如0.5ε。因此表达式成立的次数会显著地偏离N/2。因此第一步的目标就是遍历所有相关密钥比特的可能取值统计每种取值下线性表达式成立的次数。成立次数偏离N/2最远的那一个或几个密钥猜测就是最有可能的正确密钥。这一步通常可以恢复出最后一轮子密钥或第一轮子密钥的部分比特。第二步密钥穷举与验证第一步通常只能恢复出完整56位密钥中的一部分比如30-40位。剩下的密钥比特数量已经大大减少。此时攻击者就可以对剩下的密钥空间进行穷举搜索。例如如果第一步恢复了40位密钥那么剩下16位未知只需要尝试2^1665536种可能性。对于每一种完整的密钥猜测用一两对明密文进行验证很快就能找到唯一正确的密钥。我们的Python模拟将重点复现第一步也就是最体现线性分析精髓的统计攻击部分。通过这个过程你会直观地感受到如何从海量数据中“沙里淘金”利用微弱的统计偏差来撬开密码系统。3. 环境准备与DES基础实现在开始模拟攻击之前我们需要一个能够正常工作的DES加密/解密函数作为我们的“靶子”。虽然Python标准库没有DES但我们可以利用pycryptodome库或者为了更透彻地理解自己实现一个简化版的DES。为了教学清晰这里我们选择自己实现一个功能完整的DES但这会涉及大量位操作。在实际项目中你可以直接使用pycryptodome的DES模块作为攻击目标。3.1 Python环境与必要库确保你安装了Python 3.6以上版本。我们主要会用到以下库numpy用于高效的数值计算和统计。itertools用于生成密钥猜测的组合。可选tqdm用于显示进度条在遍历大量数据时体验更好。可以通过pip安装pip install numpy tqdm3.2 实现一个用于教学的DES类为了完全理解攻击过程自己实现DES是关键一步。这里给出核心结构的框架你需要填充置换表、S盒等具体数据这些数据可以在DES标准文档中找到。class MyDES: # 这里需要定义所有常量初始置换IP表、逆初始置换IP^-1表、扩展置换E表、 # 置换函数P表、8个S盒每个是4x16的矩阵、置换选择PC1、PC2表等。 # 篇幅所限不在此处完整列出所有64个元素的表。 def __init__(self, key: int): 使用64位整数密钥初始化实际使用56位每字节第8位为奇偶校验位 self.round_keys self._generate_round_keys(key) def _generate_round_keys(self, key: int): 生成16轮48位的子密钥 # 1. 通过PC1置换从64位中选出56位有效密钥位 # 2. 将这56位分成左右各28位的C0和D0 # 3. 进行16轮循环左移每轮移位数不同 # 4. 每一轮后通过PC2置换压缩成48位子密钥 # 5. 返回一个包含16个子密钥整数的列表 round_keys [] # ... 具体实现位操作和查表 ... return round_keys def encrypt(self, plaintext: int) - int: 加密一个64位的明文块返回64位密文 # 1. 初始置换IP state self._permute(plaintext, self.IP_TABLE, 64) L, R (state 32) 0xFFFFFFFF, state 0xFFFFFFFF # 2. 16轮Feistel结构 for i in range(16): new_R L ^ self._feistel_function(R, self.round_keys[i]) L, R R, new_R # 3. 最后交换左右32位第16轮后不交换但前面循环已经交换了这里需要再换回来注意实现细节 # 标准实现是最后一轮后不交换所以循环16轮后结果是(R16, L16) # 我们这里假设循环内已经完成了交换那么最终组合是 (R, L) combined (R 32) | L # 4. 逆初始置换IP^-1 ciphertext self._permute(combined, self.IP_INV_TABLE, 64) return ciphertext def _feistel_function(self, half_block: int, round_key: int) - int: Feistel轮函数输入32位输出32位 # 1. 扩展置换32位 - 48位 expanded self._permute(half_block, self.E_TABLE, 32) # 2. 与轮密钥异或 xored expanded ^ round_key # 3. S盒替换48位 - 32位 sbox_output 0 for i in range(8): # 取出6位输入 six_bits (xored (42 - i*6)) 0x3F # 计算S盒的行和列 row ((six_bits 5) 1) | (six_bits 0x01) col (six_bits 1) 0x0F # 查表得到4位输出 sbox_val self.S_BOXES[i][row][col] sbox_output (sbox_output 4) | sbox_val # 4. P置换 result self._permute(sbox_output, self.P_TABLE, 32) return result def _permute(self, block: int, table: list, input_bits: int) - int: 通用的置换函数根据table将input_bits位的block置换 result 0 for pos in table: result 1 # table中的位置是从1开始计数的我们需要取block中对应的比特 bit (block (input_bits - pos)) 0x01 result | bit return result # 同样需要实现decrypt解密函数过程是加密的逆过程。实操心得自己实现DES是一个很好的练习但极易在置换表、S盒索引计算、比特序等细节上出错。一个有效的调试方法是找一组标准的测试向量明文、密钥、密文例如从NIST的文档中获取确保你的加密函数能得出完全一致的结果。如果只是为了攻击模拟使用from Crypto.Cipher import DES并确保模式为DES.MODE_ECB会更高效可靠。4. 构建线性逼近寻找贯穿DES的脆弱路径这是线性密码分析中最具技术含量的一步。我们不需要从零开始进行复杂的数学推导而是可以借鉴松井教授等先驱已经计算好的成果。我们的目标是找到一个针对3轮DES的线性逼近表达式因为这是构建更多轮攻击的基础。在实际的16轮DES攻击中松井使用了“1-16-1”或“2-14-2”等结构的线性特征即攻击首尾几轮中间多轮作为一个整体考虑其概率。为了简化教学我们模拟一个针对3轮DES的线性分析这足以阐明所有核心概念。假设我们通过研究S盒的线性逼近表LAT找到了一条贯穿3轮的路径并推导出如下形式的线性表达式P[i1] ⊕ P[i2] ⊕ ... ⊕ C[j1] ⊕ C[j2] ⊕ ... ⊕ K[k1] ⊕ K[k2] ⊕ ... 0(成立概率 p 1/2 ε)其中P代表明文比特C代表密文比特K代表密钥比特可能是轮密钥的某些比特的组合⊕表示异或运算。等号右边的0表示这个异或链的结果为0的概率是p。例如一个高度简化的例子可能是请注意这是为了说明而虚构的并非真实有效的DES逼近P[7] ⊕ P[18] ⊕ C[12] ⊕ C[25] ⊕ K[22] ⊕ K[43] 0其概率偏差ε 0.2。4.1 从S盒逼近到多轮特征如何得到这样一个表达式呢过程如下分析单个S盒对每个S盒构建其线性逼近表。LAT的每一项(α, β)记录了当输入掩码为α选择哪些输入比特参与异或、输出掩码为β选择哪些输出比特参与异或时等式α · X ⊕ β · S(X) 0成立的次数或概率其中·表示点积即按位与后异或。偏差大的(α, β)对就是好的线性逼近。连接单轮在一轮DES中轮函数的输出经过P置换后与左半部分异或。因此需要将S盒输出的线性掩码通过P置换的逆操作映射回轮函数的输入位置再与上一轮的线性关系结合。堆叠成多轮特征将多轮的线性关系串联起来。每一轮都会引入该轮使用的轮密钥比特的参与。当两条路径在Feistel结构的左右分支交汇时需要处理异或操作带来的影响根据线性关系(A⊕B)的线性表达式等于A的表达式异或B的表达式。这个过程非常繁琐涉及大量的比特跟踪和概率计算偏差按每轮特征偏差的乘积衰减。在我们的Python模拟中我们将跳过最复杂的推导过程直接使用一个从文献中获取的、针对简化轮数DES的、已知偏差的线性表达式作为我们的“武器”。我们的重点是学会如何使用这个“武器”。假设我们通过查阅资料确定使用以下针对3轮DES的线性特征具体比特位置是示例明文参与比特P[17], P[18]比特位置从1开始计数对应明文的第17、18位密文参与比特C[5], C[6], C[7], C[8]对应3轮加密后密文的第5-8位相关密钥比特K1[22], K3[43]这里K1和K3分别代表第1轮和第3轮的子密钥中的某些比特。注意子密钥比特需要通过密钥调度算法映射回主密钥比特。线性表达式P[17] ⊕ P[18] ⊕ C[5] ⊕ C[6] ⊕ C[7] ⊕ C[8] ⊕ K1[22] ⊕ K3[43] 0理论偏差ε假设我们计算或查得这个特征的概率是p 0.75那么偏差ε |p - 0.5| 0.25。这是一个非常大的偏差仅用于教学演示真实攻击中偏差要小得多。在代码中我们需要实现函数来提取一个64位整数的特定位并计算这个线性表达式的值在给定密钥猜测的情况下。def get_bit(data: int, pos: int, total_bits64) - int: 获取整数data的第pos位1-indexed从左到右或从最高位开始取决于约定。 通常DES标准中位1是最高有效位(MSB)。我们这里约定pos1对应最高位。 # 因为整数在内存中是二进制我们约定位置1是最高位(第63比特位)。 # 所以 pos1 - 移位 (total_bits - 1) shift total_bits - pos return (data shift) 1 def compute_linear_expression(plaintext: int, ciphertext: int, key_guess: int) - int: 计算线性表达式的值0或1。 key_guess: 一个整数其低位比特代表我们对相关密钥比特的猜测。 例如假设相关密钥比特是2位那么key_guess0,1,2,3。 我们需要根据key_guess解析出K1[22]和K3[43]的具体值。 # 提取明文比特 p17 get_bit(plaintext, 17) p18 get_bit(plaintext, 18) # 提取密文比特 c5 get_bit(ciphertext, 5) c6 get_bit(ciphertext, 6) c7 get_bit(ciphertext, 7) c8 get_bit(ciphertext, 8) # 从key_guess中解析密钥比特。假设key_guess的最低比特是K1[22]次低比特是K3[43] k1_bit (key_guess 0) 1 # 假设key_guess第0位对应K1[22] k3_bit (key_guess 1) 1 # 假设key_guess第1位对应K3[43] # 计算线性表达式所有比特异或 result p17 ^ p18 ^ c5 ^ c6 ^ c7 ^ c8 ^ k1_bit ^ k3_bit return result有了这个函数我们就可以对每一对明密文在给定的密钥猜测下计算线性表达式是0还是1。5. 模拟攻击统计与密钥恢复现在进入最激动人心的部分模拟攻击。我们将扮演攻击者拥有一个用未知密钥加密的DES黑盒我们的MyDES类实例并且我们可以获取到大量的明密文对。5.1 生成测试数据首先我们生成一个随机的56位密钥实际上用64位整数表示但每字节第8位是奇偶位并创建加密实例。然后随机生成N个明文并加密得到对应的密文。import random import itertools def generate_test_data(num_pairs, true_key): 生成已知明密文对。 true_key: 真实的DES密钥64位整数含奇偶位。 des MyDES(true_key) data [] for _ in range(num_pairs): plaintext random.getrandbits(64) # 随机生成64位明文 ciphertext des.encrypt(plaintext) data.append((plaintext, ciphertext)) return data5.2 实施线性分析统计根据松井的第一步算法我们对所有可能的密钥猜测进行统计。在我们的例子中相关密钥比特是K1[22]和K3[43]共2位所以有4种可能的猜测0, 1, 2, 3。def linear_cryptanalysis_attack(data): 执行线性密码分析。 data: 列表包含多个(plaintext, ciphertext)元组。 返回最可能的密钥猜测及其统计量。 num_key_guesses 4 # 2位密钥比特 - 4种猜测 # 初始化一个计数器记录每个密钥猜测下线性表达式成立结果为0的次数 count_zero [0] * num_key_guesses for plaintext, ciphertext in data: for key_guess in range(num_key_guesses): expr_value compute_linear_expression(plaintext, ciphertext, key_guess) if expr_value 0: count_zero[key_guess] 1 # 计算每个猜测对应的“成立次数”与期望值N/2的绝对偏差 N len(data) expected N / 2.0 deviations [abs(count - expected) for count in count_zero] # 找到偏差最大的密钥猜测 best_guess deviations.index(max(deviations)) best_deviation max(deviations) return best_guess, best_deviation, count_zero, deviations5.3 运行攻击并分析结果现在让我们运行整个模拟看看需要多少明密文对才能可靠地恢复出这2位密钥。def main(): # 1. 设置真实密钥示例 true_key 0x133457799BBCDFF1 # 这是一个标准的DES测试密钥 # 注意我们需要知道在这个真实密钥下我们攻击的K1[22]和K3[43]的真实值是多少。 # 这需要通过密钥调度算法计算出来。这里我们假设通过计算得知真实值为 key_guess 2 (二进制10)。 true_partial_key 2 # 2. 尝试不同数量的明密文对 test_sizes [100, 1000, 5000, 10000, 50000] results [] for N in test_sizes: print(f\n 使用 {N} 对明密文进行攻击 ) data generate_test_data(N, true_key) best_guess, best_dev, counts, devs linear_cryptanalysis_attack(data) print(f 每个密钥猜测下表达式成立次数: {counts}) print(f 与期望值{N/2}的偏差: {devs}) print(f 最可能的密钥猜测: {best_guess} (二进制: {bin(best_guess)[2:].zfill(2)})) print(f 最大偏差: {best_dev:.2f}) print(f 猜测是否正确: {best_guess true_partial_key}) results.append((N, best_guess, best_dev, best_guess true_partial_key)) # 3. 分析结果 print(\n 攻击结果总结 ) for N, guess, dev, correct in results: status 成功 if correct else 失败 print(fN{N:6d}: 猜测{guess}, 偏差{dev:7.1f}, 状态{status})运行这段代码你会观察到随着明密文对数量N的增加统计结果会越来越清晰。当N较小时如100对由于随机噪声错误的密钥猜测也可能产生较大的偏差导致攻击失败。当N增大到数千或上万时正确密钥猜测对应的偏差会显著地、稳定地高于其他错误猜测从而使攻击成功。核心原理解读为什么正确猜测的偏差会更大因为当密钥猜测正确时我们计算的线性表达式P[...]⊕C[...]⊕K[...]实际上等于我们理论推导出的那个有偏差的线性关系其值为0的概率是p0.5ε。因此它成立的次数会围绕N * p波动。而当密钥猜测错误时错误的密钥比特相当于在表达式中引入了一个随机的偏移因为K_wrong ⊕ K_right是一个未知的固定值但它会改变整个表达式的概率使其趋近于1/2因此成立的次数会围绕N/2波动。N越大根据大数定律这两种情况的区分度就越明显。5.4 扩展到更多密钥比特与完整攻击我们模拟的只是2位密钥比特。在真实的DES线性攻击中松井的第一步通常针对最后轮或首轮的子密钥的多个S盒的有效位进行攻击可能涉及30位以上的密钥比特。此时密钥猜测空间会急剧扩大2^30约10亿不能简单地遍历。优化策略分而治之DES的轮函数是8个S盒并行工作的。每个S盒的输入依赖于6位子密钥和扩展后的6位数据。线性分析可以针对每个S盒独立进行。因为一个好的线性特征通常只涉及少数几个S盒的输入输出。我们可以分别攻击这少数几个S盒所依赖的子密钥比特每个S盒最多6位大大降低了每次需要遍历的密钥空间例如攻击3个S盒最多18位2^18262k是可遍历的。使用更高效的统计量除了直接统计成立次数还可以计算|count - N/2|或者使用平方和、似然函数等作为判断依据。利用多个线性特征可以同时使用多个偏差较大的线性特征进行攻击综合它们的结果来提高成功率或减少所需数据量。在第一步成功恢复出部分轮密钥比特后第二步就是穷举剩余的主密钥比特。因为DES密钥调度算法是确定的已知部分轮密钥比特可以反向推导出主密钥的部分比特并对未知部分进行穷举。例如如果第一步恢复了30位剩下26位未知只需穷举2^26 ≈ 6700万次这在现代计算机上是完全可行的。6. 常见问题、调试技巧与深度思考在实际编写和运行这个模拟程序时你几乎一定会遇到各种问题。下面是我在复现过程中踩过的坑和总结的经验。6.1 问题排查清单问题现象可能原因解决方案攻击始终失败所有密钥猜测的偏差都差不多且很小。1.线性表达式错误使用的比特位置或表达式形式不对导致理论偏差ε实际为0或极小。2.DES实现错误加密/解密结果不正确导致明密文对无效。3.比特序混乱DES标准、编程语言的比特序MSB/LSB、自己定义的get_bit函数之间不一致。1.验证表达式用一个小脚本枚举所有可能的密钥猜测和少量明密文手动验证表达式是否对正确密钥有偏差。确保你使用的线性特征是有文献依据的。2.测试DES使用标准测试向量验证你的MyDES.encrypt()函数。3.统一比特序明确约定并全程使用同一位序建议采用DES标准位1为最高位MSB。在get_bit和密钥提取函数中保持绝对一致。偏差方向相反。线性表达式等号右边可能应该是1而不是0。即概率p可能是0.5 - ε。在统计时同时统计表达式结果为0和结果为1的次数看哪个偏离N/2更远。或者将表达式取反整体异或1再计算。所需明密文数量远高于理论值。1.理论偏差ε太小真实的DES线性特征偏差非常小例如2^{-5}量级需要海量数据2^{43}对以上才能观测到。教学示例中我们用了夸大的ε。2.统计噪声N不够大时随机波动会掩盖信号。理解理论数据复杂度N ∝ 1/ε^2。对于真实小偏差需要极其庞大的N。教学时使用简化模型或放大偏差是合理的。恢复出的密钥比特无法用于穷举出完整密钥。从轮密钥比特映射回主密钥比特的推导错误。DES的密钥调度算法PC1, PC2循环左移比较复杂。仔细实现密钥调度算法及其逆过程。可以编写一个函数输入主密钥输出所有轮密钥再写一个反向函数输入部分轮密钥比特枚举可能的主密钥。6.2 关键调试技巧从小处着手逐步验证不要一开始就运行万级别的攻击。先用一个你知道部分密钥的实例比如你设定密钥并计算出相关密钥比特的真实值用10对、100对数据跑一下看看正确密钥猜测的计数是否如预期般有“苗头”。打印中间结果在compute_linear_expression函数里临时打印出明文、密文、密钥猜测下的各个比特值手动验算几组确保异或逻辑正确。可视化偏差将不同N值下的最大偏差绘制成图可以看到偏差随着N增长而增大的趋势对于正确猜测而错误猜测的偏差则围绕0随机波动。这能给你直观的信心。使用已知的库作为“靶子”为了排除DES实现错误的干扰在初步验证攻击逻辑时可以使用pycryptodome的DES实现作为被攻击对象。确保你的攻击脚本能对你已知的密钥生效。6.3 线性密码分析的局限与现代启示通过这次模拟你应该深刻感受到了线性密码分析的威力与代价。它的威力在于这是一种唯密文攻击理论上的通用方法但已知明密文攻击效率更高。它不依赖于算法的任何实现缺陷只依赖于算法本身数学结构上的微小不平衡。它的代价在于需要海量的已知数据并且分析过程复杂。DES被攻破直接导致了3DES的过渡和AES的征集。AES在设计时其S盒和整体结构就专门针对线性密码分析和差分密码分析进行了强化使得任何线性特征的偏差都极小从而将所需的数据复杂度提升到完全不现实的程度例如2^100以上。对于开发者而言这个项目的启示是不要自己发明密码算法设计一个能抵抗线性分析等高级分析的算法极其困难。使用经过时间检验的标准算法如AES-256。正确使用密码学即使算法本身安全错误的使用模式如ECB模式、弱密钥、不恰当的IV等也会引入漏洞。理解攻击思维学习攻击方法不是为了攻击而是为了更深刻地理解如何防御。知道线性分析的存在你就会明白为什么算法的非线性部件如此关键。最后这个Python模拟项目就像一个密码学显微镜让你亲眼看到统计规律如何战胜看似坚固的复杂系统。当你看到随着数据量增加那条代表正确密钥的统计曲线脱颖而出时那种感觉正是密码学研究的魅力所在。你可以尝试修改代码使用更真实的、偏差更小的线性特征体验一下需要多少数据才能成功或者尝试将攻击扩展到更多轮DES感受一下密码分析学家在数据复杂度和计算复杂度之间所做的精妙权衡。