简介这份资源是应用密码学实验六的完整工程包面向正在学习密码学课程、需要动手实现综合安全通信系统的高校学生与自学者。它围绕密钥协商、对称加密或序列密码加密、数字签名及签名认证等环节给出一个可编译运行的通信系统实例帮助读者把分散的密码算法串成完整流程理解安全通信中各模块的协作方式。压缩包共289个文件约85.63MB以tlog、obj、pdb、ilk等编译中间与调试文件为主另含h头文件、cpp源文件、dll动态库、exe可执行程序及sln解决方案等工程文件便于直接打开工程对照源码学习。已有470人学习下载适合作为课程实验参考读者可从中获取密钥协商与签名验证的实现思路、客户端与服务端通信的代码组织方式以及调试排错时可借鉴的工程结构。1. 一个安全通信系统实验包从密钥协商到密文传输的完整链路做密码学课程设计时最让人头疼的不是单个算法写不出来而是把对称加密、非对称加密、哈希校验、数字签名这几块拼成一个能跑通的通信系统。很多同学交上来的代码AES 单独测能过RSA 单独测能过一联调就翻车——密钥传错了、填充对不上、签名验不过。这个实验包解决的正是这个断层问题它把“一个安全的通信系统”拆成可独立验证的模块再串成一条完整的收发链路。适合正在做应用密码学实验、需要交课程设计、或者想搞明白“真实通信里密码学到底怎么落地”的从业者和学生。下面按我拆包复现的顺序讲重点放在参数怎么设、坑在哪。2. 拆开实验包模块划分与密码学选型逻辑拿到压缩包先别急着跑代码先看清楚它把系统切成了哪几块。一个安全的通信系统核心要回答四个问题身份怎么确认、密钥怎么安全到达、数据怎么加密、完整性怎么保证。实验包的目录结构通常围绕这四个问题组织我拆过的同类包里典型划分是这样的模块承担职责常见算法选型密钥协商双方在不安全信道上生成共享密钥DH / ECDH对称加密大批量数据的机密性AES-CBC / AES-GCM非对称加密密钥封装或小数据加密RSA-OAEP哈希与签名完整性校验、身份认证SHA-256 RSA/ECDSA这个划分不是随便定的。对称加密快但密钥分发是死穴非对称加密解决分发但慢得离谱所以真实系统一律是“非对称协商出对称密钥再用对称密钥加密数据”。实验包如果按这个思路组织说明设计是靠谱的如果它让你直接用 RSA 加密整段消息那基本是教学简化版跑通可以别当成生产方案。2.1 先确认运行环境与依赖拆包第一步永远是看依赖。Python 实现的密码学实验包常见依赖是pycryptodome提供 AES、RSA、哈希或cryptography。先建虚拟环境再装别污染全局python -m venv venv source venv/bin/activate # Windows 用 venv\Scripts\activate pip install pycryptodome cryptography装完先跑一个最小验证确认库能用、版本没冲突from Crypto.Cipher import AES from Crypto.PublicKey import RSA from Crypto.Hash import SHA256 # 验证 AES 可用密钥长度必须是 16/24/32 字节 key b0 * 32 cipher AES.new(key, AES.MODE_GCM) print(AES-GCM 初始化成功nonce 长度:, len(cipher.nonce)) # 验证 RSA 密钥生成 rsa_key RSA.generate(2048) print(RSA 模长:, rsa_key.size_in_bits())这段的作用是把“环境是否就绪”和“业务逻辑”分开。很多人一上来就跑主程序报错信息淹没在业务代码里根本分不清是环境问题还是逻辑问题。先做这个冒烟测试AES 和 RSA 都能初始化再往下走。参数上注意AES 密钥长度只能是 16、24、32 字节对应 AES-128/192/256RSA 模长教学用 2048 位够了1024 位现在不安全别用。2.2 密钥协商模块DH 参数怎么选密钥协商是整条链路的起点也是最容易出玄学问题的地方。实验包如果用的是 Diffie-Hellman核心参数是素数 p 和生成元 g。教学包里常见两种写法一种硬编码一组标准参数一种现场生成。现场生成大素数很慢我一般直接用标准组# 使用标准 DH 参数RFC 3526 的 2048 位组教学场景够用 P int( FFFFFFFFFFFFFFFFC90FDAA22168C234C4C6628B80DC1CD1 29024E088A67CC74020BBEA63B139B22514A08798E3404DD EF9519B3CD3A431B302B0A6DF25F14374FE1356D6D51C245 E485B576625E7EC6F44C42E9A637ED6B0BFF5CB6F406B7ED EE386BFB5A899FA5AE9F24117C4B1FE649286651ECE45B3D C2007CB8A163BF0598DA48361C55D39A69163FA8FD24CF5F 83655D23DCA3AD961C62F356208552BB9ED529077096966D 670C354E4ABC9804F1746C08CA18217C32905E462E36CE3B E39E772C180E86039B2783A2EC07A28FB5C55DF06F4C52C9 DE2BCBF6955817183995497CEA956AE515D2261898FA0510 15728E5A8AACAA68FFFFFFFFFFFFFFFF, 16 ) G 2 def dh_keypair(): private int.from_bytes(__import__(os).urandom(32), big) public pow(G, private, P) return private, public def dh_shared(private, peer_public): return pow(peer_public, private, P)逻辑很直白双方各自生成私钥算出公钥发给对方再用对方公钥和自己的私钥算出同一个共享值。参数说明p 用 2048 位标准组g 取 2私钥用 32 字节随机数。这里有个血泪经验——共享值算出来是个大整数不能直接当 AES 密钥用必须过一遍哈希比如 SHA-256截取到 32 字节否则长度对不上AES 直接报错。实验包里如果已经做了这步说明作者踩过坑没做的话你自己补上。3. 把加密链路跑通AES-GCM 与签名验证的实操密钥协商完接下来是数据加密和完整性保护。这里我强烈建议用 AES-GCM 而不是 AES-CBC原因后面避坑章节细说。GCM 是认证加密加密的同时生成认证标签解密时自动校验省掉单独做 HMAC 的麻烦。3.1 AES-GCM 加密与解密的完整调用from Crypto.Cipher import AES from Crypto.Random import get_random_bytes import hashlib def derive_aes_key(shared_secret): # 共享值转 32 字节密钥用 SHA-256 截断 return hashlib.sha256(str(shared_secret).encode()).digest() def encrypt_message(key, plaintext, aadb): cipher AES.new(key, AES.MODE_GCM) cipher.update(aad) # 附加认证数据可选 ciphertext, tag cipher.encrypt_and_digest(plaintext) return cipher.nonce, ciphertext, tag def decrypt_message(key, nonce, ciphertext, tag, aadb): cipher AES.new(key, AES.MODE_GCM, noncenonce) cipher.update(aad) return cipher.decrypt_and_verify(ciphertext, tag)参数说明nonce由库自动生成12 字节绝对不能重复使用——同一个密钥下 nonce 重复GCM 的安全性直接崩掉。aad是附加认证数据不加密但参与完整性校验适合放消息头、时间戳这类需要防篡改的明文。tag是 16 字节认证标签解密时必须带上decrypt_and_verify校验失败会抛ValueError这个异常必须捕获不能吞掉。调用顺序上发送方先协商密钥、派生 AES 密钥、加密、把 nonce 和 tag 随密文一起发接收方用同样的共享值派生密钥、拿 nonce 和 tag 解密。这里最容易翻车的是 nonce 和 tag 的传输——它们不是秘密但必须完整到达少一个字节都解不开。3.2 数字签名确认消息来源加密解决的是“别人看不到”签名解决的是“确认是谁发的”。实验包里通常用 RSA 签名或 ECDSA。RSA 签名流程是发送方用自己的私钥对消息哈希签名接收方用发送方公钥验签。from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 from Crypto.PublicKey import RSA def sign_message(private_key, message): h SHA256.new(message) return pkcs1_15.new(private_key).sign(h) def verify_signature(public_key, message, signature): h SHA256.new(message) try: pkcs1_15.new(public_key).verify(h, signature) return True except (ValueError, TypeError): return False注意签名对象是“消息的哈希”而不是消息本身pkcs1_15内部会再做一次编码。验签失败抛异常这里用 try 包住返回布尔值方便上层判断。参数上签名用的 RSA 密钥和加密用的 RSA 密钥最好分开教学场景混用问题不大但生产环境密钥用途必须隔离这是基本纪律。3.3 串成完整收发流程把上面几块拼起来一次完整通信是这样的A 生成 DH 密钥对B 生成 DH 密钥对互换公钥各自算出共享值并派生 AES 密钥A 用 AES-GCM 加密消息同时用自己的 RSA 私钥对密文签名B 收到后先验签、再解密。这个顺序不能反——先验签再解密能挡住大部分伪造消息避免对恶意数据做解密运算。# 伪代码示意完整链路 a_priv, a_pub dh_keypair() b_priv, b_pub dh_keypair() shared_a dh_shared(a_priv, b_pub) shared_b dh_shared(b_priv, a_pub) assert shared_a shared_b # 这一步必须相等否则链路断了 aes_key derive_aes_key(shared_a) nonce, ct, tag encrypt_message(aes_key, bhello secure world) sig sign_message(a_rsa_priv, ct) # 接收端 assert verify_signature(a_rsa_pub, ct, sig) plain decrypt_message(aes_key, nonce, ct, tag)assert shared_a shared_b这行看着多余实际是排错神器。两边算出来不相等八成是公钥传错了或者参数组不一致早发现早定位。4. 避坑与排查五个真实翻车现场4.1 解密报 MAC check failed现象decrypt_and_verify抛ValueError: MAC check failed。原因通常有三个密钥不一致、nonce 传错、密文被截断。先打印两边派生出的 AES 密钥哈希值对比不一致就回头查 DH 共享值密钥一致就查 nonce 和 tag 是否完整传输。解决在加密端和解密端各打一行密钥指纹日志定位是协商问题还是传输问题。4.2 RSA 签名验不过但密钥没错现象公钥确认是对方的签名就是验不过。原因多半是签名和验签用的哈希算法不一致或者签名时对原始消息、验签时对密文。解决统一约定“对什么签名”——是明文、密文还是消息摘要写进协议文档两端严格照做。我见过最隐蔽的一次是发送端签了明文、接收端拿密文验签查了一下午。4.3 DH 共享值长度不对导致 AES 报错现象AES.new报 key 长度错误。原因直接把 DH 共享的大整数当密钥传了。解决共享值必须过 KDF密钥派生函数最简单是 SHA-256 取 32 字节规范做法是 HKDF。别偷懒用str(shared)[:32]字符编码会引入不可控字节。4.4 nonce 复用导致密文可预测现象系统能跑但安全性形同虚设。原因为了“省事”把 nonce 写死成固定值。GCM 下同一密钥复用 nonce攻击者能恢复明文甚至伪造密文。解决每次加密都用get_random_bytes(12)生成新 nonce随密文一起传。这是红线没有例外。4.5 大文件一次性读入内存爆掉现象加密稍大的文件就卡死或内存溢出。原因encrypt_and_digest一次性处理全部数据。解决分块加密每块独立 nonce或者改用流式加密模式。教学包数据量小不明显一旦换成真实文件立刻暴露。5. 进阶验证用测试向量给系统做体检系统跑通不等于正确。我习惯用标准测试向量test vector反向验证每个模块这比“能加密能解密”靠谱得多。AES-GCM 有 NIST 公布的官方测试向量拿一组已知密钥、nonce、明文、密文、tag 对进去输出完全一致才算实现正确。# NIST AES-GCM 测试向量简化示例实际请对照官方文档 key bytes.fromhex(00000000000000000000000000000000) nonce bytes.fromhex(000000000000000000000000) plaintext bytes.fromhex() # 空明文时期望 tag 为 58e2fccefa7e3061367f1d57a4e7455a cipher AES.new(key, AES.MODE_GCM, noncenonce) ct, tag cipher.encrypt_and_digest(plaintext) assert tag.hex() 58e2fccefa7e3061367f1d57a4e7455a这个断言过了说明 GCM 实现和标准一致过不了要么库版本有问题要么参数传错。同样的思路可以套到 SHA-256、RSA 签名上每个模块都有公开向量。做完这轮体检再回头跑完整链路心里才有底。另一个进阶技巧是给通信链路加“重放攻击”防护在消息里带时间戳或递增序号接收端维护一个窗口超窗或重复的直接丢弃。实验包如果没做这层自己补上这是从“能跑”到“能用”的关键一步。我一般会在 AAD 里塞时间戳验签通过后再校验时间窗口两步都过才解密。从那以后我每次拆这类密码学实验包都强制先跑测试向量再碰业务代码——链路能跑通只是及格线和标准对得上才算数。希望帮到你。本文还有配套的精品资源点击获取