Python加密农产品溯源系统:AES与哈希链防篡改实现
简介这是一套面向计算机专业毕业生与Python学习者的农产品溯源仿真系统毕设源码围绕Python加密技术展开将AES、RSA等加密思路融入农产品从生产到销售的全流程信息追踪适合需要完整项目参考、准备答辩或想实践加密与溯源结合场景的学生。压缩包共2000个文件以1706个py源码为主体配合124个html与87个js构建前端交互另有47个txt、9个md说明文档及少量css、json、xml配置整体约77.3MB涵盖加密模块、数据存储、溯源逻辑与用户接口等部分。目前已有211人学习下载。资源包含需求分析、系统设计、数据库脚本、测试用例与运行指南等材料读者可据此理解加密算法落地方式、溯源系统模块划分与数据库设计思路并借助测试用例和报告梳理开发流程与排错方法适合作为毕设参考或二次开发起点。1. 拆开这份农产品溯源仿真系统加密模块到底藏在哪一层拿到「毕业设计基于python加密技术的农产品溯源仿真系统.zip」这个包第一反应不是急着跑起来而是先搞清楚它到底把加密用在了哪个环节。农产品溯源系统的核心链路是「生产记录 → 加工流转 → 仓储物流 → 终端查询」每个环节都要写入一条不可抵赖的记录。如果只是普通数据库增删改查那这个毕设跟课设没区别真正让它够得上「加密技术」四个字的是记录在落库前经过了哈希或对称加密处理查询时再校验一致性。这套源码适合两类人一是正在做同类毕设、需要一份能跑通全流程的参考实现二是想搞清楚「加密怎么嵌进业务系统」而不是只背 AES 公式的开发者。包里能看到 css 资源文件base.css、responsive.css、select2.css、widgets.css、autocomplete.css、forms.css、changelists.css、rtl.css、nav_sidebar.css说明前端用的是带后台管理风格的模板大概率是 Django admin 或类似框架的静态资源体系。这意味着它不是纯脚本 demo而是一个有管理后台、有表单、有列表页的完整 Web 仿真系统。接下来我会按「先跑通、再看加密、最后排坑」的顺序把这份资源拆到你能照着复现的程度。2. 环境搭建与项目结构从解压到第一次成功登录2.1 依赖清单与虚拟环境隔离这类毕设包最常见的翻车点不是代码逻辑而是依赖版本对不上。Python 3.83.10 是这类项目的舒适区3.11 以上某些老版本加密库会编译失败。我一般会先建虚拟环境再装依赖避免污染全局。# 创建并激活虚拟环境Python 版本建议 3.9 或 3.10 python -m venv venv # Windows venv\Scripts\activate # macOS / Linux source venv/bin/activate # 升级 pip 后安装核心依赖 pip install --upgrade pip pip install django cryptography pycryptodome pillow逻辑说明cryptography和pycryptodome是这类项目最常用的两个加密库前者偏现代 API后者兼容老式 AES/RSA 写法。两个都装上避免源码里 import 哪个都报 ModuleNotFoundError。pillow用于处理农产品图片上传溯源系统通常要给每条记录配图。参数上Django 版本要看你解压后requirements.txt里写的如果没有这个文件就按pip install django3.2这种 LTS 版本试3.2 和 4.2 对老代码兼容性最好。2.2 目录结构与关键文件定位解压后先别急着runserver用tree或文件管理器看清楚层级。典型结构是这样的project_root/ ├── manage.py # Django 入口 ├── requirements.txt # 依赖清单可能没有 ├── app_trace/ # 溯源核心应用 │ ├── models.py # 数据模型加密字段在这里定义 │ ├── views.py # 业务逻辑加密调用点 │ ├── utils/crypto.py # 加密工具模块重点看这个 │ └── migrations/ # 数据库迁移脚本 ├── static/ # 你看到的那些 css 文件 │ ├── base.css │ ├── responsive.css │ ├── select2.css │ └── ... ├── templates/ # 前端模板 └── db.sqlite3 # 可能自带也可能要自己生成重点找crypto.py或encrypt.py这类文件它决定了整个系统的加密方案。如果找不到独立加密模块就去models.py里搜hashlib、AES、encrypt关键字。常见做法是把加密逻辑写在 model 的save()方法里写入时自动加密读取时在 view 层解密。2.3 数据库初始化与首次运行确认settings.py里的DATABASES配置。如果是 SQLite直接迁移即可如果是 MySQL要先建库再改连接串。# 生成迁移文件并建表 python manage.py makemigrations python manage.py migrate # 创建后台管理员账号 python manage.py createsuperuser # 启动开发服务器 python manage.py runserver 8000逻辑说明makemigrations会根据models.py生成建表语句如果加密字段用了自定义 Field这一步可能报错需要先确认自定义 Field 的deconstruct()方法是否完整。createsuperuser是登录后台的前提很多毕设包把管理员账号写死在文档里但密码是哈希存储的你没法直接看只能自己重建。启动后访问http://127.0.0.1:8000/admin能看到登录页且 css 正常加载说明静态文件配置没问题。如果页面裸奔没样式检查settings.py里STATIC_URL和STATICFILES_DIRS是否指向了 static 目录。提示如果migrate时报「No module named xxx」先回到 2.1 把缺失的包装上不要硬改代码去绕过 import。3. 加密模块拆解哈希、AES 与溯源记录的绑定方式3.1 溯源链的防篡改原理农产品溯源的核心诉求是「记录一旦写入就不能悄悄改」。实现方式通常有两种一是对每条记录算哈希把哈希值存到独立字段查询时重新算一遍对比二是用链式结构每条记录的哈希包含前一条的哈希形成类似区块链的链条。毕设级别常见的是第一种因为实现简单、演示效果好。加密在这里的作用不是「保密」而是「验真」——数据本身可能不敏感但必须证明它没被改过。# app_trace/utils/crypto.py 常见写法 import hashlib import json def generate_record_hash(record_data: dict, prev_hash: str ) - str: 对溯源记录生成 SHA-256 哈希 record_data: 包含产品名、批次、时间、操作人等字段的字典 prev_hash: 上一条记录的哈希用于链式绑定 # 按 key 排序保证同样数据每次哈希结果一致 payload json.dumps(record_data, sort_keysTrue, ensure_asciiFalse) raw payload prev_hash return hashlib.sha256(raw.encode(utf-8)).hexdigest()逻辑说明sort_keysTrue是关键参数字典顺序不固定会导致同样的数据算出不同哈希这是最常见的「玄学 bug」。ensure_asciiFalse保证中文不被转义否则哈希值会跟预期不一致。prev_hash传入上一条记录的哈希就把单条记录串成了链改任何一条都会导致后续所有哈希对不上。参数上SHA-256 输出 64 位十六进制字符串存数据库用CharField(max_length64)刚好。3.2 AES 对称加密在敏感字段上的用法有些毕设会把农产品价格、供应商联系方式等字段做 AES 加密存储查询时解密展示。这属于「保密」需求跟上面的「验真」是两回事。常见做法是用pycryptodome的 AES-CBC 模式密钥写在配置文件里。from Crypto.Cipher import AES from Crypto.Util.Padding import pad, unpad import base64 # 密钥必须是 16/24/32 字节从 settings 读取 SECRET_KEY b0123456789abcdef # 实际项目应从环境变量读取 def aes_encrypt(plain_text: str) - str: AES-CBC 加密返回 base64 字符串 cipher AES.new(SECRET_KEY, AES.MODE_CBC) ct_bytes cipher.encrypt(pad(plain_text.encode(utf-8), AES.block_size)) # 把 iv 拼在密文前面解密时取出来用 return base64.b64encode(cipher.iv ct_bytes).decode(utf-8) def aes_decrypt(cipher_text: str) - str: AES-CBC 解密 raw base64.b64decode(cipher_text) iv raw[:16] # 前 16 字节是 iv ct raw[16:] # 后面是密文 cipher AES.new(SECRET_KEY, AES.MODE_CBC, iv) return unpad(cipher.decrypt(ct), AES.block_size).decode(utf-8)逻辑说明CBC 模式必须要有 iv初始化向量且每次加密的 iv 应该随机生成否则相同明文会产生相同密文失去语义安全性。这里把 iv 拼在密文前一起 base64解密时按固定长度切分是毕设里最省事的做法。pad和unpad处理块对齐AES 块大小固定 16 字节明文长度不是 16 的倍数时必须填充。参数上SECRET_KEY长度必须是 16、24 或 32 字节分别对应 AES-128、AES-192、AES-256。密钥硬编码在代码里是毕设通病生产环境绝对不能这么干但作为学习参考可以接受。3.3 加密字段与数据库模型的对接加密逻辑最终要落到 model 上。常见写法是重写save()方法在入库前加密或者用 Django 的property做透明加解密。from django.db import models from .utils.crypto import aes_encrypt, aes_decrypt, generate_record_hash class TraceRecord(models.Model): product_name models.CharField(max_length100) batch_no models.CharField(max_length50) # 敏感字段存密文长度要留够 supplier_contact models.TextField() record_hash models.CharField(max_length64, blankTrue) prev_hash models.CharField(max_length64, blankTrue) created_at models.DateTimeField(auto_now_addTrue) def save(self, *args, **kwargs): # 入库前加密敏感字段 if self.supplier_contact and not self.supplier_contact.startswith(gAAAA): self.supplier_contact aes_encrypt(self.supplier_contact) # 生成链式哈希 data { product_name: self.product_name, batch_no: self.batch_no, created_at: str(self.created_at), } self.record_hash generate_record_hash(data, self.prev_hash) super().save(*args, **kwargs) property def contact_display(self): 模板里直接调这个属性拿明文 return aes_decrypt(self.supplier_contact)逻辑说明startswith(gAAAA)是 base64 编码的 AES 密文常见前缀特征用来防止重复加密——如果 save 被调用两次第二次不会再加密一遍。这个判断不严谨但毕设够用。record_hash和prev_hash字段留空由 save 自动填充。contact_display用 property 包装解密模板里{{ record.contact_display }}就能显示明文不用在 view 里手动解密。参数上TextField比CharField更适合存密文因为 base64 后长度会膨胀约 33%CharField容易超长截断。注意如果迁移时提示record_hash字段没有默认值给blankTrue加nullTrue或者先删掉迁移文件重新生成。4. 避坑与排查跑不起来、解密乱码、哈希对不上怎么办4.1 现象runserver 启动报「No module named Crypto」原因装的是crypto包而不是pycryptodome这两个包名冲突crypto是个废弃的空壳包import 会失败。解决先pip uninstall crypto再pip install pycryptodome。如果还不行检查虚拟环境是否激活有时候 pip 装到了全局而 runserver 用的是 venv。4.2 现象解密后中文变成乱码或报 UnicodeDecodeError原因加密时用了encode(utf-8)解密时没有对应decode(utf-8)或者unpad之前就 decode 了。解决严格按「解密 → unpad → decode」的顺序。另外确认json.dumps时加了ensure_asciiFalse否则中文被转成\uXXXX形式哈希和原文对不上。4.3 现象同一条记录两次查询哈希校验失败原因created_at用了auto_now_add第一次 save 时才有值但哈希计算时如果created_at还是 None第二次读出来是具体时间算出来自然不一样。解决把哈希计算放到created_at赋值之后或者干脆不把时间字段纳入哈希输入。更稳妥的做法是用一个独立的uuid字段参与哈希避免依赖自动时间戳。4.4 现象后台列表页 css 加载 404页面样式全丢原因settings.py里DEBUGTrue时 Django 才会自动服务 static 文件如果DEBUGFalse或者STATICFILES_DIRS没配那些 base.css、select2.css 全都请求不到。解决开发阶段保持DEBUGTrue并确认STATICFILES_DIRS [os.path.join(BASE_DIR, static)]。如果部署到生产需要collectstatic并配 Nginx但毕设演示用开发服务器就够了。4.5 现象迁移时报「字段太长」或「Data too long for column」原因AES 加密后的 base64 字符串比原文字节数长约 33%再加上 iv 和 padding原来 50 字符的字段加密后可能超过 100 字符。解决把所有需要加密的字段长度至少放大到原来的 2 倍或者直接用TextField。已经建表的话改models.py后重新makemigrationsmigrateSQLite 对字段长度不敏感MySQL 需要手动改表结构。5. 进阶验证用脚本批量校验溯源链完整性跑通系统之后真正能证明这套加密设计有没有用的是「篡改检测」。我一般会写一个独立脚本模拟攻击者直接改数据库某条记录然后跑校验逻辑看能不能发现。# verify_chain.py 放在项目根目录用 django shell 或独立脚本跑 import os import django os.environ.setdefault(DJANGO_SETTINGS_MODULE, project_root.settings) django.setup() from app_trace.models import TraceRecord from app_trace.utils.crypto import generate_record_hash def verify_chain(): records TraceRecord.objects.order_by(id) prev_hash tampered [] for r in records: data { product_name: r.product_name, batch_no: r.batch_no, created_at: str(r.created_at), } expected generate_record_hash(data, prev_hash) if expected ! r.record_hash: tampered.append(r.id) prev_hash r.record_hash if tampered: print(f检测到被篡改的记录 ID: {tampered}) else: print(溯源链完整所有记录校验通过) if __name__ __main__: verify_chain()逻辑说明脚本按 id 顺序遍历所有记录用同样的哈希函数重新计算跟库里存的record_hash对比。如果某条被直接改过比如用 SQLite 工具改了 product_name算出来的哈希就对不上tampered列表会记录下 id。prev_hash逐条传递保证链式结构也被验证。参数上order_by(id)必须加否则数据库返回顺序不固定链式校验会误报。跑这个脚本之前可以手动改一条记录做测试# 进入 django shell python manage.py shellfrom app_trace.models import TraceRecord # 故意改掉第一条记录的产品名模拟篡改 r TraceRecord.objects.first() r.product_name 被篡改的产品 r.save() # 注意save 会重新算哈希所以这样测不出来上面这种改法会被 save 自动修正哈希测不出问题。正确的模拟方式是绕过 save用queryset.update()TraceRecord.objects.filter(id1).update(product_name被篡改的产品)update()不会触发 model 的save()所以record_hash保持原值再跑verify_chain()就能看到篡改记录被揪出来。这个验证方法比看代码逻辑更有说服力答辩时演示一遍比嘴上说「我用了 SHA-256」管用得多。从那以后我每次拿到这类带加密的毕设包都会先跑一遍篡改检测脚本确认加密逻辑是真的在业务链路上生效而不是只写在工具文件里没人调用。希望帮到你。本文还有配套的精品资源点击获取