Python实现DES加密算法:从Feistel结构到S盒的完整教学

发布时间:2026/6/29 20:37:44

Python实现DES加密算法:从Feistel结构到S盒的完整教学 1. 项目概述为什么从DES开始学加密如果你刚开始接触密码学或者想用Python做点和数据安全相关的小项目那么从DESData Encryption Standard数据加密标准入手绝对是个明智的选择。这玩意儿虽然现在在商业和军事领域因为密钥太短56位被认为不够安全早被AES高级加密标准取代了但它在密码学发展史上是个里程碑。它的结构清晰包含了现代分组密码几乎所有的核心思想Feistel网络结构、代换S盒、置换P盒、密钥编排……把这些搞明白了你再去看AES、SM4这些更现代的算法会感觉轻松很多。说白了用Python实现一遍DES就像学编程先写个“Hello World”学电路先焊个LED闪烁一样。它是一个绝佳的“教学工具”。你能在这个过程中亲手把那些听起来很玄乎的“混淆”和“扩散”概念变成一行行具体的代码。你会看到一串普通的01比特是如何经过16轮复杂的变换变成一堆谁也看不懂的密文的。这对于理解“加密”到底在干什么至关重要。我当年就是自己吭哧吭哧实现了一遍DES之后才真正对对称加密有了感觉。这次我就带你一起抛开那些厚重的教科书用Python从零开始把DES的加密和解密过程“盘”一遍。我们不光要写出能跑的代码更要搞清楚每一行代码背后的“为什么”。2. DES算法核心原理拆解DES是一种对称分组密码算法所谓“对称”就是加密和解密用同一把钥匙。“分组”意思是它每次处理固定长度的一块数据DES的分组长度是64位8个字节。它的核心流程可以概括为一个初始置换IP然后进行16轮完全相同的Feistel结构变换最后经过一个末置换IP⁻¹得到64位密文。解密过程几乎就是加密的逆过程这也是Feistel结构的精妙之处。2.1 Feistel网络结构DES的骨架Feistel结构是DES乃至许多经典密码如Blowfish, RC5的核心。它的巧妙之处在于无论轮函数F多么简单或复杂整个结构都是可逆的这大大简化了加解密硬件的设计。假设我们有一组64位的明文经过初始置换IP后被分成左右两半各32位记作L0和R0。 对于每一轮 i (i从1到16)操作如下Li R(i-1) Ri L(i-1) ⊕ F(R(i-1), Ki)这里的⊕表示异或XOR操作Ki是第i轮的子密钥F就是那个核心的轮函数。看出门道了吗每一轮右半边数据R(i-1)直接变成下一轮的左半边Li。而新的右半边Ri则由旧的左半边L(i-1)和经过轮函数F处理过的旧的右半边进行异或得到。轮函数F是加密强度的关键但它本身不需要是可逆的因为解密时我们只需要反过来算R(i-1) Li L(i-1) Ri ⊕ F(Li, Ki) // 注意这里解密时Li就是加密时的R(i-1)你会发现解密公式和加密公式在形式上一模一样唯一的区别是子密钥Ki的使用顺序相反加密用K1到K16解密用K16到K1。这种对称的美感让加解密可以用几乎相同的电路或代码模块实现非常优雅。注意很多初学者在这里会晕为什么解密时把Li即加密时的R(i-1)代入轮函数F这正是理解Feistel的关键。因为轮函数F处理的是“右半边”数据在加密的第i轮F处理的是R(i-1)在解密的对应轮次逆向进行当前轮的“右半边”数据其实就是加密时上一轮的“左半边”吗不仔细看结构解密时我们是从后往前推当前状态的右半边就是加密流程中下一轮的左半边。为了保持F函数输入的一致性总是处理“右半边”所以在解密公式中我们把当前轮的左半边即加密时下一轮的右半边作为F的输入。理解这点对正确编码至关重要。2.2 轮函数FDES的心脏轮函数F是DES算法中最复杂、最核心的部分它接受32位的右半部分输入R和一个48位的子密钥Ki输出一个32位的结果。它由四步组成扩展置换、与子密钥异或、S盒代换、P盒置换。1. 扩展置换E-box32位的R首先被扩展到48位。这不是简单补零而是一个有固定规则的置换和重复。具体来说扩展表会从32位输入中选取某些位并重复选取其中的一部分例如每4位输入块的第一个和最后一个比特会在输出中被用到两次。这样做的目的主要有两个一是让数据长度与48位子密钥匹配以便进行异或二是让输入的一位能影响后续S盒代换中的两个代换从而更快地实现“扩散”让密文每一位更依赖于明文多位。2. 与子密钥Ki异或将扩展后的48位结果与48位的子密钥Ki进行按位异或。这是将密钥引入加密过程的关键步骤。3. S盒代换Substitution-box这是DES中唯一的非线性变换是算法安全性的主要来源。经过异或得到的48位数据被分成8组每组6位。每一组独立进入一个不同的S盒共8个。每个S盒是一个固定的4行16列的查找表。6位输入中第1位和第6位组合成一个2位数0-3决定查找表的行中间4位组成一个4位数0-15决定查找表的列。找到对应位置的值0-15用4位表示替换掉原来的6位输入。这样8组6位输入就变成了8组4位输出总长度从48位压缩回32位。S盒的设计是DES最神秘也最精妙的部分其具体内容是美国政府当年征集方案时就定好的设计准则如输出不能是输入的线性或简单仿射函数、改变输入1位至少改变输出2位等直到多年后才部分公开。正是S盒的非线性特性使得线性分析和差分分析等攻击方法变得异常困难。4. P盒置换Permutation-box最后一步将S盒输出的32位数据按照一个固定的P盒进行置换就是重新排列位的位置。这一步的目的是将S盒的输出位打散让单个S盒的输出位在下一轮能扩散到多个不同的S盒中去进一步加强“扩散”效果。2.3 密钥编排算法从一把钥匙到16把子钥匙DES的主密钥是64位但其中每第8位第8, 16, 24, ..., 64位用作奇偶校验位实际参与加密过程的密钥是56位。密钥编排算法就是把这56位有效密钥生成16个48位的子密钥K1到K16的过程。置换选择1PC-164位主密钥经过PC-1置换去掉校验位并重排得到56位数据分成两个28位的半密钥C0和D0。循环左移对于每一轮iC(i-1)和D(i-1)分别进行循环左移。左移的位数根据轮数而定第1, 2, 9, 16轮左移1位其余轮次左移2位。得到Ci和Di。置换选择2PC-2将Ci和Di合并成的56位数据经过PC-2置换压缩并重排丢弃其中8位得到48位的本轮子密钥Ki。正是由于每轮循环左移的位数不同才产生了16个不同的子密钥。解密时子密钥使用顺序相反但密钥编排过程本身循环左移在解密时需要“循环右移”来还原或者更简单的方法提前生成好K1到K16解密时倒序使用即可。3. Python实现DES的关键步骤与编码理解了原理我们就可以动手用Python实现了。我们会采用“自底向上”的构建方式先实现最基础的置换、S盒等函数然后组装成轮函数F再实现密钥编排和Feistel网络最后完成加密解密主函数。这里我们使用纯Python的位操作来实现虽然效率不如C扩展但最利于理解。3.1 基础工具函数位操作的实现在DES中所有数据本质上都是一串比特0和1。在Python中我们可以用整数int来表示比特串利用其位运算,|,^,,,~的高效性。但为了清晰我们常常需要将整数与比特列表进行转换。def bit_string_to_int(bit_list): 将比特列表如[1,0,1,1]转换为整数 result 0 for bit in bit_list: result (result 1) | bit return result def int_to_bit_string(num, length): 将整数转换为指定长度的比特列表 # 使用 format(num, 0{}b.format(length)) 获取二进制字符串然后转为列表 return [int(b) for b in format(num, 0{}b.format(length))] def permute(bits, permutation_table): 根据置换表对比特列表进行置换 return [bits[i-1] for i in permutation_table] # 注意置换表通常从1开始计数实操心得置换表IP, PC-1, PC-2, E, P等在标准中都是从1开始计数的表示“输出第1位是输入的第几位”。在编程时因为列表索引从0开始我们需要将置换表中的每个值减1。这是最容易出错的地方之一务必小心。一个良好的实践是在代码中直接使用从0开始的置换表或者在注释中明确标出原始表。3.2 核心组件实现S盒与P盒S盒的实现是DES代码中最“硬编码”的部分。我们需要把8个S盒的定义原封不动地搬进来。每个S盒是一个4x16的矩阵在代码中可以用二维列表表示。# S盒定义示例S1盒其他类似 S1 [ [14, 4, 13, 1, 2, 15, 11, 8, 3, 10, 6, 12, 5, 9, 0, 7], [0, 15, 7, 4, 14, 2, 13, 1, 10, 6, 12, 11, 9, 5, 3, 8], [4, 1, 14, 8, 13, 6, 2, 11, 15, 12, 9, 7, 3, 10, 5, 0], [15, 12, 8, 2, 4, 9, 1, 7, 5, 11, 3, 14, 10, 0, 6, 13] ] def s_box_substitution(input_bits, s_box): 对6位输入进行S盒代换返回4位输出 # 输入是6个比特的列表 row (input_bits[0] 1) | input_bits[5] # 第1和6位决定行 col bit_string_to_int(input_bits[1:5]) # 中间4位决定列 value s_box[row][col] return int_to_bit_string(value, 4) def apply_all_s_boxes(expanded_xor_result): 将48位数据分成8组分别通过8个S盒返回32位结果 output [] for i in range(8): chunk expanded_xor_result[i*6:(i1)*6] s_box_output s_box_substitution(chunk, globals()[fS{i1}]) # 动态获取S盒 output.extend(s_box_output) return outputP盒的实现就简单多了它就是一个固定的置换。# P盒置换表32位输入 - 32位输出 P [16, 7, 20, 21, 29, 12, 28, 17, 1, 15, 23, 26, 5, 18, 31, 10, 2, 8, 24, 14, 32, 27, 3, 9, 19, 13, 30, 6, 22, 11, 4, 25] def p_box_permutation(s_box_output): 对S盒输出的32位进行P盒置换 return permute(s_box_output, P)3.3 轮函数F的完整实现现在我们可以把扩展置换E、异或、S盒、P盒组合起来形成完整的轮函数F。# 扩展置换E表32位 - 48位 E [32, 1, 2, 3, 4, 5, 4, 5, 6, 7, 8, 9, 8, 9, 10, 11, 12, 13, 12, 13, 14, 15, 16, 17, 16, 17, 18, 19, 20, 21, 20, 21, 22, 23, 24, 25, 24, 25, 26, 27, 28, 29, 28, 29, 30, 31, 32, 1] def round_function(right_half, subkey): 一轮的F函数输入32位右半部分和48位子密钥输出32位 # 1. 扩展置换 expanded permute(right_half, E) # 2. 与子密钥异或 xor_result [expanded[i] ^ subkey[i] for i in range(48)] # 3. S盒代换 s_box_output apply_all_s_boxes(xor_result) # 4. P盒置换 p_box_output p_box_permutation(s_box_output) return p_box_output3.4 密钥编排算法的实现密钥编排需要PC-1、循环左移、PC-2三个表。# 置换选择1 (PC-1) PC1 [57, 49, 41, 33, 25, 17, 9, 1, 58, 50, 42, 34, 26, 18, 10, 2, 59, 51, 43, 35, 27, 19, 11, 3, 60, 52, 44, 36, 63, 55, 47, 39, 31, 23, 15, 7, 62, 54, 46, 38, 30, 22, 14, 6, 61, 53, 45, 37, 29, 21, 13, 5, 28, 20, 12, 4] # 置换选择2 (PC-2) PC2 [14, 17, 11, 24, 1, 5, 3, 28, 15, 6, 21, 10, 23, 19, 12, 4, 26, 8, 16, 7, 27, 20, 13, 2, 41, 52, 31, 37, 47, 55, 30, 40, 51, 45, 33, 48, 44, 49, 39, 56, 34, 53, 46, 42, 50, 36, 29, 32] # 每轮循环左移的位数 SHIFT_SCHEDULE [1, 1, 2, 2, 2, 2, 2, 2, 1, 2, 2, 2, 2, 2, 2, 1] def generate_subkeys(master_key_bits): 从64位主密钥比特列表生成16个48位子密钥列表 # 1. PC-1置换得到56位有效密钥并分成C0, D0 key_56 permute(master_key_bits, PC1) C key_56[:28] D key_56[28:] subkeys [] for round_num in range(16): # 2. 循环左移 shift SHIFT_SCHEDULE[round_num] C C[shift:] C[:shift] D D[shift:] D[:shift] # 3. PC-2置换生成子密钥 combined_CD C D subkey permute(combined_CD, PC2) # 56位 - 48位 subkeys.append(subkey) return subkeys3.5 加密与解密主流程最后我们把所有部分组装起来。需要初始置换IP和末置换IP⁻¹的表。# 初始置换IP IP [58, 50, 42, 34, 26, 18, 10, 2, 60, 52, 44, 36, 28, 20, 12, 4, 62, 54, 46, 38, 30, 22, 14, 6, 64, 56, 48, 40, 32, 24, 16, 8, 57, 49, 41, 33, 25, 17, 9, 1, 59, 51, 43, 35, 27, 19, 11, 3, 61, 53, 45, 37, 29, 21, 13, 5, 63, 55, 47, 39, 31, 23, 15, 7] # 末置换IP^-1 (IP逆置换) IP_INV [40, 8, 48, 16, 56, 24, 64, 32, 39, 7, 47, 15, 55, 23, 63, 31, 38, 6, 46, 14, 54, 22, 62, 30, 37, 5, 45, 13, 53, 21, 61, 29, 36, 4, 44, 12, 52, 20, 60, 28, 35, 3, 43, 11, 51, 19, 59, 27, 34, 2, 42, 10, 50, 18, 58, 26, 33, 1, 41, 9, 49, 17, 57, 25] def des_crypt(input_bits, subkeys, is_encryptTrue): DES加密或解密核心函数 input_bits: 64位输入比特列表 subkeys: 16个48位子密钥列表 is_encrypt: True为加密False为解密 # 1. 初始置换IP permuted permute(input_bits, IP) L permuted[:32] R permuted[32:] # 2. 16轮Feistel网络 key_order subkeys if is_encrypt else subkeys[::-1] # 加密正序解密切序 for i in range(16): new_R [L[j] ^ key_order[i][j] for j in range(32)] # 这里简化了实际应先经过F函数 # 正确的应该是new_R xor(L, round_function(R, key_order[i])) # 我们先用伪代码下面更正 f_result round_function(R, key_order[i]) new_R [L[j] ^ f_result[j] for j in range(32)] L, R R, new_R # 注意Feistel结构的赋值 # 3. 最后一轮后交换L和R16轮后未交换所以需要再换一次 # 实际上标准DES的16轮后左右是不交换的直接合并成R16||L16 combined_final R L # 4. 末置换IP^-1 output_bits permute(combined_final, IP_INV) return output_bits def des_encrypt(plaintext_bits, key_bits): DES加密 subkeys generate_subkeys(key_bits) return des_crypt(plaintext_bits, subkeys, is_encryptTrue) def des_decrypt(ciphertext_bits, key_bits): DES解密 subkeys generate_subkeys(key_bits) return des_crypt(ciphertext_bits, subkeys, is_encryptFalse)3.6 处理字符串与字节我们上面操作的都是比特列表。实际应用中我们需要处理字符串或字节。import struct def string_to_bit_list(text): 将字符串或字节转换为64位8字节整数列表再转为比特列表 # 确保文本是8字节的倍数不足用零填充PKCS#5/PKCS#7填充是实际标准这里简化 while len(text) % 8 ! 0: text b\x00 if isinstance(text, bytes) else \x00 bit_list [] # 假设text是bytes for byte in text: bit_list.extend(int_to_bit_string(byte, 8)) return bit_list def bit_list_to_string(bit_list): 将比特列表转换回字节字符串 byte_list [] for i in range(0, len(bit_list), 8): byte_bits bit_list[i:i8] byte_val bit_string_to_int(byte_bits) byte_list.append(byte_val) return bytes(byte_list) # 示例使用 if __name__ __main__: # 密钥和明文8字节 key_hex 133457799BBCDFF1 plaintext_hex 0123456789ABCDEF # 将16进制字符串转换为比特列表 key_bits [] for i in range(0, len(key_hex), 2): byte_val int(key_hex[i:i2], 16) key_bits.extend(int_to_bit_string(byte_val, 8)) plaintext_bits [] for i in range(0, len(plaintext_hex), 2): byte_val int(plaintext_hex[i:i2], 16) plaintext_bits.extend(int_to_bit_string(byte_val, 8)) print(明文比特:, plaintext_bits[:64]) cipher_bits des_encrypt(plaintext_bits[:64], key_bits) # 只取前64位 print(密文比特:, cipher_bits) decrypted_bits des_decrypt(cipher_bits, key_bits) print(解密后比特:, decrypted_bits) print(是否匹配:, decrypted_bits plaintext_bits[:64])4. 实现过程中的常见陷阱与调试技巧自己实现DES就像拼一个复杂的乐高很容易在某个小步骤上出错导致最终结果对不上。下面是我在实现和教学中总结的几个最容易踩坑的地方和排查方法。4.1 比特顺序与字节顺序Endianness这是第一大坑。DES标准文档中描述比特序列时通常认为最高位Most Significant Bit, MSB是第1位。但在计算机存储和我们的代码中一个字节8位内的比特顺序以及多个字节之间的顺序需要仔细处理。问题表现你的加密结果和标准测试向量Test Vector对不上可能某几位是反的。解决方案统一约定在代码内部我们始终使用“列表索引0对应最高位”或“对应最低位”的一种约定。我建议采用列表索引0对应最高位MSB这样和大多数文档的描述一致。这意味着int_to_bit_string(0x8, 4)应该返回[1, 0, 0, 0]因为8的二进制是1000最高位是1。测试向量使用NIST等权威机构提供的标准测试向量。输入、密钥、输出都使用明确的十六进制表示。在将十六进制字符串转为比特列表时要确保你的转换函数符合你的比特顺序约定。例如对于字节0x12二进制00010010如果约定列表索引0是MSB那么转换后的列表应该是[0,0,0,1,0,0,1,0]。打印调试在关键步骤如初始置换IP后、第一轮F函数输入输出、第一轮子密钥生成后打印出比特序列或十六进制与手工计算或已知的中间结果进行比对。网上可以找到一些DES的逐步计算示例。4.2 置换表索引的偏移错误所有置换表IP, E, P, PC-1, PC-2, IP⁻¹在标准中都是从1开始计数的。如果你直接把这些数字当作Python列表的索引从0开始就会全部错位。问题表现结果完全错误与预期结果无任何相似性。解决方案在代码中预处理在定义置换表时就将其每个值减1。例如标准IP表第一个值是58我们在代码中应该存57。或者在permute函数内部进行减1操作return [bits[i-1] for i in permutation_table]。务必选择一种方式并贯穿始终。仔细核对特别检查PC-1和PC-2表它们丢弃了一些位校验位更容易出错。4.3 S盒查找的行列计算错误S盒的6位输入取第1位和第6位组成行号中间4位组成列号。这里的“第1位”指的是6位分组中的第一个比特在我们的列表中可能是索引0或索引5取决于你的比特顺序约定。问题表现加密结果错误但可能在前几轮还能看到后面就完全乱了因为S盒的非线性错误会迅速扩散。解决方案固定一个测试用例找一个简单的输入手动计算第一轮第一个S盒S1的输入和输出。例如让第一轮扩展置换后的右半部分与子密钥异或的结果的前6位是[0,1,1,0,1,1]。根据你的比特顺序约定确定行是(bit[0], bit[5])还是(bit[5], bit[0])。然后查S1表看输出是否与你的代码一致。编写单元测试为S_box_substitution函数编写针对性的测试用几个已知的输入输出对来验证。4.4 Feistel轮次处理中的左右交换错误这是逻辑错误的高发区。加密时每一轮结束后L, R R, new_R。但要注意new_R的计算公式是L_old ⊕ F(R_old, Ki)。在16轮全部结束后标准的DES是不交换左右两部分的直接合并成R16 || L16即最后一轮的右半部分放在前面左半部分放在后面再进行末置换。很多教科书图示在最后一轮画了个“不交换”的标记但代码中容易忽略。问题表现加密结果错误但解密可能还能还原出明文如果解密逻辑对称地错了或者加解密结果完全不对。解决方案严格对照伪代码找一份可靠的DES伪代码逐行对照你的实现。中间状态输出在每一轮结束后打印出L和R的值用十六进制简短表示与已知的正确中间状态进行比对。网上有一些提供每轮中间结果的测试用例。4.5 密钥编排中的循环左移循环左移是在C和D两个28位半密钥上分别进行的。移位位数表SHIFT_SCHEDULE是针对16轮的。注意我们是先有C0/D0然后根据第一轮的移位次数1位生成C1/D1并用C1/D1去生成子密钥K1。以此类推。问题表现加密结果错误但如果你使用别人生成的子密钥来测试你的加密函数是正常的那问题就出在密钥编排上。解决方案分步验证找一个已知的密钥手动计算或找到前两轮的C_i/D_i和K_i的值与你的程序输出对比。注意移位是累积的C_i和D_i是上一轮的C_{i-1}和D_{i-1}移位得到的而不是从原始的C0/D0移位i次总和。但实现时我们通常是在循环中不断更新C和D。4.6 使用已知测试向量进行整体验证这是最有效的调试方法。搜索“DES test vectors”或“DES known answer tests”你会找到类似下面的数据KEY 01345789ABCDEF (十六进制) PLAINTEXT 0123456789ABCDEF CIPHERTEXT 85E813540F0AB405 (十六进制)用你的程序加密这个明文和密钥看输出是否完全等于这个密文。如果不相等再结合上面的分步调试法定位问题。可以先验证密钥编排生成的16个子密钥是否正确再验证第一轮的加密中间结果。实操心得在实现这类位操作密集的算法时编写大量的小型单元测试是最高效的方法。为permute、s_box_substitution、generate_subkeys等每个独立函数都写测试用已知的小例子验证。这比写完全局代码再从头调试要快得多也稳健得多。5. 从DES实现到现代应用的安全思考当我们成功用Python实现DES后除了成就感更应该建立起一些重要的安全观念。DES在当今已不被推荐用于任何需要安全性的实际场景原因就在于其56位的密钥长度。2^56种可能在现代计算能力特别是GPU和专用硬件面前已经可以通过暴力枚举在合理时间内破解。5.1 为什么DES不安全密钥长度与暴力破解密码学的安全性建立在“计算不可行性”上。对于对称密码最直接的攻击就是尝试所有可能的密钥即暴力破解。DES的密钥空间是2^56 ≈ 7.2×10^16。这个数字在1970年代是天文数字但现在呢1998年电子前沿基金会EFF制造的专用硬件“Deep Crack”造价约25万美元能在平均4.5天内破解一个DES密钥。现在随着云计算和硬件性能的指数级增长暴力破解56位DES密钥的成本和时间已大大降低。对于有足够动机的攻击者如国家行为体、大型犯罪组织破解单次DES加密已非难事。因此绝对不要在任何真实的、需要保密性的系统中使用DES。它的教学意义远大于实用意义。5.2 3DES一个过渡方案为了应对DES密钥太短的问题在AES诞生之前人们提出了3DESTriple DES。顾名思义就是用DES算法处理三次。有两种常见模式EDE模式使用两个或三个密钥K1, K2, K3。加密C E_K3(D_K2(E_K1(P)))。解密P D_K1(E_K2(D_K3(C)))。其中E是加密D是解密。EEE模式直接加密三次C E_K3(E_K2(E_K1(P)))。使用两个密钥的3DESK1, K2, K1有效密钥长度是112位使用三个密钥则是168位。这大大增加了暴力破解的难度。3DES目前在一些遗留系统中仍有使用但其速度慢是DES的三倍且块大小仍是64位在某些模式下可能存在安全问题。它最终也被AES取代。5.3 AESDES的继任者AESAdvanced Encryption Standard高级加密标准是NIST在2000年选定的新标准取代DES。获胜算法是Rijndael。密钥长度支持128、192、256位彻底解决了密钥长度问题。分组长度128位16字节比DES的64位更优能处理更多数据且在某些模式下更安全。结构AES不是Feistel结构而是SPNSubstitution-Permutation Network代换-置换网络结构。它每一轮都包含字节代换S-Box、行移位、列混合最后一轮除外和轮密钥加操作。其数学基础更加严谨在有限域GF(2^8)上操作。性能与安全AES设计时就考虑了在软硬件上的高效实现并且经过了更充分的分析和验证目前没有已知的、对完整轮次AES的有效攻击除了边信道攻击。5.4 在Python中使用现代加密库理解了DES的原理后在实际项目中我们应该使用经过严格审计的、高效的现代加密库。在Python中首推cryptography库。from cryptography.hazmat.primitives.ciphers import Cipher, algorithms, modes from cryptography.hazmat.backends import default_backend import os # 加密 def aes_encrypt(plaintext, key): # 密钥必须是16(AES-128), 24(AES-192), 或32(AES-256)字节 iv os.urandom(16) # 生成随机初始化向量用于CBC等模式 cipher Cipher(algorithms.AES(key), modes.CBC(iv), backenddefault_backend()) encryptor cipher.encryptor() # 需要填充因为AES是块密码 padder padding.PKCS7(128).padder() padded_data padder.update(plaintext) padder.finalize() ciphertext encryptor.update(padded_data) encryptor.finalize() return iv ciphertext # 通常将IV和密文一起存储/传输 # 解密 def aes_decrypt(ciphertext_with_iv, key): iv ciphertext_with_iv[:16] ciphertext ciphertext_with_iv[16:] cipher Cipher(algorithms.AES(key), modes.CBC(iv), backenddefault_backend()) decryptor cipher.decryptor() padded_plaintext decryptor.update(ciphertext) decryptor.finalize() unpadder padding.PKCS7(128).unpadder() plaintext unpadder.update(padded_plaintext) unpadder.finalize() return plaintext # 使用示例 key os.urandom(32) # 256位密钥 secret_message bThis is a secret message. encrypted aes_encrypt(secret_message, key) decrypted aes_decrypt(encrypted, key) print(decrypted secret_message) # 输出: True重要安全提示自己实现的DES甚至自己实现的AES只能用于学习和测试。用于生产环境的密码学代码必须使用像cryptography、PyCryptodome这样的成熟库。这些库经过了无数安全专家的审查正确实现了算法并提供了安全的默认参数如操作模式、填充方式还能抵御时序攻击等边信道攻击这是自己实现的代码难以做到的。通过这个从零实现DES的过程我们不仅学会了算法本身更重要的是建立了对对称加密的直观认识理解了“混淆”和“扩散”是如何通过S盒和P盒等操作一步步实现的也明白了为什么DES会被淘汰以及在实际中应该如何正确、安全地使用加密技术。这才是学习经典算法最大的价值。

相关新闻