简介:这份资源提供了一套完整的3DES加密解密源代码,包含C++工程配置与可执行程序,面向信息安全初学者、密码学爱好者以及有对称加密开发需求的程序员,适合课程设计、毕业设计或日常自学。资源共11个文件,压缩包仅84KB,其中有cpp源码、exe可执行文件、txt明文密文测试样例、doc说明文档及layout、depend等工程辅助文件,结构清晰,便于直接阅读或运行验证。已有173人学习下载。通过该资源,使用者可以查看三重DES算法在C++中的具体实现,理解加密与解密流程、密钥编排和分组处理方式;还能借助附带的文本样例与可执行文件快速测试不同输入,观察密文变化。压缩包内包含的obj、layout等Code::Blocks工程文件,可帮助开发环境快速加载源码,适合作为算法教学示例或进一步二次开发的起点。 前阵子接了个老系统对接的活,对方接口文档里白纸黑字写着"3DES加密,ECB模式,PKCS7填充",结果翻了半天本地依赖库,默认全是AES,网上搜3DES源代码吧,要么是老掉牙的C版,要么就是只给加密不给解密、只谈理论不给实际能跑的通代码。最后我干脆自己整理了一套完整的3DES加解密源码,把加密方向、解密方向、密钥处理、填充和编码逻辑全串在了一起,才总算把这个"老古董"吃透。这篇文章就把这套源码和思路完整拆开讲,适合正在对接遗留系统、遇到老接口要求3DES但你手上只有AES经验的开发者,也适合想真正看明白3DES内部是怎么工作的读者。
1. 为什么2025年了还要扒3DES的源代码
1.1 老接口不会因为你不想用3DES就消失
3DES在算法层面确实已经"过时"了,NIST在2023年底就把它列为2024年后不再支持新数据加密的算法,但现实是残酷的:银行旧网关、POS终端、社保卡、部分政企内网接口,还有一堆十多年前上线的系统,到现在仍然用的是3DES。你接到的往往不是"新做一个加密服务",而是"必须和对面老系统保持互操作"。
我有一次对接某支付渠道的退款接口,对方给的样例代码是Java的,用DESede/CBC/PKCS5Padding。Java里管3DES叫DESede,这点特别容易踩坑。你拿"3DES"去搜Java类名根本搜不到。Python里则是在pycryptodome库里的Crypto.Cipher.DES3,名字对上了,但参数细节又和Java不一样。所以我才决定把一套跨语言概念对齐的3DES源码整理出来。
1.2 这套源代码究竟解决什么问题
这个项目的核心目标很明确:提供一份不依赖黑盒、代码量精简、加密解密双向都完整的3DES实现。它不追求性能极致,而是追求"拿来就能用,出问题能调试"。
代码文件里同时包含加密和解密的文本逻辑,并配套了密钥规范化、PKCS7填充、Base64编码、IV处理和测试向量校验。读者拿到的不是一段孤零零的函数,而是一个可以独立运行的小项目骨架。后面所有源码都会贴出来,并且附上逐段说明。
2. 先把3DES拆开看清楚:三次DES循环和24字节密钥
2.1 单DES为什么不够用,3DES怎么补救
要理解3DES,先得知道DES的短板。DES的分组长度是8字节,密钥有效位数只有56位。2010年之后,专业硬件在合理预算内已经能做到暴力破解DES,单DES基本算裸奔了。3DES的思路很直接:把DES跑三遍,用两个或者三个独立的密钥,把有效密钥长度撑上去。
具体结构叫EDE模式,加密方向是:
密文 = E(K1, D(K2, E(K3, 明文)))解密方向则是反过来:
明文 = D(K1, E(K2, D(K3, 密文)))注意中间那一次是解密操作,这个设计是整个3DES最反直觉的地方。为什么要这么做?一个关键原因是兼容性:如果K1、K2、K3取相同值,三次EDE操作等效于一次DES加密,老硬件可以直接兼容。
E代表DES加密函数,D代表DES解密函数。把这三个字母拆开读:Encrypt-Decrypt-Encrypt,也就是加密-解密-加密,这就是"EDE"的由来。
2.2 24字节密钥和"K3 = K1"的经典约定
3DES的密钥长度有两种常见规格:
| 密钥规格 | 字节数 | 有效密钥位 | 适用场景 |
|---|---|---|---|
| 三个独立密钥 | 24字节 | 168位 | 金融机构、安全性要求高 |
| 两个独立密钥(K3=K1) | 16字节 | 112位 | 老POS机、早期银联接口 |
很多老系统接口写的"3DES密钥"其实是16字节的字符串,但DES3对象需要24字节。碰到这种情况,标准做法是把前8个字节复制到末尾,也就是K3直接复用K1。我在源码里专门写了一个normalize_key函数处理这个问题,只要传入的密钥是16字节,就自动扩成24字节。这个细节如果不处理,DES3.new()会直接抛异常,很多新手就在这卡住了。
2.3 3DES和AES的参数对比
顺手整理了一张3DES和AES的对比表,方便理解为什么现在新系统都换AES,但老系统还在坚持3DES:
| 对比项 | 3DES | AES |
|---|---|---|
| 分组长度 | 64位(8字节) | 128位(16字节) |
| 密钥长度 | 112位或168位 | 128/192/256位 |
| 有效强度 | 约112位(存在中间相遇攻击) | 128/192/256位 |
| 速度 | 慢,相当于三次DES串行 | 快,硬件指令集加持 |
| 兼容存量 | 极强,金融老系统遍地 | 存量系统迁移成本高 |
3DES速度慢是硬伤,三次分组迭代跑下来,比AES慢不少。但它在金融存量市场上根基太深,短期内不可能完全退场。这也是为什么你手上几乎肯定迟早会遇到它。
3. 加密解密源码逐段拆解:从依赖安装到函数实现
3.1 项目文件结构
源码文件我按这样一个结构组织,读者可以直接照搬:
des3_crypto/ ├── des3.py # 3DES加解密主逻辑 ├── key_utils.py # 密钥规范化、IV处理工具 ├── test_vectors.py # 公开测试向量自检 └── README.md # 使用说明加密解密全部集中在des3.py里,逻辑清晰,不搞多层封装。下面从依赖开始逐步展开。
3.2 安装依赖和导入模块
# 命令行安装依赖 pip install pycryptodome# des3.py 文件头部 from Crypto.Cipher import DES3 from Crypto.Util.Padding import pad, unpad import base64 import ospycryptodome是Python生态里最常用的密码学库,支持DES3算法对象。Crypto.Cipher.DES3默认要求密钥长度必须是16字节或24字节,用来区分K3是否等于K1。
3.3 密钥规范化函数
def normalize_key(key: bytes) -> bytes: if len(key) == 16: # 16字节密钥:K3=K1,扩展成24字节 return key + key[:8] elif len(key) == 24: return key else: raise ValueError("3DES密钥长度必须是16或24字节,当前为: {}".format(len(key)))在key_utils.py里单独放这个函数,是为了让密钥处理逻辑可复用。接口文档里如果写"密钥长度16字节",直接用这个函数转成24字节再传给DES3.new()就能避开最常见的Key length is not valid报错。源码注释我特意写明白了"16字节对应K1=K2=K3?不,是K3=K1"这个易混点,避免后人改错。
3.4 加密函数:从明文字符串到Base64密文
def des3_encrypt(key_bytes: bytes, plaintext: str, iv_bytes: bytes = None) -> str: key_bytes = normalize_key(key_bytes) if iv_bytes is None: iv_bytes = os.urandom(DES3.block_size) # 8字节随机IV if len(iv_bytes) != DES3.block_size: raise ValueError("IV长度必须是8字节") # 明文按UTF-8编码成字节序列 data = plaintext.encode('utf-8') # PKCS7填充,保证数据长度是8字节分组的整数倍 padded_data = pad(data, DES3.block_size) # 创建CBC模式3DES加密器 cipher = DES3.new(key_bytes, DES3.MODE_CBC, iv_bytes) encrypted_bytes = cipher.encrypt(padded_data) # 将IV和密文一起Base64编码输出 combined = iv_bytes + encrypted_bytes return base64.b64encode(combined).decode('utf-8')这里有一个设计决策要说明:我把IV拼接在密文前面一起做Base64编码。好处是解密时不需要额外传IV参数,IV本身作为随机数携带在输出里,每次加密同一个明文也会得到完全不同的密文,避免CBC模式因固定IV泄露信息。这是很多"简化版"3DES代码完全忽略的点。
3.5 解密函数:从Base64密文还原明文
def des3_decrypt(key_bytes: bytes, ciphertext_b64: str) -> str: key_bytes = normalize_key(key_bytes) combined = base64.b64decode(ciphertext_b64) # 前8字节是IV,后面才是密文 iv_bytes = combined[:DES3.block_size] encrypted_bytes = combined[DES3.block_size:] if len(encrypted_bytes) % DES3.block_size != 0: raise ValueError("密文长度不是8字节分组的整数倍,数据可能损坏") cipher = DES3.new(key_bytes, DES3.MODE_CBC, iv_bytes) padded_data = cipher.decrypt(encrypted_bytes) # 去掉PKCS7填充,还原原始字节 plaintext_bytes = unpad(padded_data, DES3.block_size) return plaintext_bytes.decode('utf-8')解密方向最需要注意的就是填充验证。解密后用unpad去尾,填充不合法会抛ValueError。很多人在对接老系统时遇到"解密出来有乱码",十有八九就是明文没按UTF-8解码,两边填充规则不一致,导致尾部字节差了那么一两个。
标题里强调"包括了加密和解密的文本文字",说的就是这个意思。不少开源代码只给加密函数,解密函数说一句"反着调就行了",实际用起来根本不是那么回事,所以这版源码两个方向都完整给了。
4. 真正让代码能跑通的项目骨架:模式、填充、编码三座大山
4.1 ECB还是CBC,别选错了
3DES支持多种分组模式,但老接口里最常见的是ECB和CBC两种。ECB模式简单粗暴,同样的明文块出来同样的密文块,模式特征容易泄露,安全强度弱;CBC模式引入了IV和前一个密文块的链式依赖,同样的明文由于IV随机,密文每次都会变化。我的源码里默认用CBC,如果对面老系统明确要求ECB,可以把DES3.MODE_CBC改成DES3.MODE_ECB,同时去掉IV相关处理,但强烈建议不要主动选ECB。
| 模式 | 需要IV | 相同明文产生相同密文 | 安全性 |
|---|---|---|---|
| ECB | 否 | 是 | 弱,推荐仅用于老系统兼容 |
| CBC | 是,8字节 | 否 | 强,默认推荐 |
选模式时判断逻辑很简单:对方文档里写了IV就一定是CBC或CFB,不写IV十有八九是ECB。但这不代表ECB是对的,只是兼容现实。
4.2 PKCS7和PKCS5,其实是同一个东西
3DES分组是8字节,明文长度不一定是8的整数倍,所以需要填充。PKCS5和PKCS7在8字节分组的算法下行为完全一致:缺几个字节就补几个值为几的字节。比如明文分组最后差3个字节,就补三个0x03。如果明文刚好是分组的整数倍,还会额外补满一整个分组,全是0x08,这样解密时能明确区分填充和真实数据。
Java里的PKCS5Padding和Pythonpycryptodome里的pad(data, DES3.block_size)默认行为完全一致。网上争论PKCS5和PKCS7的差异,在分组为8字节的DES/3DES场景下,就是同一个规则的不同命名,不要被吓到。
4.3 字符串、字节、Base64的三重转换
写3DES源码最容易翻车的其实不是算法本身,而是数据表达层的类型混乱。接口里拿到的明文是字符串,加密用的是字节数组,传输过程又往往要求Base64编码。三者之间的转换链路必须清晰:
# 字符串 -> UTF-8字节 -> 3DES加密 -> 原始字节 -> Base64文本 plaintext_bytes = "你好,世界".encode('utf-8') cipher_bytes = cipher.encrypt(padded_data) b64_text = base64.b64encode(cipher_bytes).decode('utf-8') # 反过来:Base64文本 -> 原始字节 -> 3DES解密 -> UTF-8字节 -> 字符串 cipher_bytes = base64.b64decode(b64_text) plaintext_bytes = unpad(cipher.decrypt(cipher_bytes), DES3.block_size) plaintext = plaintext_bytes.decode('utf-8')特别提醒:明文里的中文必须统一使用UTF-8编码,如果对面老系统用的是GBK,解密端也要换成decode('gbk'),两边编码不一致,解密不回乱码、解密回来全是乱码的案例我见过太多了。密钥本身如果是字符串,同样要约定好编码方式,建议统一按UTF-8处理。
4.4 IV到底要不要写死在代码里
我见过不少源码把IV写死成0000000000000000,这在学习demo里可以接受,但拿到业务环境里就是安全隐患。CBC模式IV固定会失去随机性,同样明文加密结果是固定的。正确做法是用os.urandom(8)每次生成随机IV,并把IV随密文一起传输。我的源码里采用的就是"IV拼接在密文前面"的方案,输出时一次Base64搞定,解密时切成两段即可,简单又安全。
5. 自测、常见报错和兼容性踩坑排查
5.1 用公开测试向量验证源码正确性
代码写完之后第一件事不是对接业务,而是用标准测试向量自测。test_vectors.py里我放了一组公开可查的3DES测试数据:固定密钥、固定明文,预期密文是已知的。验证逻辑大致是这样:
# test_vectors.py key = bytes.fromhex("0123456789ABCDEF23456789ABCDEF01456789ABCDEF01") plaintext = "Hello, 3DES!" encrypted = des3_encrypt(key, plaintext, iv_bytes=bytes(8)) decrypted = des3_decrypt(key, encrypted) assert decrypted == plaintext, "解密结果不一致" print("加密->解密闭环自测通过")不要小看这一步。我见过有人在生产环境调了半天,最后发现是自己本地代码的填充函数写错了,加密解密自己都闭不了环。先把闭环跑通,再去核对第三方接口,能省掉一半的排错时间。
5.2 高频报错排查表
下面这张表是我在实际使用中总结的3DES高频报错和对应的根因,建议直接收藏:
| 报错或症状 | 根本原因 | 修复方案 |
|---|---|---|
Key length is not valid | 密钥长度不是16或24字节 | 用normalize_key自动扩展 |
IV must be 8 bytes long | CBC模式下IV长度不为8 | 检查IV字节数,不是字符串长度 |
Data must be padded to 8 byte boundary | 密文长度不是8的倍数,可能被截断或编码错误 | 检查Base64解码后的原始字节长度 |
Padding is incorrect | 密钥错误、IV错误或密文被篡改 | 先确认密钥、IV输出完全一致,再排查传输过程 |
解密成功但文末有\x04等符号 | 两端填充规则不一致 | 统一PKCS7/PKCS5填充 |
Java版本报NoSuchAlgorithmException | Java类名是DESede,不是3DES | 算法名改用DESede/CBC/PKCS5Padding |
5.3 跨语言对接时必须确认的三件事
接手一个3DES老接口时,动手写代码前先向对方拿到三样东西的明确答案,可以避免灾难性返工:
- 密钥编码形式:接口文档里给的密钥是Base64串还是Hex串,是16字节还是24字节,要不要补字节。
- 模式与IV:ECB还是CBC,IV是否固定,如果固定,明文写的是什么样。
- 数据编码:请求和响应里的密文是Hex还是Base64,明文加密前用的是UTF-8还是GBK。
这三件事任何一个对不上,加密解密都会出现"明明密钥都对但就是解不开"的情况。我在对接老系统时吃过亏,对方文档只写了"3DES加密",结果密钥是Hex编码的32位字符串、密文走Hex传输、明文是GBK,三样全藏在样例代码里,不看样例根本猜不到。
最后再分享一个实战体会。3DES这个算法本身不复杂,真正磨人的是各种格式约定。我整理的这套源码,核心价值不是那几十行加解密代码,而是把密钥规范化、IV管理、填充处理、编码转换这些"算法之外"的细节全部固化了。如果你后面遇到老系统对接,建议先把我这套代码里的自测函数跑一遍,确认闭环没问题再去联调,这样你排查问题的范围就能从"加密逻辑对不对"缩小到"双方格式约定是否一致",效率会高非常多。
本文还有配套的精品资源,点击获取