简介:本资源为基于深度学习的恶意加密流量检测系统毕业设计完整源码包,面向计算机、网络安全相关专业的学生及需要完成课程设计或期末大作业的开发者。项目围绕加密流量特征提取与恶意流量识别展开,涵盖DoH与CTU-13等数据集的Boruta特征筛选、相关性分析及模型训练结果,可帮助读者理解从数据预处理到模型评估的完整流程。压缩包共217个文件,约25.61MB,包含4个Python核心脚本、6个CSV特征与结果文件、6个npy数据文件、2个pcap流量样本,以及14个HTML可视化页面和若干日志、图片与样式文件,代码注释清晰,新手也能快速部署运行。目前已有251人学习下载,适合作为毕业设计、课程设计的高分参考方案,便于对照复现实验并撰写论文。
1. 恶意加密流量检测:为什么毕业设计选这个方向不容易翻车
很多同学做毕业设计时,第一反应是找个"看起来能跑"的题目,结果开题后被导师追问"你的创新点在哪""数据集从哪来""模型为什么选这个",直接卡壳。恶意加密流量检测这个方向,恰好卡在一个舒服的位置:它有真实的安全需求(企业出口流量里超过八成是加密的,传统基于端口和签名的检测手段基本失效),又有足够成熟的公开数据集和算法基础,不需要你自己去抓几十万条流量。用 Python 加深度学习,一台带显卡的笔记本就能跑通全流程。
这个系统的核心任务,是把网络流量中"加密且恶意"的那部分识别出来。注意是"加密且恶意",不是单纯检测恶意流量,也不是单纯做加密流量分类。你要处理的是 TLS/SSL 加密后的流量特征,比如包长序列、到达时间间隔、上下行字节比,而不是去解密内容。适合做这个题的人:会 Python 基础语法、装过 numpy 和 pytorch、能看懂混淆矩阵。如果你连 python 安装都要现查教程,建议先花两天把环境跑通再回来。
2. 从流量到张量:数据管线怎么搭才不返工
2.1 为什么选 CIC-IDS2017 和 USTC-TFC2016 这两个数据集
做恶意加密流量检测,数据集决定了你后面所有工作的上限。我一般会推荐两个:CIC-IDS2017 覆盖了多种攻击类型(DDoS、暴力破解、Web 攻击等),流量以 PCAP 格式提供,适合做特征工程;USTC-TFC2016 是专门针对加密流量的数据集,已经切分好会话,标注了恶意家族(如 Cridex、Geodo、Htbot 等),更适合做端到端的深度学习。
选 USTC-TFC2016 的理由很直接:它把每条流截断成固定长度的字节序列,你不需要自己写会话重组逻辑。很多同学在这一步翻车——用 CICFlowMeter 提取特征后发现加密流量的统计特征区分度不够,模型准确率卡在 70% 上不去。USTC 的数据格式更干净,适合毕业设计的体量。
注意:下载数据集时确认文件完整性,USTC-TFC2016 的 PCAP 文件解压后约 3GB,别下到一半断了。
2.2 用 Python 把 PCAP 转成模型能吃的张量
下面这段代码做三件事:读取 PCAP、按五元组切分会话、把每条会话的前 784 字节转成 28x28 的灰度图。为什么是 784 字节?因为 28x28 是 LeNet 的标准输入,方便你复用经典 CNN 结构,也方便可视化排查。
import numpy as np from scapy.all import rdpcap, IP, TCP, UDP from sklearn.model_selection import train_test_split def pcap_to_sessions(pcap_path, max_len=784): """把 PCAP 按五元组切分成会话,每条会话取前 max_len 字节""" packets = rdpcap(pcap_path) sessions = {} for pkt in packets: if IP not in pkt: continue # 五元组:源IP、目的IP、源端口、目的端口、协议 if TCP in pkt: key = (pkt[IP].src, pkt[IP].dst, pkt[TCP].sport, pkt[TCP].dport, 'TCP') elif UDP in pkt: key = (pkt[IP].src, pkt[IP].dst, pkt[UDP].sport, pkt[UDP].dport, 'UDP') else: continue raw = bytes(pkt) if key not in sessions: sessions[key] = bytearray() sessions[key].extend(raw[:max_len - len(sessions[key])]) if len(sessions[key]) >= max_len: continue # 补齐或截断到固定长度 result = [] for key, data in sessions.items(): arr = np.frombuffer(bytes(data[:max_len]), dtype=np.uint8) if len(arr) < max_len: arr = np.pad(arr, (0, max_len - len(arr)), 'constant') result.append(arr) return np.array(result) # 假设你已经把恶意和良性 PCAP 分开放了 malicious = pcap_to_sessions('malicious.pcap') benign = pcap_to_sessions('benign.pcap') X = np.vstack([malicious, benign]) y = np.hstack([np.ones(len(malicious)), np.zeros(len(benign))]) # 归一化到 0-1,reshape 成 CNN 输入格式 X = X.astype(np.float32) / 255.0 X = X.reshape(-1, 28, 28, 1) X_train, X_test, y_train, y_test = train_test_split(X, y, test_size=0.2, random_state=42) print(f"训练集: {X_train.shape}, 测试集: {X_test.shape}")逻辑说明:rdpcap读包,五元组做会话键,bytearray累积字节。参数max_len=784是经验值,你可以改成 1024 或 1500,但要注意显存。random_state=42保证每次切分一致,方便复现。跑完这段你应该看到类似训练集: (8000, 28, 28, 1)的输出,如果数量差太多,检查 PCAP 里是不是混了非 IP 包。
2.3 特征工程:什么时候该用统计特征,什么时候直接上原始字节
这里有个选型分叉。如果你用 CIC-IDS2017,建议走统计特征路线:用 CICFlowMeter 提取 80 多维特征(包长均值、方差、流持续时间、IAT 等),然后喂给 XGBoost 或 MLP。优点是可解释性强,答辩时能画出特征重要性图。缺点是加密流量下部分特征区分度下降。
如果你用 USTC-TFC2016,直接上原始字节序列做 CNN 更省事。我一般会两条路都跑一遍,对比 F1 值。统计特征路线在 CIC-IDS2017 上通常能到 95% 以上,原始字节路线在 USTC 上能到 98% 左右。毕业设计里选一条主路线,另一条作为对比实验写进论文,工作量刚好。
3. 模型选型与训练:CNN、LSTM 还是 Transformer
3.1 一维 CNN 和二维 CNN 在流量分类上的差异
流量数据本质是一维字节序列,但很多论文把它 reshape 成二维图再用 CNN。这两种做法我都试过。一维 CNN 直接对序列做卷积,感受野沿字节顺序滑动,更符合流量的时序特性。二维 CNN 把 784 字节排成 28x28,卷积核在"图像"上滑动,实际上破坏了字节的原始顺序,但意外地能捕捉到局部字节模式的共现关系。
我的建议:毕业设计用一维 CNN 做主模型,二维 CNN 作为对比。一维 CNN 的代码更简洁,训练更快,答辩时解释起来也顺。下面是一个可以直接跑的一维 CNN:
import torch import torch.nn as nn class TrafficCNN1D(nn.Module): def __init__(self, input_len=784, num_classes=2): super().__init__() self.conv1 = nn.Sequential( nn.Conv1d(1, 32, kernel_size=5, padding=2), nn.BatchNorm1d(32), nn.ReLU(), nn.MaxPool1d(2) ) self.conv2 = nn.Sequential( nn.Conv1d(32, 64, kernel_size=5, padding=2), nn.BatchNorm1d(64), nn.ReLU(), nn.MaxPool1d(2) ) self.conv3 = nn.Sequential( nn.Conv1d(64, 128, kernel_size=3, padding=1), nn.BatchNorm1d(128), nn.ReLU(), nn.AdaptiveAvgPool1d(1) ) self.fc = nn.Sequential( nn.Linear(128, 64), nn.ReLU(), nn.Dropout(0.5), nn.Linear(64, num_classes) ) def forward(self, x): # x: (batch, 1, 784) x = self.conv1(x) x = self.conv2(x) x = self.conv3(x) x = x.squeeze(-1) return self.fc(x) model = TrafficCNN1D() print(sum(p.numel() for p in model.parameters()), "个参数")参数说明:kernel_size=5是流量分类里常用的感受野,覆盖 5 个连续字节。BatchNorm1d加速收敛,Dropout(0.5)防过拟合。AdaptiveAvgPool1d(1)把变长输出压成固定长度,这样你换input_len不用改全连接层。整个模型约 10 万参数,笔记本 CPU 也能训。
3.2 训练循环里必须监控的三个指标
很多同学训练时只看 loss,结果模型在测试集上翻车。血泪经验:必须同时看准确率、F1 值和混淆矩阵。恶意流量检测是不平衡分类,准确率会骗人——如果恶意样本只占 5%,模型全预测良性也有 95% 准确率。
from sklearn.metrics import f1_score, confusion_matrix import torch.optim as optim device = torch.device('cuda' if torch.cuda.is_available() else 'cpu') model = model.to(device) optimizer = optim.Adam(model.parameters(), lr=1e-3) criterion = nn.CrossEntropyLoss() # 假设 X_train_tensor, y_train_tensor 已经转成 torch 张量 for epoch in range(20): model.train() optimizer.zero_grad() out = model(X_train_tensor) loss = criterion(out, y_train_tensor) loss.backward() optimizer.step() model.eval() with torch.no_grad(): pred = model(X_test_tensor).argmax(dim=1) f1 = f1_score(y_test_tensor.cpu(), pred.cpu(), average='binary') cm = confusion_matrix(y_test_tensor.cpu(), pred.cpu()) print(f"Epoch {epoch}: loss={loss.item():.4f}, F1={f1:.4f}") print(cm)逻辑说明:每轮训练后切到eval()模式,关掉 Dropout 和 BatchNorm 的训练行为。f1_score用average='binary'针对二分类。混淆矩阵打印出来,重点看假阴性(恶意被判成良性)的数量,这个在安全场景里代价最高。
3.3 学习率、批大小和早停的实操参数
学习率我一般从 1e-3 开始,用 Adam 优化器。如果 loss 震荡,降到 1e-4。批大小 64 或 128,取决于显存。早停策略:连续 5 轮验证集 F1 不提升就停,保存最佳模型权重。
best_f1 = 0 patience = 5 counter = 0 for epoch in range(50): # ... 训练代码 ... if f1 > best_f1: best_f1 = f1 torch.save(model.state_dict(), 'best_model.pth') counter = 0 else: counter += 1 if counter >= patience: print(f"早停于第 {epoch} 轮") break这套参数在 USTC-TFC2016 上通常 15 轮内收敛。如果 30 轮还在 0.7 以下,检查数据标签是不是错了,或者归一化有没有做。
4. 避坑与排查:那些让模型精度突然掉点的原因
4.1 数据泄漏:训练集和测试集混了同一条流
现象:测试集准确率 99%,换一批数据掉到 60%。原因:切分时没按会话切,同一条流的包被分到训练和测试两边。解决:切分前先按五元组聚合,确保整条流只出现在一边。用GroupShuffleSplit按流 ID 分组切分。
4.2 类别不平衡:恶意样本太少导致模型"偷懒"
现象:混淆矩阵里恶意类召回率极低,模型几乎全预测良性。原因:恶意样本占比不到 10%。解决:用WeightedRandomSampler给少数类更高采样权重,或者在 loss 里加class_weight。我一般用采样器,改动最小。
from torch.utils.data import WeightedRandomSampler class_counts = np.bincount(y_train) weights = 1.0 / class_counts samples_weights = weights[y_train] sampler = WeightedRandomSampler(samples_weights, len(samples_weights))4.3 字节归一化方式选错
现象:模型完全不收敛,loss 在 0.69 附近不动。原因:直接把 0-255 的字节喂进网络,数值范围太大。解决:除以 255 归一化到 0-1。别用 StandardScaler,字节数据不是正态分布。
4.4 会话截断长度设得太短
现象:F1 值卡在 0.85 上不去。原因:max_len=784截掉了太多信息,很多恶意流的关键特征在后半段。解决:试试 1024 或 1500,但注意显存。如果显存不够,用AdaptiveMaxPool1d在模型里做降采样。
4.5 忘了固定随机种子
现象:每次跑出来的结果不一样,论文里的数字对不上。原因:PyTorch、numpy、Python 的随机种子没固定。解决:在代码开头加这几行。
import random import numpy as np import torch def set_seed(seed=42): random.seed(seed) np.random.seed(seed) torch.manual_seed(seed) torch.cuda.manual_seed_all(seed) torch.backends.cudnn.deterministic = True torch.backends.cudnn.benchmark = False set_seed(42)5. 把模型变成系统:从脚本到可演示的检测流程
5.1 用 Flask 包一个最小可用的检测接口
毕业设计答辩时,光有训练脚本不够,最好能演示"输入一条流量,输出检测结果"。用 Flask 写一个 20 行的接口:
from flask import Flask, request, jsonify import numpy as np import torch app = Flask(__name__) model = TrafficCNN1D() model.load_state_dict(torch.load('best_model.pth', map_location='cpu')) model.eval() @app.route('/detect', methods=['POST']) def detect(): data = request.json['bytes'] # 长度 784 的整数列表 arr = np.array(data, dtype=np.float32) / 255.0 tensor = torch.from_numpy(arr).reshape(1, 1, -1) with torch.no_grad(): out = model(tensor) prob = torch.softmax(out, dim=1) pred = out.argmax(dim=1).item() return jsonify({ 'label': 'malicious' if pred == 1 else 'benign', 'confidence': prob[0][pred].item() }) if __name__ == '__main__': app.run(host='0.0.0.0', port=5000)逻辑说明:接收 JSON 格式的字节列表,归一化后转张量,前向推理,返回标签和置信度。参数port=5000可以改。测试时用curl发一条请求:
curl -X POST http://127.0.0.1:5000/detect \ -H "Content-Type: application/json" \ -d '{"bytes": [1,2,3,...]}'5.2 检测阈值怎么调:别用默认的 0.5
二分类默认阈值 0.5,但在安全场景里,漏报的代价远高于误报。我一般把阈值降到 0.3,宁可多报几个良性,也别放过恶意。调整方法:在验证集上画 ROC 曲线,找 FPR 最低且 TPR 可接受的点。
from sklearn.metrics import roc_curve fpr, tpr, thresholds = roc_curve(y_test, probs) # 找 FPR < 0.05 时 TPR 最高的阈值 idx = np.where(fpr < 0.05)[0] best_threshold = thresholds[idx[np.argmax(tpr[idx])]] print(f"建议阈值: {best_threshold:.4f}")5.3 可视化:混淆矩阵和 ROC 曲线怎么画进论文
论文里放两张图就够了:混淆矩阵热力图和 ROC 曲线。用 matplotlib 和 seaborn,代码不超过 15 行。
import matplotlib.pyplot as plt import seaborn as sns from sklearn.metrics import confusion_matrix, roc_curve, auc cm = confusion_matrix(y_test, preds) sns.heatmap(cm, annot=True, fmt='d', cmap='Blues') plt.xlabel('Predicted') plt.ylabel('True') plt.title('Confusion Matrix') plt.savefig('confusion_matrix.png', dpi=300) fpr, tpr, _ = roc_curve(y_test, probs) roc_auc = auc(fpr, tpr) plt.figure() plt.plot(fpr, tpr, label=f'AUC = {roc_auc:.4f}') plt.plot([0, 1], [0, 1], 'k--') plt.xlabel('False Positive Rate') plt.ylabel('True Positive Rate') plt.legend() plt.savefig('roc_curve.png', dpi=300)dpi=300保证论文打印清晰。annot=True在格子里显示数字。这两张图加上 F1 值表格,实验部分就完整了。
5.4 源码和文档的组织方式
最后说一个容易被忽略的点:代码目录结构。答辩老师会翻你的源码,如果所有文件堆在一个文件夹里,印象分直接扣。我一般这样组织:
project/ ├── data/ # 数据集存放 ├── src/ │ ├── preprocess.py # PCAP 转张量 │ ├── model.py # 模型定义 │ ├── train.py # 训练脚本 │ └── app.py # Flask 接口 ├── models/ # 保存的权重 ├── results/ # 混淆矩阵、ROC 图 ├── requirements.txt # 依赖清单 └── README.md # 运行说明requirements.txt用pip freeze > requirements.txt生成。README 里写清楚:Python 版本、依赖安装命令、数据放置路径、训练和推理的运行命令。这三样写清楚,别人拿到你的源码能跑起来,毕业设计的"工程完整性"就达标了。
我自己做这类项目最大的教训是:别等到最后一周才跑实验。数据预处理和模型调参各留三天,写文档和录演示视频留两天。中间遇到 F1 上不去,先检查数据泄漏和类别不平衡,这两个坑我踩过不止一次。希望帮到你。
本文还有配套的精品资源,点击获取