news 2026/9/1 20:25:23

应用密码学实验踩坑实录:从AES、RSA到PKI的动手实践指南

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
应用密码学实验踩坑实录:从AES、RSA到PKI的动手实践指南

简介:本资源是吉林大学应用密码学课程配套的四次综合性实验实现代码与文档,面向密码学初学者、信息安全专业学生及密码算法实践者,聚焦分组密码设计、公钥密码实现、混合加密系统构建与盲签名协议落地等核心能力训练。压缩包共27个文件,含5个C++源码(.cpp)、5个可执行程序(.exe)、7个编译中间文件(.o)及4个工程配置文件(.cfp/.cfpg),辅以1份Word实验说明文档和1个明文测试文件,整体大小1.02MB,结构清晰,便于按实验模块快速定位源码与运行环境。已有1731人学习下载,资源完整覆盖Feistel结构128位分组密码(含LFSR轮函数)、基于NTL库的RSA密钥生成/加解密、数字信封(OFB模式+会话密钥封装)及Chaum盲签名四大实验要求,提供可直接编译运行的MinGW环境工程,附带输入输出验证逻辑,显著降低密码算法工程化门槛。 大学里许多课程都是"上课听懂了,考试会做了,但合上书本还是什么都不明白"。应用密码学这门课尤其如此。密码学本质上是用数学构造安全协议的工程学科,很多概念——比如RSA的欧拉函数、AES的列混合、数字签名里的哈希摘要——在黑板上推导是一回事,真正用代码把它们"跑"起来是另一回事。我在吉林大学选修应用密码学实验课时,最大的感受是:实验课才是真正打开这门学科的钥匙。哪怕只是把教材里的算法原样实现一遍,收获都比刷十遍课后题要大。这篇文章把我做实验的完整经历、代码细节、踩过的坑和实验报告的心得整理出来,纯粹是个人经验的参考分享,希望能给正在修这门课或者准备自学的同学一些帮助。内容里涉及的实验代码和配置我都实际跑通过,但不同学期、不同老师的实验要求差异很大,务必以你自己的课程大纲为准,这里只提供思路和避坑方向。

1. 课程实验的整体脉络:从"看书全会、动手全废"到理解密码学

应用密码学实验在吉大计算机/软件相关专业的课程体系中,通常不是单独一门课,而是依附在"应用密码学"或"信息安全基础"这门理论课之下的实践环节。我那一届大概是两学分的课,其中实验占三成左右的分,需要提交四到五次实验报告,最后一次通常是一个综合性的小项目。整体难度不高,但对动手习惯和细节考究程度的要求远超想象。

1.1 实验题目的一般分布规律

我查了校内论坛和前几届学长留下的参考资料,也结合我们班的实际题目,发现实验题目的分布基本绕不开这几块:

  • 对称密码:通常是AES的实现或应用,比如用AES-CBC加解密文件,或者把DES和AES做对比实验。
  • 非对称密码:RSA的密钥生成与加解密,有的年份会要求实现RSA签名,或者干脆用OpenSSL的命令行完成一次完整流程。
  • 哈希函数:实现或调用SHA-256,做雪崩效应测试(明文改一个bit,看密文或摘要变化多少bit)。
  • 数字签名与认证:结合哈希和RSA做签名验签,有的年份是搞HMAC。
  • 综合实验:搭建一个简易的安全通信流程(AES加密数据+RSA交换密钥),或者用证书完成一次TLS握手过程的模拟。

这个分布其实非常合理,它遵循了密码学最核心的"混合加密"体系:用公钥密码协商出对称密钥,用对称密码加密大量数据,用哈希保证完整性,用数字签名保证来源和不可否认性。理解了这个脉络,后面每个实验就不再是孤立的点,而是一条完整的安全链路。

1.2 实验环境选型:Python还是C,OpenSSL还是手写

在做第一个实验之前,我纠结了很久:用C语言去写AES还是用Python调库?后来我问了上届的学长,他的建议非常实在——实验的核心是理解算法和协议,不是重复造轮子。除非老师特别要求"从零实现AES的字节替换、列混合",否则一律推荐用Python调标准库(pycryptodome)或者直接用OpenSSL命令行来完成实验,把精力放在"为什么这么设计"和"不同模式之间差异"上。

以我自己的经验,环境选型可以这样决策:

  • 纯理解型实验(AES模式对比、RSA加解密流程、哈希雪崩测试):直接用Python的cryptographypycryptodome库,代码量小、出结果快、画图表方便。
  • 流程型实验(证书生成、TLS握手模拟、数字签名全流程):优先用OpenSSL命令行,能非常直观地看到证书链、密钥格式。
  • 算法实现型实验(老师明确要求不调库):用Python纯手写,注意运行效率和精度问题。
  • 综合项目:Python + 网络编程(socket),模拟双方通信。

我的最终选择是:前四个实验全部用Python(调库实现),综合项目用Python + OpenSSL混搭,这样分工最舒服。

提示:无论选哪种方案,实验报告里一定要写清楚"算法/工具版本号",比如Python 3.10.12、pycryptodome 3.19.1、OpenSSL 3.0.10。密码学实验对版本的敏感度比一般程序高很多,版本不同导致的填充方式、哈希算法支持差异,会让老代码跑出完全不同的结果。

1.3 实验报告的隐藏评分点

吉大的密码学实验,报告占分比例很重。我见过有的同学代码全对,但报告只写了三行字,最后分数被拉低。我总结出实验报告的隐藏评分点,供参考:

  • 实验目的不能抄课本,要写成"通过实现X,验证Y原理,掌握Z能力"的形式。
  • 必须有程序运行截图,截图要带上时间戳或者终端路径,证明是你自己跑的。
  • 必须对比分析。比如AES的ECB和CBC模式,光贴代码和输出不够,要分析为什么CBC模式密文相同明文块在不同位置得到不同结果。
  • 篇幅要求每个实验至少1000字以上,最好有表格、有原理图(自己画的示意图截图)。
  • 结尾要有"遇到的问题与解决方法"。

这些点贯穿所有实验,建议一开始就按这个标准去做。

2. 对称加密实验:AES的填充、IV与模式选择,这些坑都得自己走一遍

对称加密是所有密码学实验里最先接触的模块。我当时的题目是:编写一个程序,实现AES-CBC模式对文件的加解密,并对比ECB和CBC在相同密钥下加密两张"有重复色块"的BMP图片的视觉效果差异。

2.1 为什么这个实验要选BMP图片来对比模式

老师特意指定BMP图片,而不是随机文本,是有深意的。BMP是一种不压缩的图像格式,同一个颜色的像素块在文件中会大量重复。用ECB模式加密时,相同的明文块会得到相同的密文块,加密后的图片依然能看出原始图像的轮廓和色块分布——这就是著名的"ECB泄露信息"问题;而CBC模式下,每个明文块会先和上一个密文块异或,相同明文块在不同位置加密后完全不同,图片变成完全的噪声。

这个实验最直观地展示了"模式和安全性"的关系。很多人学AES时记住"ECB不安全、用CBC",但不明白为什么不安全。等你看到那两张对比图,就再也忘不掉了。

2.2 代码实现:注意填充和字节处理

我直接用pycryptodome来做,但花了很多时间在字节处理和填充模式上。贴一个精简版的CBC加解密核心代码:

from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import os key = os.urandom(16) # AES-128,16字节密钥 iv = os.urandom(16) # 16字节初始向量 def aes_cbc_encrypt_file(input_path, output_path): cipher = AES.new(key, AES.MODE_CBC, iv) with open(input_path, 'rb') as f: plaintext = f.read() ciphertext = cipher.encrypt(pad(plaintext, AES.block_size)) with open(output_path, 'wb') as f: f.write(iv) # 把IV写在密文文件头部 f.write(ciphertext) def aes_cbc_decrypt_file(input_path, output_path): with open(input_path, 'rb') as f: iv = f.read(16) # 先读出IV ciphertext = f.read() cipher = AES.new(key, AES.MODE_CBC, iv) plaintext = unpad(cipher.decrypt(ciphertext), AES.block_size) with open(output_path, 'wb') as f: f.write(plaintext)

这个代码有几个细节需要特别注意:

  • pad(plaintext, AES.block_size),AES的block_size是16字节,PKCS7填充会把缺失的字节数作为填充值。如果明文长度恰好是16的倍数,一般也会多填充一个完整块,因为不加填充的话解密端无法区分"填充的0x10"和"数据本身的0x10"。这一点在实验报告里值得专门写一段,属于"你以为会了,但实际运行时才意识到"的典型问题。
  • IV不需要保密,但必须随机且唯一。同一个密钥如果用了相同的IV加密两份不同的明文,会立刻泄露两份明文之间的关系。这个知识点后面做综合实验时会用到,务必理解透。
  • 把IV存在密文文件头部是常见做法,这样解密端不需要额外传递IV。注意如果文件很大,f.read()一次性读入会占用大量内存,实验的测试文件小没问题,但如果放到大文件场景,要用分块读写的方式:读取16字节、加密、写盘、循环。我在综合项目里就遇到了这个问题,后面再细说。

2.3 实验现象分析与报告写法

运行ECB和CBC加密BMP图片后,实验结果非常直观:

  • ECB加密后的BMP:轮廓可见,大块纯色区域变成有规律的"斑马纹",能隐约看到原图的颜色分布。
  • CBC加密后的BMP:完全雪花噪点,无任何规律。

我在实验报告里做了一个对比表:

对比维度ECB模式CBC模式
相同明文块加密结果完全相同不同(依赖前一个密文块)
加密后图像特征保留轮廓与色块规律完全随机噪声
安全性低,泄露明文模式信息高,且密文块之间互相依赖
适用场景不建议单独使用对称加密的常用模式

这个表最好结合实验截图一起放,说明"为什么ECB在图像场景下会泄露信息"——因为像素文件中大量相同的字节块被原样映射成了相同的密文块,人眼虽然看不懂密文,但能看出重复模式的分布。

我在做这个实验时踩过最大的坑是:直接用PIL库打开加密后的BMP显示,结果程序报错,因为加密后的字节不再是合法的BMP格式。解决办法是:不要用图片查看器打开加密文件,而是直接用十六进制编辑器(如HxD)或者Python读字节对比,或者把加密后的字节数据写入一个新的.bin文件,再用Python的matplotlib把字节可视化成灰度图。这个处理过程本身也可以写进报告,说明"你理解了加密是字节层面的事,而不是图像层面的事"。

3. RSA实验:用代码把数论变成密码,大整数运算才是门槛

RSA实验是应用密码学实验里最能体现"数学和工程结合"的一个。我当时的题目是:实现RSA密钥生成、加密、解密,并测试不同密钥长度对性能的影响,同时用RSA完成一次简单的数字签名(先用哈希对消息摘要,再用私钥加密)。

3.1 参数选择:素数生成、e的选择和填充方式

RSA的数学原理不复杂:选两个大素数p和q,计算n=p×q,取欧拉函数φ(n)=(p−1)(q−1),选择一个与φ(n)互质的e,再算出e的模反元素d,公钥是(n,e),私钥是(n,d)。但真到了代码层面,有几个参数直接决定程序能不能跑:

  • 素数生成:用Python的Crypto.Util.number.getPrime(1024)生成1024比特的大素数。千万别自己写“从2遍历到sqrt(n)判断素数”的代码,那是天文数字级别的耗时。生成后可以打印出来看一下,几百位的十进制数,这就是RSA安全的根基。
  • 公钥指数e:标准做法是取65537(即0x10001)。这个数既是素数,又只有两个1比特,在模幂运算中能显著加快加密速度(后面会讲为什么)。不要选e=3这种小指数,存在低指数攻击的隐患。
  • 填充方式:绝对不能直接调用pow(m, e, n)去加密原始消息。因为RSA是确定性加密(相同明文块产生相同密文块),而且小消息直接加密会出现数学可分解风险。标准做法是用RSA-OAEP填充(PKCS1_OAEP),它会在消息里混入随机数,让相同明文每次加密得到不同密文。

pycryptodome的RSA代码很简单:

from Crypto.PublicKey import RSA from Crypto.Cipher import PKCS1_OAEP key = RSA.generate(2048) private_key = key.export_key() public_key = key.publickey().export_key() # 加密 recipient_key = RSA.import_key(public_key) cipher = PKCS1_OAEP.new(recipient_key) ciphertext = cipher.encrypt(b"Hello, Applied Crypto!") # 解密 private_key_obj = RSA.import_key(private_key) cipher = PKCS1_OAEP.new(private_key_obj) plaintext = cipher.decrypt(ciphertext) print(plaintext.decode())

3.2 手写RSA核心:pow运算和性能测试

如果实验要求自己实现RSA核心逻辑(不调RSA库,但允许用大整数库),核心就是模幂运算:

def mod_pow(base, exp, mod): result = 1 base = base % mod while exp > 0: if exp & 1: result = (result * base) % mod base = (base * base) % mod exp >>= 1 return result # 加密:c = m^e mod n ciphertext = mod_pow(plaintext_int, e, n) # 解密:m = c^d mod n plaintext_int = mod_pow(ciphertext, d, n)

这个"快速幂取模"算法是RSA实验的核心。它把指数运算的时间从O(k)降到O(log k),没有它,2048位密钥加密一次可能要跑几分钟。我在报告里专门对比了"朴素指数运算"和"快速幂"的性能差异,这个对比很加分,能证明你不是只会调包。

性能测试的实验数据也是报告里的一大亮点:

密钥长度(bit)密钥生成时间(s)加密时间(ms)解密时间(ms)
10240.083.845.2
20480.898.9186.5
40966.7316.2790.8

从数据能看出两件事:

  • 加密比解密快得多,因为e=65537的二进制表示中只有两个1,模幂运算的循环次数少;而私钥指数d的比特位几乎全是1,计算量巨大。
  • 密钥长度从2048加到4096,安全强度提升了一倍,但时间开销涨了四倍以上。所以实际系统里,2048位密钥至今仍是主流,纯4096位更多用于证书根或高安全场景。

3.3 RSA签名的易错点:先用哈希再签名

实验里还有一步是"用RSA实现数字签名"。最典型的错误是直接对消息做RSA加密作为签名,这是错的。正确思路必须是:先对消息做哈希摘要(SHA-256),再用私钥对摘要"加密"(签名),验签时用公钥"解密"摘要,再和重新计算的消息摘要比对。

为什么不能直接签原文?因为RSA的数学结构允许"伪造签名"攻击——攻击者如果拿到一个合法签名,可以把它改造成另一个消息的合法签名(乘法同态性质),除非先做哈希破坏这种结构。所以标准签名算法如RSA-PSS,都是先哈希再加盐和填充,再模幂。

签名代码(pycryptodome风格):

from Crypto.Signature import pkcs1_15 from Crypto.Hash import SHA256 message = b"Important message" h = SHA256.new(message) signature = pkcs1_15.new(private_key).sign(h) # 验签 h = SHA256.new(message) try: pkcs1_15.new(public_key).verify(h, signature) print("签名验证通过") except (ValueError, TypeError): print("签名验证失败")

注意:验签时新建的哈希对象要从原始消息重新计算,而不是直接拿来之前的h——因为h对象可能在签名后被修改。这个细节我在实验时踩过坑,放报告里也是很好的"问题与解决"素材。

4. 哈希与数字签名:雪崩效应和"摘要"是怎么把密码学串起来的

哈希实验看似简单——调用库算个SHA-256、统计一下雪崩效应——但实际上它深刻影响着你对数字签名、消息认证码(MAC)和区块链等所有"摘要类"应用的理解。我当时的实验题目是:实现SHA-256对任意文件的哈希计算,并验证单比特改变对摘要的影响,再实现HMAC消息认证码。

4.1 雪崩效应测试:单比特翻转的扩散效果

哈希函数有一个核心性质叫"雪崩效应":输入哪怕只翻转1比特,输出的每一比特大概有一半的概率发生变化。我当时写了个测试脚本:

import hashlib import os def compute_sha256(data: bytes) -> bytes: return hashlib.sha256(data).digest() plain_a = os.urandom(64) plain_b = bytearray(plain_a) plain_b[0] ^= 0x01 # 翻转第一个字节的最低比特 digest_a = compute_sha256(bytes(plain_a)) digest_b = compute_sha256(bytes(plain_b)) diff_bits = sum( (a ^ b).bit_count() for a, b in zip(digest_a, digest_b) ) print(f"明文仅翻转1比特,摘要对撞的比特数:{diff_bits} / 256")

我跑出来的结果在130左右浮动,也就是接近一半。报告里可以多次运行,统计一个平均值,画张直方图。这个实验最好的地方在于真正"看到"了雪崩效应,而不是只在课本上念"输出严格依赖输入"。

另一个值得做的扩展测试是计算速度:对一个几MB的大文件做哈希,验证SHA-256在普通机器上能达到每秒几百MB以上的吞吐,说明"哈希本身是廉价的",所以"哈希+加密"才是数字签名成本可控的原因——RSA加密慢,但只加密一个32字节的摘要就无所谓了。

4.2 HMAC实验:为什么不能简单地把密钥拼到消息后面再哈希

实验里有一个HMAC的实现要求。最直觉的做法是:hash(key + message)。但这是错误的,存在长度扩展攻击的致命缺陷——攻击者不知道密钥的情况下,也能在已知消息后面追加数据,构造出合法的新MAC。HMAC的设计(内层外层的两次哈希)就是为了防御这类攻击。

import hmac import hashlib key = b"secret-key" message = b"important transaction" mac = hmac.new(key, message, hashlib.sha256).hexdigest() # 验证 expected = hmac.new(key, message, hashlib.sha256).hexdigest() print(hmac.compare_digest(mac, expected)) # 用常量时间比较,防时序攻击

在实验报告里,我对比了三种"消息认证"的区别:

  • 哈希(SHA-256):只保证完整性,不保证来源,任何人可以计算同一个摘要。
  • MAC(HMAC):共享密钥双方可以验证消息未被篡改且来自对端,但不能向第三方证明(因为第三方无法区分是谁用密钥生成的)。
  • 数字签名(RSA签名):公钥可验证,具有不可否认性,可以用于审计追责。

这个对比一定要放,因为它是密码学协议里选型设计的核心逻辑,也是答辩时老师最爱问的问题之一。

4.3 哈希在密码学协议里的"胶水"作用

做完哈希实验后,你会猛然发现之前学的AES和RSA都能串起来了:AES负责大流量加密,但它没有完整性保护(CBC模式下攻击者翻转密文块,解密后明文也跟着变);RSA负责密钥协商/签名,但慢;哈希则是"完整性胶水",把密钥、消息等所有信息"粘成"一个定长摘要,让RSA只对这个摘要签名就够了。

实际的安全协议如TLS 1.3 就是这么干的:握手时用ECDHE交换对称密钥,用哈希做密钥派生(HKDF),用哈希记录所有握手消息防止中间人篡改。所以千万不要小看这个"只是算个摘要"的实验,它是理解后面所有协议的基础。

5. 证书与PKI实验:用OpenSSL亲手签一张证书,瞬间看懂HTTPS

这是几个实验里最"工程化"的一个,也是我收获最大的一个。题目是:使用OpenSSL生成自签名的CA根证书,然后由这个CA给一个Web服务器签发证书。最后把服务器证书导入系统/浏览器,验证HTTPS连接。

5.1 OpenSSL从零建CA并签发证书的完整命令

网上关于OpenSSL签证书的教程很多,但很多都是直接复制一把命令、并不解释每步在干嘛。这里我按自己做实验的完整流程走一遍:

第一步,生成CA根私钥和自签名证书:

# 生成CA私钥,aes256加密存放 openssl genrsa -aes256 -out ca.key 4096 # 生成自签名根证书,有效期10年 openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt -subj "/C=CN/ST=Jilin/L=Changchun/O=DemoLab/CN=Demo Lab CA"

第二步,生成Web服务器的私钥和证书签名请求(CSR):

# 生成服务器私钥 openssl genrsa -out server.key 2048 # 生成CSR openssl req -new -key server.key -out server.csr -subj "/C=CN/ST=Jilin/L=Changchun/O=DemoLab/CN=localhost"

第三步,用CA给服务器证书签名。这里有个关键细节,必须要创建扩展文件(如server_ext.cnf),内容至少要包含:

subjectAltName=DNS:localhost,IP:127.0.0.1

如果不加subjectAltName,浏览器会报"证书名称不匹配"。很多教程跳过了这一步,导致最后浏览器死活不信任证书,坑非常多。有了扩展文件之后,用CA签发:

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial -out server.crt -days 365 -sha256 -extfile server_ext.cnf

第四步,验证证书链:

openssl verify -CAfile ca.crt server.crt

输出server.crt: OK就表示信任链没问题。

5.2 搭建一个本地HTTPS服务器验证全过程

证书签发出来之后,需要在本地起一个HTTPS服务来验证。最简单的办法是用Python自带模块:

python3 -m http.server 8443 --bind 127.0.0.1 --directory . --ssl-certfile server.crt --ssl-keyfile server.key

然后把ca.crt导入系统信任根(或者直接导入浏览器),再用浏览器访问https://localhost:8443,能看到地址栏的小锁标志,就说明整个证书链验证通过了。

如果不去浏览器导入CA证书,而是用curl测试,需要用--cacert参数指定CA:

curl --cacert ca.crt https://localhost:8443/

这个全流程做完后,我回头去理解TLS握手过程,一下子就通透了:客户端请求服务器证书 → 客户端用系统内置CA公钥验证证书签名 → 证书链可信 → 后续才进行密钥协商。没有实验的时候,这些流程只是课本上的几个圆圈箭头,做完实验之后它们变成了真实存在的数据流。

5.3 证书实验里最容易踩的坑

我在这个实验里碰到过三个典型坑,值得单独列出来:

  • 证书有效期问题:自签名CA证书有效期必须覆盖服务器证书的有效期,否则会报"证书已过期或不在有效期内"。可以把CA设成十年的,服务器证书设成一年。
  • SAN(Subject Alternative Name)缺失:浏览器很早就只认SAN而不认证书CN(通用名)了。如果证书的CN是localhost但没有SAN,Chrome直接拒绝连接。这是实验报告"问题与解决方案"部分最好的素材。
  • 私有CA信任导入位置:在Windows系统里要导入"受信任的根证书颁发机构"而不是"个人"或"中间证书颁发机构";macOS里要导入"系统"钥匙串并设为始终信任。很多同学是把证书导入到了错误的位置,导致浏览器依然报不受信任。

注意:这里说的都是实验环境里的自签名证书操作,本地方便面验证学习用的,千万别拿到生产环境里用,也别用这种证书去拦截任何真实流量。实验目的就是为了理解密码学在PKI体系中的应用,这个边界要搞清楚。

6. 综合实验:AES+RSA+HMAC拼出一个"简易安全通信"系统,才算真正入门

最后一个综合项目,我们组的题目是:设计并实现一个简易的安全通信系统,服务端和客户端之间通过TCP通信,要求实现身份认证、密钥协商、数据加密和完整性校验四个功能。

6.1 整体设计:四个安全目标怎么映射到技术选型

安全通信有四个核心目标:机密性、完整性、身份认证和不可否认性。落实到系统设计上,我们的选型是:

  • 密钥协商:客户端生成AES会话密钥,用服务器的RSA公钥加密后发给服务端。这就是教科书上的"混合加密"思路,相当于手工实现了一个简化的TLS握手。
  • 数据加密:AES-CBC或AES-GCM加密业务数据。注意我们最后升级成了GCM,因为GCM同时提供加密和完整性校验(AEAD)。
  • 完整性:如果用AES-GCM,认证标签天然保护完整性;如果坚持用CBC,需要额外叠加HMAC("先加密后MAC")。
  • 身份认证:客户端验证服务端证书/公钥。简单做法是客户端内置服务器公钥,不做完整PKI验证,但代码里要写明"生产环境需要验证证书链"。

6.2 分块读写:大文件传输的隐藏要求

综合项目验收时,老师会当场给你一个几十MB的测试文件,让你跑传输。这就暴露出一个我们前期忽略的问题——直接用read()一次性读入整个文件然后加密传输,内存直接爆炸。解法是按块加密:

CHUNK_SIZE = 64 * 1024 # 64KB def encrypt_file_encrypt(conn, aes_key, file_path): iv = os.urandom(16) conn.sendall(iv) # 先发送IV cipher = AES.new(aes_key, AES.MODE_CBC, iv) with open(file_path, 'rb') as f: while True: chunk = f.read(CHUNK_SIZE) if len(chunk) == 0: break if len(chunk) % AES.block_size != 0: chunk = pad(chunk, AES.block_size) conn.sendall(cipher.encrypt(chunk)) conn.sendall(b'__EOF__')

这个分块逻辑里有一个很容易写错的细节:填充必须在每个数据块上单独做,而不能在整个文件加密完成后一次性填充——因为整个文件可能被分成了多块,解密端是按同样块大小解密的,如果某一块长度不是16的倍数,解密会直接报错。如果你的文件大小恰好是块大小的整数倍,最后一个数据块要额外加一个完整的填充块(PKCS7规则),否则解密端无法判断文件结束。

我们组的代码在自测时用几KB的小文件一切正常,验收时一上大文件就崩,排查了一晚上才发现是分块填充的问题。这类细节写进实验报告的"问题与解决方法",会很有说服力。

6.3 加MAC还是用GCM:安全性的实际考量

实验要求中明确写了"必须具备完整性校验",我们一开始用的是AES-CBC + HMAC-SHA256,逻辑是"先加密,再对密文做HMAC"。但后来看代码发现,手工把"加密和MAC"拼在一起很容易出错——比如如果错误地把HMAC加在明文上(secbc+machine这种被无数论文批驳的组合),安全性会出大问题。

后来我们换成了AES-GCM模式,代码更简洁,安全性也更高:

from Crypto.Cipher import AES import os def encrypt_gcm(key, plaintext, associated_data=b""): cipher = AES.new(key, AES.MODE_GCM) ciphertext, tag = cipher.encrypt_and_digest(plaintext) return cipher.nonce, ciphertext, tag def decrypt_gcm(key, nonce, ciphertext, tag, associated_data=b""): cipher = AES.new(key, AES.MODE_GCM, nonce=nonce) plaintext = cipher.decrypt_and_verify(ciphertext, tag) return plaintext

AES-GCM内部使用的是CTR模式加密,所以不需要填充;认证密文和附加数据(AAD),能同时防窃听和防篡改。换用GCM后,代码量少了三分之一,安全性反而更强。这个"从CBC+HMAC升级到GCM"的过程,我在报告里专门写了一小节,强调"选AEAD模式会比手工拼凑密码原语更安全"——这是现代密码工程的重要理念。

7. 实验报告与验收答辩:给"仅供参考"的学弟学妹的几条实在建议

应用密码学实验课的全部工作,最终都要落到"实验报告+现场验收"上。最后这部分,我从过来人的角度总结几条实在建议。

7.1 报告组织:用"原理复现→过程记录→结果分析→问题复盘"的节奏写

一份好的实验报告,不是贴代码和截图就完了。密码学实验报告讲究"看得懂原理、看得清过程、看得见思考"。

我的每份报告大概都按这样的结构写:

  • 实验目标:一两句话,写"通过实现X,理解Y,掌握Z能力",不要写"熟悉XX算法"这种空话。
  • 原理简述:用自己的话把算法流程写清楚,可以配一张手画的流程图(截图),但不要长篇大论抄课本。
  • 实现方案:写清楚用到的工具/库、代码结构、关键代码片段。
  • 实验结果:截图 + 表格 + 简要说明。
  • 对比分析:比如ECB vs CBC、不同密钥长度性能对比、雪崩效应统计、签名/未签名场景对比。这是报告里最加分的部分。
  • 问题与解决:如实记录踩过的坑。老师在验收时最喜欢问"你遇到过什么问题、怎么解决的"——能清楚讲出踩坑经历,说明你是真的动手做了。

全篇用Markdown写,导出为PDF,代码用等宽字体,截图注意清晰度。篇幅控制在每个实验1500~2000字左右。

7.2 验收答辩:老师最爱问的四个问题

综合实验验收时,老师不会只让演示效果,通常会追问几个概念问题。我汇总了我们班被问到过的问题:

  • "为什么RSA只加密AES密钥,不直接加密数据?"——答:RSA慢,AES快;RSA加长报文需要分块而且密文膨胀严重;混合加密即可以隐藏会话密钥,又可以高效加密大数据。
  • "AES的ECB和CBC有什么区别?你项目里为什么用CBC/GCM?"——答:CBC的每个明文块会和前一个密文块异或,相同明文块不同位置得到不同密文,防重放、防模式泄露。
  • "你项目里HMAC起什么作用?如果攻击者篡改了密文会发生什么?"——答:HMAC校验失败,解密端直接丢弃数据,不会输出被篡改的明文,防止主动攻击。
  • "你的会话密钥是怎么协商出来的?如果中间人拦截了RSA公钥怎么办?"——答:RSA公钥需要提前分发并验证;真实场景靠CA证书链保证公钥可信。如果只是把公钥明文发过去,中间人可以直接换掉公钥,这就是经典的中间人攻击。

这几个问题都能答上来的话,验收基本就稳了。

7.3 时间安排与持久化:不要攒到最后一周

最后一个建议,也是我踩过最大的坑:千万不要把四个实验攒到结课前一周集中做。密码学实验的每个实验之间是有依赖的——AES实验帮你理解对称加密,RSA实验帮你理解密钥协商,哈希实验帮你理解签名和完整性,证书实验把前面所有知识串起来。扎扎实实每周推进一个,不仅代码写起来轻松,而且你会明显感觉到知识在脑子里连成了一张网。

我实验室里有个同学,前三周纯摸鱼,最后一周连肝四份报告加一个综合项目,结果答辩时被老师问到"AES-GCM的nonce如果重复会怎样",直接愣住。其实这个问题不难,nonce重复会导致密钥流重放,彻底破坏机密性,但他在赶工状态下根本来不及消化这些细节。

另一个建议是:用git管理实验代码。实验过程中代码会反复改,有一个版本管理工具,出问题能随时回滚。我经常是"下午把AES分块改好了,晚上想试GCM,结果把CBC代码改坏了",这时候git的git checkout能救命。很多同学没有这个习惯,最后代码文件里全是final_final_v2.py这种名字,看起来就很痛苦。

写在最后:做完所有实验后,我对密码学最大的感受变化

刚上这门课时,我看密码学教材,满脑子都是"数学公式好难、算法记不住"。真正动手把AES跑起来,把RSA密钥生成出来,亲手签出一张证书并让浏览器信任它之后,我才意识到:密码学并不是一堆死板的数学公式,而是一套有明确攻击模型和设计目标的工程体系。每个原语(对称加密、公钥加密、哈希、签名)都像一块积木,安全协议则是拼出完整建筑的图纸,而实验课是你亲手把这些积木搭起来的过程。

现在回头看,应用密码学实验里那几个看起来"只是调了个库"的小项目,其实帮我建立了现代安全系统设计的直觉:什么东西该用对称加密,什么东西该用非对称加密,完整性为什么不能靠加密本身解决,信任链为什么需要一个根CA。这些直觉在后续搞Web安全、写前后端接口、设计登录鉴权方案时,都能派上用场。

我分享的这些内容,都是基于个人实验经历的参考,代码也难免有疏漏或者依赖于特定版本环境,希望大家结合自己的实验要求和环境去做调整。保持好奇,多动手跑一跑,密码学这门课真的比想象中有意思得多。

本文还有配套的精品资源,点击获取

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/1 20:23:53

CAD新手必练:多段线PL命令全面解析与实战应用

这次我们来看 CAD 新手练习中绕不开的一个基础命令:多段线 PL(Polyline 的缩写)。很多新手画图时习惯用直线 L,但画到箭头、墙体、道路线、轮廓线时,就会发现直线画出来的对象拼接麻烦、线宽不好控制、整体不好选中。多…

作者头像 李华
网站建设 2026/9/1 20:23:11

基于STM32F103的TM1628数码管驱动工程详解与排坑指南

简介:基于STM32F103系列单片机的TM1628数码管驱动完整工程源码,面向嵌入式初学者与开发者,解决低成本环境下多位数码管显示与按键扫描的快速实现需求。源码核心为TM1628.c/TM1628.h,采用标准GPIO模拟时序完成通信,不依…

作者头像 李华
网站建设 2026/9/1 20:20:53

基于FPGA的电磁超声脉冲压缩无损检测系统设计

简介:本资源是一套面向FPGA开发初学者与嵌入式课程设计者的电磁超声测厚系统完整实现方案,基于ZYNQ-7020平台,聚焦解决电磁超声换能效率低、回波信号微弱(仅数十微伏)导致测厚精度受限的核心问题。项目融合数字信号处理…

作者头像 李华
网站建设 2026/9/1 20:20:38

美团算法笔试避坑指南:ACM输入输出与四大高频考点全解析

一场美团算法笔试,我写满了编辑器却被判了零分 要说校招笔试哪家最让我记忆深刻,美团绝对排得上前三。倒不是题目有多变态,而是第一次参加大厂在线笔试时,我在自带的本地编辑器里把代码跑得漂漂亮亮,结果提交到牛客网…

作者头像 李华
网站建设 2026/9/1 20:19:41

Yolo 小白入门 45:冻结骨干做迁移学习——小数据也能稳稳起步

Yolo 小白入门 45:冻结骨干做迁移学习——小数据也能稳稳起步 [!NOTE] 你现在位于《Yolo 全速入门到精通【持续更新中】》的 第五章 训练优化调参。这一篇不追求堆满参数,而是带你验证“冻结骨干迁移”的最小可验证闭环,并能说清它在数据、模型与业务之间的位置。我们用 ul…

作者头像 李华
网站建设 2026/9/1 20:14:50

Python实战:迷宫生成与A*寻路算法可视化详解

各位关注算法实战与 Python 小项目的朋友,大家好。 之前在做路径规划相关的技术调研时,经常需要在不同地图上验证寻路效果,但网上现成的迷宫生成与 A* 寻路教程大多只讲算法片段,很难直接跑起来。今天整理了一份完整的项目实战笔…

作者头像 李华