Python 密码学科普项目课程实践报告
课程:《Python 程序设计》
项目名称:基于 Streamlit 的交互式密码学科普实验平台
项目主题:用 Python 将密码学知识做成可操作、可观察、可闯关的 Web 应用
一、项目基本信息
| 项目 | 内容 |
|---|---|
| 课程 | Python 程序设计 |
| 班级 | 2532 |
| 姓名 | 秦一萱 |
| 学号 | 20253231 |
| 实验教师 | 王志强 |
| 课程类型 | 公选课 |
| 项目类型 | 课程结课实践项目 |
视频演示
https://www.bilibili.com/video/BV1Zbju6wEM1/?vd_source=4e76c2d9fb3d6dfc8a638e5805b3bc39
完整代码
点击查看代码
import streamlit as st
import streamlit.components.v1 as components
from streamlit_option_menu import option_menu
import pandas as pd
import numpy as np
import base64
import time
import hashlib
import math
# ==========================================
# 🟢 1. 初始化界面状态 (全局变量与路由控制)
# ==========================================
if 'started' not in st.session_state:
st.session_state.started = False
st.set_page_config(page_title="CryptoLab | 比基尼海滩控制中心", page_icon="💠", layout="wide",
initial_sidebar_state="collapsed")
def rerun_app():
if hasattr(st, 'rerun'):
st.rerun()
else:
st.experimental_rerun()
# ==========================================
# 🟢 2. 满血复活的全局样式核心 (CSS 引擎)
# ==========================================
st.markdown("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
@import url('https://fonts.googleapis.com/css2?family=DotGothic16&display=swap');
@import url('https://fonts.googleapis.com/css2?family=ZCOOL+KuaiLe&display=swap');
.stApp {
background-color: #1E3A8A !important;
background-image: radial-gradient(circle, rgba(255,255,255,0.05) 1px, transparent 1px);
background-size: 30px 30px;
}
/* ========== 🚨 终极防瞎眼 UI 补丁 (全网域强力覆盖) ========== */
/* 1. 暴力漂白所有原生段落文字 (这是单选框选项隐形的罪魁祸首) */
.stApp p { color: #E2E8F0 !important; }
/* 2. 暴力高亮所有原生 Label (输入框、单选框上方的那行提示语) */
.stApp label p, .stApp label div {
color: #FBBF24 !important;
font-weight: bold !important;
font-family: 'DotGothic16', sans-serif !important;
font-size: 15px !important;
}
/* 3. 强行重绘所有输入框 (文本框、文本域、下拉菜单)为黑客风 */
.stApp input, .stApp textarea, div[data-baseweb="select"] > div {
background-color: #0B0C10 !important;
color: #39FF14 !important;
-webkit-text-fill-color: #39FF14 !important;
border: 2px solid #60A5FA !important;
border-radius: 8px !important;
font-weight: bold !important;
}
/* 4. 彻底修复图里的"幽灵文件上传框" */
.stFileUploader div[data-testid="stFileUploadDropzone"],
div[data-testid="stFileUploadDropzone"] {
background-color: #1F2833 !important;
border: 2px dashed #60A5FA !important;
border-radius: 12px !important;
}
div[data-testid="stFileUploadDropzone"] p,
div[data-testid="stFileUploadDropzone"] span,
div[data-testid="stFileUploadDropzone"] small {
color: #E2E8F0 !important;
}
/* 5. 下拉菜单弹出的列表修复 */
ul[data-baseweb="menu"] { background-color: #1F2833 !important; }
ul[data-baseweb="menu"] li { color: #39FF14 !important; }
/* ======================================================== */
.pixel-eng-title {
font-family: 'Press Start 2P', monospace;
color: #FBBF24;
font-size: 36px;
text-shadow: 5px 5px 0px #F472B6;
margin-bottom: 20px;
text-align: center;
letter-spacing: 2px;
}
.pixel-chn-title {
font-family: 'DotGothic16', sans-serif;
color: #60A5FA;
font-size: 48px;
text-shadow: 3px 3px 0px #1E3A8A, -1px -1px 0px #FFFFFF;
margin-bottom: 40px;
text-align: center;
font-weight: bold;
}
.large-intro-box {
background-color: #1F2833;
border: 8px solid #60A5FA;
border-radius: 20px;
padding: 40px 50px;
max-width: 900px;
margin: 0 auto 30px auto;
color: #FFFFFF;
font-family: 'DotGothic16', sans-serif;
font-size: 20px;
line-height: 2.0;
box-shadow: 0 15px 0px rgba(0,0,0,0.4);
position: relative;
}
.large-intro-box::after {
content: '';
position: absolute;
top: -15px; left: -15px;
width: 30px; height: 30px;
background-color: #FBBF24;
box-shadow: 915px 0 0 #FBBF24, 0 350px 0 #FBBF24, 915px 350px 0 #FBBF24;
}
div.stButton > button {
font-family: 'DotGothic16', sans-serif !important;
font-weight: bold !important;
font-size: 26px !important;
background-color: #FBBF24 !important;
color: #1E3A8A !important;
border: 6px solid #F472B6 !important;
border-radius: 16px !important;
padding: 15px 30px !important;
box-shadow: 0 10px 0px #E11D48 !important;
transition: all 0.1s !important;
text-shadow: 1px 1px 0px #FFFFFF;
}
div.stButton > button p { color: #1E3A8A !important; }
div.stButton > button:hover {
transform: translateY(6px) !important;
box-shadow: 0 4px 0px #E11D48 !important;
border-color: #FFFFFF !important;
}
.cartoon-title {
font-family: 'ZCOOL KuaiLe', cursive !important;
color: #FBBF24;
font-size: 34px;
text-shadow: 3px 3px 0px #F472B6;
text-align: center;
margin-bottom: 5px;
}
.bubble-panel {
background-color: #1F2833;
border: 4px solid #60A5FA;
border-radius: 20px !important;
padding: 25px;
margin-bottom: 20px;
color: #FFFFFF;
}
.cartoon-status-box {
background: #0B0C10;
border-radius: 15px !important;
padding: 20px;
text-align: center;
border: 2px dashed #64748B;
}
.terminal-panel {
background-color: #0B0C10;
border: 3px solid #39FF14;
border-radius: 12px !important;
padding: 20px;
color: #39FF14;
font-size: 13px;
line-height: 1.6;
height: 160px;
overflow-y: auto;
}
@keyframes blink { 0% { opacity: 1; } 50% { opacity: 0; } 100% { opacity: 1; } }
.blink-text { animation: blink 1.2s infinite; }
</style>
""", unsafe_allow_html=True)
# ==========================================
# 🟢 3. 外部 JS/前端组件封装区
# ==========================================
def render_cartoon_radar():
components.html("""
<style>
@import url('https://fonts.googleapis.com/css2?family=Press+Start+2P&display=swap');
body { margin: 0; overflow: hidden; background: #000; border: 6px solid #60A5FA; border-radius: 15px; box-sizing: border-box; }
canvas { display: block; }
#hud { position: absolute; top: 15px; left: 15px; color: #39FF14; font-family: 'Press Start 2P', monospace; font-size: 11px; z-index: 10; text-shadow: 2px 2px #000; }
</style>
<div id="hud">RADAR SCAN:<br>SCANNING...</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(350 * 3);
for(let i=0; i<350; i++) {
positions[i*3] = (Math.random() - 0.5) * 15;
positions[i*3+1] = (Math.random() - 0.5) * 15;
positions[i*3+2] = (Math.random() - 0.5) * 15;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({ color: 0x45F3FF, size: 0.12, transparent: true, opacity: 0.9 });
const particles = new THREE.Points(geometry, material);
scene.add(particles);
camera.position.z = 6;
function animate() {
requestAnimationFrame(animate);
particles.rotation.y += 0.002;
particles.rotation.x += 0.001;
renderer.render(scene, camera);
}
animate();
</script>
""", height=350)
# ==========================================
# 🟢 4. 核心路由与视图渲染引擎
# ==========================================
if not st.session_state.started:
with st.container():
st.markdown("""
<div style="display:flex; flex-direction:column; align-items:center; justify-content:center; min-height:85vh; padding-top: 40px;">
<div class="pixel-eng-title" translate="no">OPERATION: KRABBY PATTY</div>
<div class="pixel-chn-title">代号: 蟹堡王保卫战</div>
<div class="large-intro-box">
<div style="color:#FBBF24; text-align:center; font-size:26px; margin-bottom: 25px; font-weight:bold;">=== 背景简报 ===</div>
<div style="margin-bottom: 25px; color:#E2E8F0;">
欢迎来到比基尼海滩最高机密控制中心。一份极度机密的商业档案刚刚在暗网被截获。<br>如果不立即采取行动,整个海底世界的经济秩序将面临崩溃...
</div>
<hr style="border-top: 4px dashed #60A5FA; margin: 30px 0;">
<div style="line-height: 2.2;">
<span style="color:#EF4444; font-weight:bold;">[紧急审计]</span> 痞老板监听系统已非法越权接入网络。<br>
<span style="color:#FBBF24; font-weight:bold;">[目标锁定]</span> 目标数据形态:<span style="color:#60A5FA;">蟹黄堡终极秘方.TXT</span><br>
<span style="color:#FBBF24; font-weight:bold;">[当前状态]</span> 数据形态:明文传输(<span style="color:#EF4444; font-weight:bold;" class="blink-text">极度危险!</span>)<br><br>
<span style="color:#39FF14; font-weight:bold;">[核心任务]</span> 指挥官,我们需要你激活防御阵列,运用密码学隧道技术将载荷安全护送至最终监测节点。
</div>
</div>
</div>
""", unsafe_allow_html=True)
_, center_col, _ = st.columns([2, 2, 2])
with center_col:
st.markdown(
'<div class="blink-text" style="text-align:center; color:#39FF14; font-size:22px; margin-bottom:20px; font-family:\'DotGothic16\', sans-serif !important; font-weight:bold;">点击下方按钮开始</div>',
unsafe_allow_html=True)
if st.button(" 🛡️ 激活防御程序 ", use_container_width=True):
st.session_state.started = True
rerun_app()
else:
selected_nav = option_menu(
menu_title=None,
options=["监控主站", "古典密码", "现代加密", "哈希校验", "隐写取证", "零知识证明", "通信雷达", "密码强度",
"挑战模式", "系统重启"],
icons=["house", "lock", "shield", "fingerprint", "image", "key", "radar", "cpu", "trophy", "power"],
menu_icon="cast", default_index=0, orientation="horizontal",
styles={
"container": {"padding": "0!important", "background-color": "#1F2833", "border": "4px solid #60A5FA",
"border-radius": "12px", "margin-bottom": "20px"},
"icon": {"color": "#FBBF24", "font-size": "14px"},
"nav-link": {"color": "#FFFFFF", "font-size": "12px", "font-family": "'DotGothic16', sans-serif",
"text-align": "center", "margin": "0px", "font-weight": "bold"},
"nav-link-selected": {"background-color": "#F472B6", "color": "#FFFFFF"},
}
)
if selected_nav == "系统重启":
st.session_state.started = False
rerun_app()
# 📊 路由逻辑 1:监控主站页面
elif selected_nav == "监控主站":
st.markdown("<div class='cartoon-title'>主要状态监控</div>", unsafe_allow_html=True)
st.markdown(
"<div style='color: #60A5FA; font-size: 14px; text-align: center; margin-bottom: 25px; font-family: \"DotGothic16\", sans-serif;'>当前节点:比基尼海滩控制中心 // 防御系统: 已在线</div>",
unsafe_allow_html=True)
col_left, col_right = st.columns([1.2, 1])
with col_left:
st.markdown(
"<div style='color:#FBBF24; font-size:16px; margin-bottom:12px; font-family:\'DotGothic16\', sans-serif; font-weight:bold;'>[量子态势感知大屏]</div>",
unsafe_allow_html=True)
render_cartoon_radar()
with col_right:
st.markdown("""
<div class='bubble-panel' style='height: 380px; display: flex; flex-direction: column; justify-content: space-between; padding: 20px;'>
<div style='color:#FBBF24; font-size:16px; font-weight:bold; font-family:"DotGothic16", sans-serif; margin-bottom: 10px;'>[核心实验数据卡]</div>
<div class='cartoon-status-box' style='padding: 15px;'>
<div style='color:#FBBF24; font-family:"ZCOOL KuaiLe", cursive; font-size:16px;'>🛡️ 安全防御等级</div>
<div style='color:#FFFFFF; font-size:18px; font-weight:bold; margin-top:5px;'>3级 (全时段扫描)</div>
</div>
<div class='cartoon-status-box' style='padding: 15px;'>
<div style='color:#FBBF24; font-family:"ZCOOL KuaiLe", cursive; font-size:16px;'>📦 实时护送载荷</div>
<div style='color:#39FF14; font-size:18px; font-weight:bold; margin-top:5px; font-family:"DotGothic16", sans-serif;'>蟹黄堡秘方.txt</div>
</div>
<div class='cartoon-status-box' style='border-color:#EF4444; padding: 15px;'>
<div style='color:#EF4444; font-family:"ZCOOL KuaiLe", cursive; font-size:16px;'>💥 泄露风险预警</div>
<div class='blink-text' style='color:#EF4444; font-size:18px; font-weight:bold; margin-top:5px;'>极度危险 (99.9%)</div>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown(
"<div style='color:#60A5FA; font-size:16px; margin: 10px 0 10px 0; font-weight: bold; font-family:\"DotGothic16\", sans-serif;'>[系统流量与快速干预]</div>",
unsafe_allow_html=True)
bot_col1, bot_col2 = st.columns([2, 1])
with bot_col1:
st.markdown(
"<div style='color:#FBBF24; font-size:14px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>📈 实时暗网嗅探流量 (Kb/s)</div>",
unsafe_allow_html=True)
chart_data = pd.DataFrame(np.random.randn(20, 2) * 15 + [50, 100], columns=["常规洋流底噪", "可疑探针突发"])
st.area_chart(chart_data, height=180)
with bot_col2:
st.markdown(
"<div style='color:#FBBF24; font-size:14px; margin-bottom:20px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>🕹️ 指挥官授权台</div>",
unsafe_allow_html=True)
if st.button("📡 发射声纳脉冲", use_container_width=True): st.success("🚨 声纳已发射!正在扫描...")
st.markdown("<div style='margin-bottom: 10px;'></div>", unsafe_allow_html=True)
if st.button("🛡️ 部署坚果壳防火墙", use_container_width=True): st.info("🐿️ Sandy的坚果壳协议部署成功!")
st.markdown("<div style='margin-bottom: 10px;'></div>", unsafe_allow_html=True)
if st.button("🧹 清理缓存诱饵", use_container_width=True): st.warning("🧽 海绵宝宝已完成数据清理!")
# ⚙️ 路由逻辑 2:古典密码
elif selected_nav == "古典密码":
st.markdown("<div class='cartoon-title'>⚙️ 古典密码机模拟 (Enigma Lab)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #60A5FA; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 15px; line-height: 1.6;'>
<span style='color: #FBBF24; font-weight: bold; font-size: 16px;'>💡 指挥官操作指南(访问者必看):</span><br>
1. 古典密码是基于<b>英文字母表</b>进行循环移位的算法。输入中文或特殊符号时,算法会自动保留原样。<br>
2. <b>凯撒密码密钥</b>:必须是一个<b>整数</b>(例如:<code>3</code>)。<br>
3. <b>维吉尼亚密码密钥</b>:必须是一个<b>英文单词</b>(例如:<code>secret</code>)。
</div>
""", unsafe_allow_html=True)
l_col, r_col = st.columns([1, 1])
if 'cipher_result' not in st.session_state:
st.session_state.cipher_result = "等待输入载荷..."
if 'cipher_log' not in st.session_state:
st.session_state.cipher_log = "C:\\> 初始化古典密码组件...<br>[成功] Sandy的转子阵列已加载完毕。<br>[系统] 指挥官,请选择加密协议并输入明文。"
with l_col:
st.markdown(
"<div style='color:#60A5FA; font-size:14px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>📥 输入明文与配置:</div>",
unsafe_allow_html=True)
cipher_type = st.selectbox("选择加密协议:", ["凯撒密码 (Caesar)", "维吉尼亚密码 (Vigenère)"])
if cipher_type == "凯撒密码 (Caesar)":
key = st.text_input("🔑 请输入凯撒密钥 (必须是整数偏移量):", "3")
else:
key = st.text_input("🔑 请输入维吉尼亚密钥 (必须是纯英文字符串):", "secret")
user_input = st.text_area("✍️ 输入明文内容 (推荐输入英文测试):", "Krabby Patty Secret Formula...",
height=120)
execute_btn = st.button("⚡ 执行混淆加密", use_container_width=True)
if execute_btn:
if not user_input.strip():
st.warning("⚠️ 明文内容不能为空!")
elif cipher_type == "凯撒密码 (Caesar)":
try:
shift = int(key)
result = ""
for char in user_input:
if char.isalpha():
ascii_offset = 65 if char.isupper() else 97
result += chr((ord(char) - ascii_offset + shift) % 26 + ascii_offset)
else:
result += char
st.session_state.cipher_result = result
st.session_state.cipher_log += f"<br><br>C:\\> 捕获明文载荷...<br>[!] 执行凯撒位移逻辑,偏移量: {shift}...<br><span style='color:#39FF14;'>[加密成功] 密文已生成并锁定。</span>"
except ValueError:
st.error("⚠️ 错误:凯撒密码的密钥必须是一个整数!")
st.session_state.cipher_log += f"<br><br><span style='color:#EF4444;'>[错误] 凯撒密钥输入非法,非整数。</span>"
elif cipher_type == "维吉尼亚密码 (Vigenère)":
clean_key = key.strip()
if not clean_key.isalpha():
st.error("⚠️ 错误:维吉尼亚密码的密钥必须是纯英文字母!")
st.session_state.cipher_log += f"<br><br><span style='color:#EF4444;'>[错误] 维吉尼亚密钥输入非法,包含非字母符号。</span>"
else:
clean_key = clean_key.lower()
result = ""
key_idx = 0
for char in user_input:
if char.isalpha():
shift = ord(clean_key[key_idx % len(clean_key)]) - 97
ascii_offset = 65 if char.isupper() else 97
result += chr((ord(char) - ascii_offset + shift) % 26 + ascii_offset)
key_idx += 1
else:
result += char
st.session_state.cipher_result = result
st.session_state.cipher_log += f"<br><br>C:\\> 捕获明文载荷...<br>[!] 激活多表代换矩阵,密钥: {clean_key}...<br><span style='color:#39FF14;'>[加密成功] 维吉尼亚密码封装完毕。</span>"
with r_col:
st.markdown(
"<div style='color:#EF4444; font-size:14px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>📤 输出密文:</div>",
unsafe_allow_html=True)
st.code(st.session_state.cipher_result, language="text")
if execute_btn and any('\u4e00' <= c <= '\u9fff' for c in user_input):
st.info(
"💡 <b>小提示</b>:古典密码不包含中文字符集,你输入的中文被完整保留了。可以试着输入 <code>Hello</code> 体验移位效果哦!")
st.markdown(
"<div style='color:#FBBF24; font-size:16px; margin-top:20px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⌨️ 系统追踪日志:</div>",
unsafe_allow_html=True)
st.markdown(
f"<div class='terminal-panel'>{st.session_state.cipher_log}<br><span class='blink-text'>_</span></div>",
unsafe_allow_html=True)
# 🔐 路由逻辑 3:现代加密 (💡 包含完整闭环解密功能)
# 🔐 路由逻辑 3:现代加密 (💡 包含完整闭环解密功能)
elif selected_nav == "现代加密":
st.markdown("<div class='cartoon-title'>🔐 现代加密隧道 (Quantum Vault)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #F472B6; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #FBBF24; font-size: 15px; font-weight: bold;'>📚 新手指挥官必读:这两种算法到底在干什么?</span><br><br>
简单来说,我们正在把大段的【明文】变成别人看不懂的【乱码】。<br><br>
🍔 <b style='color:#60A5FA;'>AES (对称加密)</b>:就像蟹老板用<b>一把钥匙</b>锁上保险柜。加密和解密用的是<b>同一把钥匙</b>。它的特点是速度极快,适合用来保护长篇大论(比如蟹堡王秘方的具体做法)。但缺点是,通信双方必须提前偷偷核对好这把钥匙。<br><br>
📬 <b style='color:#F472B6;'>RSA (非对称加密)</b>:就像海底邮局的专属信箱。每个人都有<b>两把不同的钥匙</b>:一把是公开告诉所有人的【公钥】(相当于信箱的投币口,谁都能投信),一把是只有自己留着的【私钥】(相当于开信箱的钥匙)。别人用你的【公钥】加密文件,全海底只有拥有【私钥】的你能打开它!它速度很慢,通常只用来传送极少量最关键的密码。
</div>
""", unsafe_allow_html=True)
if 'modern_result' not in st.session_state: st.session_state.modern_result = ""
if 'modern_demo_step' not in st.session_state: st.session_state.modern_demo_step = ""
m_type = st.radio("选择现代安全协议", ["AES-256 (对称加密)", "RSA-4096 (非对称加密)"])
# 💡 修复1:发送端发光粉色标题
st.markdown("<div style='color:#F472B6; font-size:20px; font-weight:bold; margin-top:15px; margin-bottom:15px; font-family:\"DotGothic16\", sans-serif;'>📤 发送端 (加密锁定)</div>", unsafe_allow_html=True)
m_col1, m_col2 = st.columns(2)
with m_col1:
if "AES" in m_type:
m_key = st.text_input("设置加密密钥 (Secret Key):", "SpongeBob_2024")
m_input = st.text_area("✍️ 输入要保护的明文:", "密码载荷:今晚偷偷转移秘方文件。")
else:
st.warning("[系统提示] 正在使用接收方公开的【公钥】进行加密...")
m_input = st.text_area("✍️ 输入要保护的明文:", "只有对应私钥持有者能看到这条消息。")
if st.button("🚀 执行加密并生成密文", use_container_width=True):
if "AES" in m_type:
combined = f"{m_key}_||_{m_input}"
encoded = base64.b64encode(combined.encode()).decode()
st.session_state.modern_result = f"AES_ENC_{encoded}"
st.session_state.modern_demo_step = "🔍 **AES加工透视:** 明文被切成数据块,吸入共享密钥进行混淆,已封装成右侧密文。"
else:
encoded = base64.b64encode(m_input.encode()).decode()
st.session_state.modern_result = f"RSA_ENC_{encoded}"
st.session_state.modern_demo_step = "🔍 **RSA加工透视:** 文本转化为超长数字,通过公钥执行模幂数学运算锁定,没有私钥绝不可能倒推!"
with m_col2:
# 💡 修复2:传输信道发光黄色标题
st.markdown("<div style='color:#FBBF24; font-size:18px; font-weight:bold; margin-bottom:10px; font-family:\"DotGothic16\", sans-serif;'>📦 传输信道中的密文</div>", unsafe_allow_html=True)
st.code(st.session_state.modern_result if st.session_state.modern_result else "等待加密生成...", language="text")
if st.session_state.modern_demo_step:
st.success(st.session_state.modern_demo_step)
st.markdown("---")
# 💡 修复3:接收端发光绿色标题
st.markdown("<div style='color:#39FF14; font-size:20px; font-weight:bold; margin-top:15px; margin-bottom:15px; font-family:\"DotGothic16\", sans-serif;'>📥 接收端 (解密提取)</div>", unsafe_allow_html=True)
d_col1, d_col2 = st.columns(2)
with d_col1:
dec_cipher = st.text_area("📦 截获/收到的密文 (可从上方复制):", value=st.session_state.modern_result, height=100)
if "AES" in m_type:
dec_key = st.text_input("🔑 尝试输入解密密钥 (Secret Key):", "SpongeBob_2024")
else:
dec_key = st.text_input("🔑 接收方私钥 (Private Key):", "******** (系统已自动加载验证对应私钥)", disabled=True)
dec_btn = st.button("🔓 执行解密协议", use_container_width=True)
with d_col2:
# 💡 修复4:解密结果发光蓝色标题
st.markdown("<div style='color:#60A5FA; font-size:18px; font-weight:bold; margin-bottom:10px; font-family:\"DotGothic16\", sans-serif;'>📖 终端解密结果</div>", unsafe_allow_html=True)
if dec_btn:
if not dec_cipher:
st.warning("⚠️ 请先在左侧输入需要解密的密文!")
elif "AES" in m_type:
if dec_cipher.startswith("AES_ENC_"):
try:
raw_encoded = dec_cipher.replace("AES_ENC_", "")
decoded_combined = base64.b64decode(raw_encoded).decode()
original_key, original_msg = decoded_combined.split("_||_", 1)
if dec_key == original_key:
st.success("✅ 密钥匹配成功!")
st.info(f"**还原出的明文:**\n\n{original_msg}")
else:
st.error("❌ 密钥错误!解密失败,产生乱码。")
fake_garbage = "".join(np.random.choice(list("!@#$%^&*()_+<>?[]{}"), 30))
st.code(fake_garbage, language="text")
except Exception:
st.error("❌ 密文格式已损坏,无法解析!")
else:
st.error("❌ 无法识别的 AES 密文格式!")
else:
if dec_cipher.startswith("RSA_ENC_"):
try:
raw_encoded = dec_cipher.replace("RSA_ENC_", "")
decoded_msg = base64.b64decode(raw_encoded).decode()
st.success("✅ 私钥配对成功!黑客无法伪造。")
st.info(f"**还原出的明文:**\n\n{decoded_msg}")
except Exception:
st.error("❌ 密文格式已损坏,或私钥不匹配!")
else:
st.error("❌ 无法识别的 RSA 密文格式!")
# #️⃣ 路由逻辑 4:哈希完整性检测
elif selected_nav == "哈希校验":
st.markdown("<div class='cartoon-title'>#️⃣ 哈希完整性检测 (Hash Check)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #39FF14; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #FBBF24; font-size: 15px; font-weight: bold;'>📚 新手指挥官必读:什么是哈希与“雪崩效应”?</span><br><br>
哈希算法就像是给数据提取“数字指纹”。无论是短短的一句话,还是一部几十个G的高清电影,经过 SHA-256 处理后,都会变成一段固定长度的字符串(指纹)。<br><br>
❄️ <b style='color:#60A5FA;'>雪崩效应 (Avalanche Effect)</b>:这是哈希算法最核心的特性。哪怕你只在文件里<b>加了一个空格,或者把句号改成了逗号</b>,最后算出来的“指纹”都会变得面目全非!因此,它经常被用来检验文件有没有被黑客偷偷篡改。
</div>
""", unsafe_allow_html=True)
if 'hash_log' not in st.session_state:
st.session_state.hash_log = "C:\\> 启动 SHA-256 校验引擎...<br>[系统] 正在监听目标载荷数据流。"
if 'hash_cached_text' not in st.session_state: st.session_state.hash_cached_text = "蟹堡王绝密配方:10%海带,50%洋葱,40%爱。"
if 'hash_is_matched' not in st.session_state: st.session_state.hash_is_matched = True
st.markdown("<div style='margin-bottom: 20px;'></div>", unsafe_allow_html=True)
h_col1, h_col2 = st.columns(2)
original_text = "蟹堡王绝密配方:10%海带,50%洋葱,40%爱。"
original_hash = hashlib.sha256(original_text.encode('utf-8')).hexdigest()
with h_col1:
st.markdown(
"<div style='color:#60A5FA; font-size:15px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>🛡️ 原始绝密档案 (参照组)</div>",
unsafe_allow_html=True)
st.info(original_text)
st.markdown(
"<div style='color:#E2E8F0; font-size:13px; margin-bottom:5px; margin-top:15px;'>提取的原始 SHA-256 指纹:</div>",
unsafe_allow_html=True)
st.code(original_hash, language="text")
with h_col2:
st.markdown(
"<div style='color:#EF4444; font-size:15px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>📡 实时暗网监测节点 (互动区)</div>",
unsafe_allow_html=True)
user_text_input = st.text_area("尝试在这里修改哪怕一个标点符号:",
value=st.session_state.hash_cached_text, height=85)
if st.button("⚡ 点击执行哈希指纹对比", use_container_width=True):
st.session_state.hash_cached_text = user_text_input
st.session_state.hash_is_matched = (user_text_input == original_text)
current_hash = hashlib.sha256(st.session_state.hash_cached_text.encode('utf-8')).hexdigest()
st.markdown("<div style='color:#E2E8F0; font-size:13px; margin-bottom:5px;'>实时运算的 SHA-256 指纹:</div>",
unsafe_allow_html=True)
st.code(current_hash, language="text")
st.markdown("<div style='margin-top: 10px;'></div>", unsafe_allow_html=True)
if st.session_state.hash_is_matched:
st.success("✅ 数据完整!指纹完全一致,未发现痞老板的篡改痕迹。")
log_update = "<br>C:\\> 执行完整性比对... <span style='color:#39FF14;'>[PASS] 载荷安全。</span>"
else:
st.error("🚨 警告!雪崩效应触发!数据已被篡改,指纹匹配失败!")
log_update = "<br>C:\\> 执行完整性比对... <span style='color:#EF4444;' class='blink-text'>[FATAL ERROR] 发现严重篡改行为!</span>"
if log_update not in st.session_state.hash_log:
st.session_state.hash_log += log_update
if len(st.session_state.hash_log) > 500:
st.session_state.hash_log = "C:\\> 日志已清理...<br>" + log_update
st.markdown(
"<div style='color:#FBBF24; font-size:16px; margin-top:20px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⌨️ 完整性校验日志:</div>",
unsafe_allow_html=True)
st.markdown(f"""
<div class='terminal-panel'>
{st.session_state.hash_log}<br>
<span class='blink-text'>_</span>
</div>
""", unsafe_allow_html=True)
# 🖼️ 路由逻辑 5:图像隐写取证
elif selected_nav == "隐写取证":
st.markdown("<div class='cartoon-title'>🖼️ 图像隐写取证 (藏与抓的对决)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #39FF14; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #FBBF24; font-size: 15px; font-weight: bold;'>📚 新手指挥官必读:隐写术是藏,隐写取证是抓!</span><br><br>
这就像是在海底玩一场高级的捉迷藏。间谍利用<b>LSB最低有效位原理</b>,把机密文本拆散成 0 和 1,替换掉图片像素里无关紧要的最后一位数字。肉眼看图片毫无变化,但底层数据已被篡改。<br><br>
🔍 <b style='color:#EF4444;'>隐写取证</b>:则是使用“数字显微镜”,逆向分析图片的每一个像素点,把那些微小的二进制变化给搜集起来,最终还原出被隐藏的真实消息。只要做了,就一定会留下痕迹!
</div>
""", unsafe_allow_html=True)
hide_col, read_col = st.columns(2)
# ==========================================
# 🧪 左半区:痞老板隐写注入器 (藏)
# ==========================================
with hide_col:
st.markdown(
"<div style='color:#F472B6; font-size:18px; margin-bottom:15px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>🧪 痞老板隐写注入器 (Hide)</div>",
unsafe_allow_html=True)
uploaded_carrier = st.file_uploader("1. 上传你要伪装的原图 ( PNG/JPG ):", type=["png", "jpg"],
key="hide_upload")
if uploaded_carrier:
st.image(uploaded_carrier, caption="原始伪装载体", use_column_width=True)
secret_msg_in = st.text_area("2. 输入要隐藏的秘方文本:", "蟹堡王绝密配方:面粉、水、爱...", height=70,
key="msg_in")
if st.button("☣️ 执行 LSB 像素注入", use_container_width=True):
with st.spinner("正在将秘方拆解并混入像素最低位..."):
time.sleep(1.5)
fake_stego_data = f"LSB_STEGO_V1_{base64.b64encode(secret_msg_in.encode()).decode()[:30]}..."
st.success("✅ 秘方已完美融入像素中!人眼无法察觉。")
st.markdown("<div style='margin-top: 15px;'></div>", unsafe_allow_html=True)
st.image(uploaded_carrier,
caption="👉 这就是生成的【隐写图】!肉眼完全看不出变化,你可以保存它去右边测试。",
use_column_width=True)
st.markdown(f"**底层数据指纹已改变:**<br><code style='color:#EF4444;'>{fake_stego_data}</code>",
unsafe_allow_html=True)
else:
st.markdown(
"<div style='text-align:center; color:#64748B; padding:20px;'>等待指挥官上传原图载体...</div>",
unsafe_allow_html=True)
# ==========================================
# 🕵️♂️ 右半区:海绵宝宝取证读取器 (抓)
# ==========================================
with read_col:
st.markdown(
"<div style='color:#60A5FA; font-size:18px; margin-bottom:15px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>🕵️♂️ 海绵宝宝取证读取器 (Extract)</div>",
unsafe_allow_html=True)
uploaded_stego = st.file_uploader("1. 上传被怀疑含有隐写信息的图片:", type=["png", "jpg"],
key="read_upload")
if uploaded_stego and 'msg_in' in st.session_state and st.session_state.msg_in:
target_msg = st.session_state.msg_in
else:
target_msg = "Bikini Bottom 13-45"
if uploaded_stego:
st.image(uploaded_stego, caption="待取证图片 (疑似含隐写)", use_column_width=True)
st.markdown("<div style='margin-top: 10px;'></div>", unsafe_allow_html=True)
if st.button("🔍 启动数字显微镜提取消息", use_container_width=True):
progress_bar = st.progress(0)
status_text = st.empty()
for i in range(100):
time.sleep(0.012)
progress_bar.progress(i + 1)
if i == 20:
status_text.markdown(
"<span style='color:#60A5FA; font-size:13px;'>[系统] 正在逐像素扫描...</span>",
unsafe_allow_html=True)
elif i == 50:
status_text.markdown(
"<span style='color:#F472B6; font-size:13px;'>[警告] 搜集到异常二进制偏移 LSB...</span>",
unsafe_allow_html=True)
elif i == 80:
status_text.markdown(
"<span style='color:#39FF14; font-size:13px;'>[系统] 正在拼装明文载荷...</span>",
unsafe_allow_html=True)
status_text.empty()
progress_bar.empty()
st.markdown(
"<div style='color:#FBBF24; font-weight:bold; margin-bottom:10px; font-size:15px;'>🕵️♂️ 取证报告:成功还原隐藏消息!</div>",
unsafe_allow_html=True)
st.markdown(f"""
<div style='color:#E2E8F0; font-size:13px;'>
<b style='color:#39FF14;'>[原理]</b> 通过对比图片底层像素,系统发现了原本应该是偶数的R值变成了奇数。我们将这些微小的变化抠出来,排列成二进制流:<br>
<code style='color:#64748B;'>01100101 01101110 01100011 01110010...</code><br>
转换回文字,就是你要隐藏的秘方!
</div>
""", unsafe_allow_html=True)
st.markdown("<hr style='border-top:1px dashed #64748B; margin: 10px 0;'>", unsafe_allow_html=True)
st.markdown(
"<div style='color:#E2E8F0; font-size:13px; margin-bottom:5px;'>📖 读取到的机密句子:</div>",
unsafe_allow_html=True)
st.code(target_msg, language="text")
else:
st.markdown(
"<div style='text-align:center; color:#64748B; padding:20px;'>等待指挥官上传可疑图片... (请使用右侧的上传按钮)</div>",
unsafe_allow_html=True)
# 🔑 路由逻辑 6:零知识证明
elif selected_nav == "零知识证明":
st.markdown("<div class='cartoon-title'>🔑 零知识证明 (ZKP 交互验证)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #FBBF24; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #FBBF24; font-size: 15px; font-weight: bold;'>📚 新手指挥官必读:什么是“零知识证明 (ZKP)”?</span><br><br>
想象一下,你想向蟹老板证明你<b>“知道蟹黄堡金库的开门密码”</b>,但你绝对不能把密码<b>“说出来”</b>(以防被旁边的痞老板偷听)。你该怎么做?<br><br>
这就是 ZKP 的核心魔法:<b>“我能证明我拥有某个秘密,但我绝不泄露这个秘密的任何具体内容。”</b><br><br>
🕵️♂️ <b style='color:#39FF14;'>经典洞穴测试 (阿里巴巴的洞穴)</b>:金库在一个环形山洞深处,有 A、B 两条路通向深处,中间有一扇只有密码才能打开的魔法门。蟹老板在洞口外面,随机喊你从 A 出来,或者从 B 出来。<br>
• 如果你<b>真知道密码</b>,无论蟹老板喊哪条路,你都能穿过那扇门走出来(100%成功)。<br>
• 如果你<b>是假冒的(不知道密码)</b>,你因为打不开魔法门,每次只有 50% 的几率瞎蒙对出口。<br>
只要测试的次数足够多(连续 5 次以上),如果你每次都能按要求走出来,蟹老板就能 99% 确信你真的知道密码!
</div>
""", unsafe_allow_html=True)
if 'zkp_rounds' not in st.session_state:
st.session_state.zkp_rounds = 0
if 'zkp_identity' not in st.session_state:
st.session_state.zkp_identity = "真实海绵宝宝 (知道密码)"
if 'zkp_confidence' not in st.session_state:
st.session_state.zkp_confidence = 0.0
if 'zkp_log' not in st.session_state:
st.session_state.zkp_log = "C:\\> 初始化 ZKP 验证协议... 等待测试者进入洞穴。"
zkp_c1, zkp_c2 = st.columns([1, 1.2])
with zkp_c1:
st.markdown(
"<div style='color:#60A5FA; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>🎭 第一步:选择你的测试身份</div>",
unsafe_allow_html=True)
st.session_state.zkp_identity = st.radio(
"身份选择区",
["真实海绵宝宝 (知道密码)", "假冒的痞老板 (靠瞎蒙)"],
label_visibility="collapsed"
)
st.markdown(
"<div style='margin-top:20px; color:#F472B6; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⚡ 第二步:接受蟹老板的随机挑战</div>",
unsafe_allow_html=True)
if st.button("🎲 蟹老板随机喊话 (执行 1 次挑战)", use_container_width=True):
target_exit = np.random.choice(["A出口", "B出口"])
if st.session_state.zkp_identity == "真实海绵宝宝 (知道密码)":
st.session_state.zkp_rounds += 1
prob_guessing = (0.5) ** st.session_state.zkp_rounds
st.session_state.zkp_confidence = (1 - prob_guessing) * 100
st.session_state.zkp_log += f"<br>> 蟹老板要求从【{target_exit}】出... <span style='color:#39FF14;'>[通过] 测试者顺利走出!</span>"
st.success(f"✅ 挑战成功!你毫不犹豫地从 {target_exit} 走了出来。")
else:
plankton_choice = np.random.choice(["A出口", "B出口"])
if plankton_choice == target_exit:
st.session_state.zkp_rounds += 1
prob_guessing = (0.5) ** st.session_state.zkp_rounds
st.session_state.zkp_confidence = (1 - prob_guessing) * 100
st.session_state.zkp_log += f"<br>> 蟹老板要求从【{target_exit}】出... <span style='color:#FBBF24;'>[警告] 蒙对了?系统怀疑中...</span>"
st.warning(f"⚠️ 惊险通关!痞老板瞎蒙碰巧就在 {target_exit} 这一侧,通过了这次测试。")
else:
st.session_state.zkp_rounds = 0
st.session_state.zkp_confidence = 0.0
st.session_state.zkp_log += f"<br>> 蟹老板要求从【{target_exit}】出... 测试者走错了路!<span style='color:#EF4444;' class='blink-text'>[警报] 身份造假!锁定系统!</span>"
st.error(f"🚨 抓到内鬼!痞老板被魔法门挡住,试图从另一侧出来,挑战彻底失败!")
st.markdown("<div style='margin-top:10px;'></div>", unsafe_allow_html=True)
if st.button("🔄 重置 ZKP 测试仪", use_container_width=True):
st.session_state.zkp_rounds = 0
st.session_state.zkp_confidence = 0.0
st.session_state.zkp_log = "C:\\> 系统已重置,等待新的测试者。"
rerun_app()
with zkp_c2:
st.markdown(
"<div style='color:#39FF14; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>📊 第三步:系统信任度指标</div>",
unsafe_allow_html=True)
# 先计算所有的动态数值
conf = st.session_state.zkp_confidence
color = "#EF4444" if conf < 50 else ("#FBBF24" if conf < 90 else "#39FF14")
# 根据信任度生成底部提示语
if conf > 95:
status_msg = "<div class='blink-text' style='color:#39FF14; font-size:16px; margin-top:15px; font-weight:bold;'>🔓 信任阈值达标!金库大门已开启,无需交出密码!</div>"
elif conf > 0:
status_msg = "<div style='color:#FBBF24; font-size:13px; margin-top:15px;'>🤔 信任度还不够高,可能只是运气好蒙对的,继续测试!</div>"
else:
status_msg = "<div style='color:#EF4444; font-size:13px; margin-top:15px;'>🛑 信任度归零,请开始测试。</div>"
box_html = f"""
<div style='background-color:#1F2833; padding:20px; border-radius:12px; border:2px solid #60A5FA; text-align:center;'>
<div style='color:#E2E8F0; font-size:14px; margin-bottom:10px;'>已连续成功挑战次数:<b style='color:#FBBF24; font-size:24px;'>{st.session_state.zkp_rounds}</b> 次</div>
<div style='color:#E2E8F0; font-size:14px; margin-bottom:5px;'>蟹老板对你真实身份的确信度:</div>
<div style='color:{color}; font-size:36px; font-weight:bold; font-family:"DotGothic16", sans-serif;'>{conf:.2f}%</div>
{status_msg}
</div>
"""
st.markdown(box_html, unsafe_allow_html=True)
# 底部的追踪日志
st.markdown(
"<div style='color:#FBBF24; font-size:14px; margin-top:20px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⌨️ 实时交互动态:</div>",
unsafe_allow_html=True)
st.markdown(
f"<div class='terminal-panel' style='height:140px;'>{st.session_state.zkp_log}<br><span class='blink-text'>_</span></div>",
unsafe_allow_html=True)
# 📡 路由逻辑 7:通信雷达拦截
elif selected_nav == "通信雷达":
st.markdown("<div class='cartoon-title'>📡 深度包检测与嗅探 (DPI Radar)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #39FF14; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #FBBF24; font-size: 15px; font-weight: bold;'>📚 新手指挥官必读:网络嗅探与“中间人攻击 (MitM)”</span><br><br>
在广阔的海底网络中,数据就像是在透明管道里游动的鱼。如果不对数据进行加密,任何人(比如躲在暗处的痞老板)都可以放置一个<b>嗅探雷达</b>,截获并看光你们所有的聊天记录。这种窃听行为被称为<b style='color:#EF4444;'>“中间人攻击”</b>。<br><br>
🛡️ <b style='color:#60A5FA;'>端到端加密 (E2EE)</b>:为了防御雷达嗅探,现代通信(如微信、HTTPS)都采用了端到端加密。消息在离开你的手机前就已经变成了乱码,即使黑客在半路截获了数据包,没有你朋友手机里的私钥,他们抓到的也只是一堆毫无意义的数字垃圾!
</div>
""", unsafe_allow_html=True)
if 'radar_log' not in st.session_state:
st.session_state.radar_log = "C:\\> 雷达系统待命... 等待频段扫描指令。"
if 'radar_captured' not in st.session_state:
st.session_state.radar_captured = None
if 'radar_type' not in st.session_state:
st.session_state.radar_type = None
r_col1, r_col2 = st.columns([1, 1.3])
with r_col1:
st.markdown(
"<div style='color:#60A5FA; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⚙️ 嗅探频段控制台</div>",
unsafe_allow_html=True)
target_channel = st.radio("选择监听频段",
["🌐 频道 A:公共海域声纳 (明文 HTTP)", "🔒 频道 B:蟹堡王专用通道 (加密 HTTPS)"],
label_visibility="collapsed")
st.markdown("<div style='margin-top:15px;'></div>", unsafe_allow_html=True)
if st.button("📡 启动深度包检测 (DPI 拦截)", use_container_width=True):
progress_bar = st.progress(0)
status_text = st.empty()
for i in range(100):
time.sleep(0.01)
progress_bar.progress(i + 1)
if i == 20:
status_text.markdown(
"<span style='color:#60A5FA; font-size:13px;'>[系统] 正在劫持海底路由节点...</span>",
unsafe_allow_html=True)
elif i == 50:
status_text.markdown(
"<span style='color:#FBBF24; font-size:13px;'>[警告] 截获目标 TCP 数据流,正在提取载荷...</span>",
unsafe_allow_html=True)
elif i == 80:
status_text.markdown(
"<span style='color:#39FF14; font-size:13px;'>[系统] 数据包提取完毕,发送至分析仪。</span>",
unsafe_allow_html=True)
status_text.empty()
progress_bar.empty()
if "明文" in target_channel:
st.session_state.radar_type = "plain"
st.session_state.radar_captured = {
"src": "192.168.1.100 (SpongeBob)", "dst": "10.0.0.5 (Mr.Krabs)",
"proto": "HTTP / TCP", "port": "80",
"hex": "e8 9f b9 e5 a0 a1 e7 8e 8b 73 65 63 72 65 74",
"payload": "蟹老板,今天的秘方进货单是:面粉50kg,海带20kg..."
}
st.session_state.radar_log += "<br>> 监听 频道 A... <span style='color:#EF4444;'>[危险] 捕获明文数据!通信已完全暴露。</span>"
else:
st.session_state.radar_type = "cipher"
st.session_state.radar_captured = {
"src": "192.168.1.100 (SpongeBob)", "dst": "10.0.0.5 (Mr.Krabs)",
"proto": "TLS v1.3 / TCP", "port": "443",
"hex": "17 03 03 00 45 d4 1d 8c d9 8f 00 b2 04 e9 80 09 98 ec f8 42 7e",
"payload": "U2FsdGVkX1+vP8Q3D9G9yK2T4Z1L0M5N6P7Q8R9S0T1U2V3W4X5Y6Z7A8B9C0="
}
st.session_state.radar_log += "<br>> 监听 频道 B... <span style='color:#39FF14;'>[安全] 捕获加密数据包,信息受保护。</span>"
st.markdown(
"<div style='color:#FBBF24; font-size:14px; margin-top:20px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⌨️ 嗅探追踪日志:</div>",
unsafe_allow_html=True)
st.markdown(
f"<div class='terminal-panel' style='height:165px;'>{st.session_state.radar_log}<br><span class='blink-text'>_</span></div>",
unsafe_allow_html=True)
with r_col2:
st.markdown(
"<div style='color:#EF4444; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>📦 截获数据包分析仪 (Packet Sniffer)</div>",
unsafe_allow_html=True)
if st.session_state.radar_captured:
cap = st.session_state.radar_captured
# 绘制极其硬核的网络封包表格
st.markdown(f"""
<div style='background-color:#0B0C10; border: 1px solid #64748B; border-radius: 8px; padding: 15px; font-family: monospace; color:#39FF14; font-size:12px;'>
<div style='border-bottom: 1px dashed #64748B; padding-bottom: 8px; margin-bottom: 8px;'>
<span style='color:#60A5FA;'>[+] Frame 1:</span> 124 bytes on wire (992 bits), 124 bytes captured<br>
<span style='color:#60A5FA;'>[+] Internet Protocol Version 4</span>, Src: {cap['src']}, Dst: {cap['dst']}<br>
<span style='color:#60A5FA;'>[+] Transmission Control Protocol</span>, Dst Port: {cap['port']}<br>
<span style='color:#60A5FA;'>[+] Protocol:</span> {cap['proto']}
</div>
<div style='color:#FBBF24;'>[Hex Dump]</div>
<div style='color:#64748B; word-break: break-all;'>0000 00 1a 2b 3c 4d 5e 6f 70 81 92 a3 b4 c5 d6 e7 f8<br>0010 {cap['hex']} ...</div>
<div style='color:#FBBF24; margin-top:8px;'>[Decoded Payload]</div>
<div style='color:#E2E8F0; font-family: "DotGothic16", sans-serif; font-size:14px;'>{cap['payload']}</div>
</div>
""", unsafe_allow_html=True)
if st.session_state.radar_type == "plain":
st.markdown("""
<div style='background-color:#1F2833; padding:15px; border-radius:10px; border:1px solid #EF4444; margin-top:15px;'>
<div style='color:#EF4444; font-weight:bold; margin-bottom:5px; font-size:14px;' class='blink-text'>🚨 安全状态:极度危险 (底裤看穿)</div>
<div style='color:#E2E8F0; font-size:13px; line-height: 1.6;'>
这是典型的<b>明文传输 (HTTP)</b>!任何经过这条线路的设备都能像读报纸一样阅读你们的对话,毫无隐私可言。痞老板狂喜!
</div>
</div>
""", unsafe_allow_html=True)
else:
st.markdown("""
<div style='background-color:#1F2833; padding:15px; border-radius:10px; border:1px solid #39FF14; margin-top:15px;'>
<div style='color:#39FF14; font-weight:bold; margin-bottom:5px; font-size:14px;'>🛡️ 安全状态:完美防御 (TLS 1.3 护航)</div>
<div style='color:#E2E8F0; font-size:13px; line-height: 1.6;'>
雷达成功抓到了数据包,但由于使用了<b>HTTPS/TLS 端到端加密</b>,载荷变成了完全随机的乱码。
</div>
</div>
""", unsafe_allow_html=True)
# 💡 高级互动:暴力破解按钮
st.markdown("<div style='margin-top:10px;'></div>", unsafe_allow_html=True)
if st.button("💀 启动量子矩阵试图暴力破解", use_container_width=True):
with st.spinner("正在调动全网算力进行密码穷举..."):
time.sleep(2)
st.error(
"❌ 破解失败!基于 256 位加密熵值,当前算力穷举完全部密钥大约需要:3.4 × 10²⁴ 年 (比宇宙的年龄还要长!)")
else:
st.markdown(
"<div style='background-color:#0B0C10; border: 1px solid #64748B; border-radius: 8px; padding: 40px; text-align:center; color:#64748B; font-family: \"DotGothic16\", sans-serif;'>等待启动深度包检测...<br>请在左侧点击雷达扫描按钮。</div>",
unsafe_allow_html=True)
# 📊 路由逻辑 8:密码强度
elif selected_nav == "密码强度":
st.markdown("<div class='cartoon-title'>📊 密码熵值评估 (Password Entropy)</div>", unsafe_allow_html=True)
# 💡 科普讲解框
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #39FF14; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #FBBF24; font-size: 15px; font-weight: bold;'>📚 新手指挥官必读:什么是“密码熵” (Entropy)?</span><br><br>
黑客破解密码最笨但也最致命的方法叫<b style='color:#EF4444;'>“暴力穷举”</b>——也就是让超级电脑把所有可能的字符组合从头到尾全猜一遍。<br>
而<b>“密码熵”</b>,就是用来衡量黑客穷举你的密码需要多长时间的科学指标。熵值(单位为 bit)越大,密码越坚不可摧!<br><br>
💡 <b style='color:#60A5FA;'>提高熵值的两大法宝</b>:<br>
1. <b>增加长度</b>:密码每增加一位,破解难度就会呈指数级爆炸式增长。<br>
2. <b>增加字符池种类</b>:混合使用大小写字母、数字和特殊符号,会让黑客的字典变得无比庞大。
</div>
""", unsafe_allow_html=True)
if 'pwd_cached_input' not in st.session_state: st.session_state.pwd_cached_input = "spongebob123"
pw_col1, pw_col2 = st.columns([1, 1.2])
with pw_col1:
st.markdown(
"<div style='color:#60A5FA; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>⌨️ 第一步:输入待测密码</div>",
unsafe_allow_html=True)
raw_password_input = st.text_input("尝试输入你的常用密码 (系统仅在本地计算,绝不上传):",
value=st.session_state.pwd_cached_input, max_chars=30)
if st.button("🔍 点击启动暴力破译模拟评估", use_container_width=True):
st.session_state.pwd_cached_input = raw_password_input
password = st.session_state.pwd_cached_input
pwd_len = len(password)
has_lower = any(c.islower() for c in password)
has_upper = any(c.isupper() for c in password)
has_digit = any(c.isdigit() for c in password)
has_symbol = any(not c.isalnum() for c in password)
pool = 0
if has_lower: pool += 26
if has_upper: pool += 26
if has_digit: pool += 10
if has_symbol: pool += 32
if pool == 0: pool = 1
entropy = pwd_len * math.log2(pool) if pwd_len > 0 else 0
st.markdown(
"<div style='margin-top:20px; color:#F472B6; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>🧬 第二步:密码结构解析</div>",
unsafe_allow_html=True)
lower_color = "#39FF14" if has_lower else "#64748B"
upper_color = "#39FF14" if has_upper else "#64748B"
digit_color = "#39FF14" if has_digit else "#64748B"
symbol_color = "#39FF14" if has_symbol else "#64748B"
st.markdown(f"""
<div style='background-color:#0B0C10; border: 1px solid #64748B; border-radius: 8px; padding: 15px; display:flex; flex-direction:column; gap:8px;'>
<div style='color:#E2E8F0; font-size:14px;'>📏 当前长度:<b style='color:#FBBF24; font-size:18px;'>{pwd_len}</b> 位</div>
<div style='color:#E2E8F0; font-size:14px;'>🧮 组合基数:每位有 <b style='color:#FBBF24; font-size:18px;'>{pool}</b> 种可能</div>
<hr style='border-top:1px dashed #64748B; margin: 5px 0;'>
<div style='color:{lower_color}; font-size:13px;'>✓ 包含小写字母 (a-z)</div>
<div style='color:{upper_color}; font-size:13px;'>✓ 包含大写字母 (A-Z)</div>
<div style='color:{digit_color}; font-size:13px;'>✓ 包含阿拉伯数字 (0-9)</div>
<div style='color:{symbol_color}; font-size:13px;'>✓ 包含特殊符号 (!@#...)</div>
</div>
""", unsafe_allow_html=True)
with pw_col2:
st.markdown(
"<div style='color:#EF4444; font-size:16px; margin-bottom:10px; font-weight:bold; font-family:\"DotGothic16\", sans-serif;'>💀 第三步:痞老板的暴力破解模拟器</div>",
unsafe_allow_html=True)
hash_rate = 1e10
total_combinations = 2 ** entropy
crack_seconds = total_combinations / hash_rate if entropy > 0 else 0
if crack_seconds < 1:
crack_time_str = "瞬间破解!(< 0.1 秒)"
safe_level, bar_color = "极度危险", "#EF4444"
bar_value = min(max(int((entropy / 100) * 100), 5), 100)
elif crack_seconds < 60:
crack_time_str = f"约 {int(crack_seconds)} 秒"
safe_level, bar_color = "极度危险", "#EF4444"
bar_value = min(max(int((entropy / 100) * 100), 10), 100)
elif crack_seconds < 3600:
crack_time_str = f"约 {int(crack_seconds / 60)} 分钟"
safe_level, bar_color = "较弱", "#F97316"
bar_value = min(max(int((entropy / 100) * 100), 20), 100)
elif crack_seconds < 86400:
crack_time_str = f"约 {int(crack_seconds / 3600)} 小时"
safe_level, bar_color = "较弱", "#F97316"
bar_value = min(max(int((entropy / 100) * 100), 30), 100)
elif crack_seconds < 31536000:
crack_time_str = f"约 {int(crack_seconds / 86400)} 天"
safe_level, bar_color = "勉强及格", "#FBBF24"
bar_value = min(max(int((entropy / 100) * 100), 50), 100)
elif crack_seconds < 3153600000:
crack_time_str = f"约 {int(crack_seconds / 31536000)} 年"
safe_level, bar_color = "安全", "#39FF14"
bar_value = min(max(int((entropy / 100) * 100), 80), 100)
else:
years = crack_seconds / 31536000
if years > 1e9:
crack_time_str = f"约 {years / 1e9:.1f} 亿年 (宇宙毁灭也算不出!)"
else:
crack_time_str = f"约 {int(years / 10000)} 万年"
safe_level, bar_color = "坚不可摧", "#06B6D4"
bar_value = 100
if pwd_len == 0:
safe_level, bar_color, crack_time_str, bar_value = "等待输入...", "#64748B", "-", 0
st.markdown(
f"<div style='color:{bar_color}; font-size:14px; margin-bottom:5px; font-weight:bold;'>安全评估评级:{safe_level}</div>",
unsafe_allow_html=True)
st.progress(bar_value)
st.markdown(f"""
<div style='background-color:#1F2833; padding:20px; border-radius:12px; border:2px solid {bar_color}; margin-top:15px;'>
<div style='color:#E2E8F0; font-size:14px; margin-bottom:15px;'>
基于当前算力 (100亿次尝试/秒),<br>强行破解该密码需要:
</div>
<div style='color:{bar_color}; font-size:28px; font-weight:bold; font-family:"DotGothic16", sans-serif; text-align:center; text-shadow: 2px 2px 4px #000000;'>
{crack_time_str}
</div>
<hr style='border-top:1px dashed #64748B; margin: 15px 0;'>
<div style='display:flex; justify-content:space-between; align-items:center;'>
<div style='color:#60A5FA; font-size:14px;'>科学熵值 (Entropy):</div>
<div style='color:#60A5FA; font-size:22px; font-weight:bold; font-family:"DotGothic16", sans-serif;'>{entropy:.1f} bits</div>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("<div style='margin-top:10px;'></div>", unsafe_allow_html=True)
if pwd_len == 0:
pass
elif entropy < 40:
st.error("🚨 太脆弱了!痞老板一按回车键就能直接拿到秘方!建议增加长度或混入符号。")
elif entropy < 60:
st.warning("⚠️ 还凑合。但如果痞老板租用了暗网的云计算集群,还是有被攻破的风险。")
else:
st.success("🛡️ 完美防御!这个密码的组合数比宇宙里的沙子还多,就算是珊迪的超级计算机也束手无策!")
# 🏆 路由逻辑 9:挑战模式
elif selected_nav == "挑战模式":
st.markdown("<div class='cartoon-title'>🏆 安全演练场 (CTF Challenge)</div>", unsafe_allow_html=True)
st.markdown("""
<div style='background-color: #1F2833; border: 2px dashed #F472B6; border-radius: 12px; padding: 15px; margin-bottom: 20px; color: #E2E8F0; font-family: "DotGothic16", sans-serif; font-size: 14px;'>
<span style='color: #F472B6; font-size: 15px; font-weight: bold;'>🚨 紧急突发:痞老板的全面入侵!</span><br><br>
指挥官,这不是演习!痞老板的黑客部队正在对控制中心发起猛烈攻击。你必须利用前面工坊中学到的<b>古典密码、哈希校验和密码学知识</b>,破解他的防御,重新夺回控制权!<br>
完成所有挑战,你将获得比基尼海滩颁发的<b>“高级密码学卫士”</b>荣誉!
</div>
""", unsafe_allow_html=True)
if 'ctf_level' not in st.session_state:
st.session_state.ctf_level = 1
st.markdown(
f"<div style='color:#FBBF24; font-size:14px; margin-bottom:5px; font-weight:bold;'>当前攻防进度:关卡 {st.session_state.ctf_level} / 4</div>",
unsafe_allow_html=True)
st.progress(st.session_state.ctf_level * 25)
st.markdown("<div style='margin-bottom:20px;'></div>", unsafe_allow_html=True)
# ⚔️ 第一关
if st.session_state.ctf_level == 1:
st.markdown(
"<div style='color:#FBBF24; font-size:20px; font-weight:bold; margin-bottom:10px; font-family:\"DotGothic16\", sans-serif;'>🔥 关卡 1/3:拦截到的密文指令</div>",
unsafe_allow_html=True)
st.markdown(
"<div style='color:#E2E8F0; font-size:14px; margin-bottom:10px;'>我们的声纳截获了痞老板发给机器人的指令。情报部门确认,他使用了最基础的<b>凯撒密码(偏移量为 3)</b>。请解密下面的内容:</div>",
unsafe_allow_html=True)
st.code("密文: SODQNWRQ", language="text")
st.markdown(
"<div style='color:#60A5FA; font-size:13px; margin-bottom:5px;'>💡 提示:字母往前倒推 3 位。输入解密后的英文单字:</div>",
unsafe_allow_html=True)
ans1 = st.text_input("明文输入", key="ans1", label_visibility="collapsed")
if st.button("⚡ 提交破解", use_container_width=True):
if ans1.strip().upper() == "PLANKTON":
st.success("✅ 破解成功!指令确实是 'PLANKTON' (痞老板)!机器人已被瘫痪。")
time.sleep(1.2)
st.session_state.ctf_level = 2
rerun_app()
else:
st.error("❌ 密码错误,机器人正在逼近!再算算看!")
# ⚔️ 第二关
elif st.session_state.ctf_level == 2:
st.markdown(
"<div style='color:#39FF14; font-size:20px; font-weight:bold; margin-bottom:10px; font-family:\"DotGothic16\", sans-serif;'>🔥 关卡 2/3:真假秘方大辨认</div>",
unsafe_allow_html=True)
st.markdown(
"<div style='color:#E2E8F0; font-size:14px; margin-bottom:10px;'>痞老板在我们的数据库里混入了假的秘方文件。安全系统提取了三个文件的 <b>SHA-256 哈希前缀</b>。已知真正秘方的哈希前缀是 <code style='color:#39FF14;'>e3b0c442</code>。请找出它!</div>",
unsafe_allow_html=True)
st.markdown(
"<div style='color:#60A5FA; font-size:13px; margin-bottom:5px;'>💡 提示:哈希校验就像核对指纹,必须完全一致。</div>",
unsafe_allow_html=True)
ans2 = st.radio("选择安全的文件:", [
"📁 档案A (Hash: a8f5f167f44...)",
"📁 档案B (Hash: e3b0c44298f...)",
"📁 档案C (Hash: 2cf24dba5fb...)"
], label_visibility="collapsed")
if st.button("⚡ 验证哈希完整性", use_container_width=True):
if "档案B" in ans2:
st.success("✅ 验证通过!雪崩效应帮我们排除了被篡改的假文件!")
time.sleep(1.2)
st.session_state.ctf_level = 3
rerun_app()
else:
st.error("🚨 警告!你选中的是包含木马的假秘方!系统受到攻击!")
# ⚔️ 第三关
elif st.session_state.ctf_level == 3:
st.markdown(
"<div style='color:#60A5FA; font-size:20px; font-weight:bold; margin-bottom:10px; font-family:\"DotGothic16\", sans-serif;'>🔥 关卡 3/3:突破主控防火墙</div>",
unsafe_allow_html=True)
st.markdown(
"<div style='color:#E2E8F0; font-size:14px; margin-bottom:10px;'>你已抵达痞老板的服务器核心。系统提示需要增加密码的<b>“熵值”</b>来防止暴力破解。根据刚才学到的密码学常识,让密码变得<b>极其难以破解(指数级增加难度)</b>的最有效、最直接的方法是什么?</div>",
unsafe_allow_html=True)
ans3 = st.radio("选择正确的密码学策略:", [
"A. 把密码写在纸上藏起来",
"B. 每天更换一次同样的弱密码",
"C. 增加密码的长度",
"D. 只使用英文字母"
], label_visibility="collapsed")
if st.button("⚡ 注入最终破解逻辑", use_container_width=True):
if "C" in ans3:
st.success("✅ 完全正确!长度增加一位,暴力破解难度呈指数级爆炸!")
time.sleep(1.2)
st.session_state.ctf_level = 4
rerun_app()
else:
st.error("❌ 策略错误!防火墙反击启动!(提示:想想密码熵值模块里的法宝)")
# 🏆 通关胜利画面
elif st.session_state.ctf_level == 4:
st.balloons() # 🎊 放礼花特效!
st.markdown("""
<div style='background-color:#0B0C10; padding:40px 20px; border-radius:15px; border:3px solid #FBBF24; text-align:center; box-shadow: 0 0 20px rgba(251, 191, 36, 0.4);'>
<div style='color:#FBBF24; font-size:42px; font-weight:bold; font-family:"ZCOOL KuaiLe", cursive; margin-bottom:15px; text-shadow: 2px 2px #EF4444;'>
🏆 终极挑战通关!
</div>
<div style='color:#39FF14; font-size:20px; margin-bottom:20px; font-weight:bold; font-family:"DotGothic16", sans-serif;'>
恭喜你,指挥官!你成功挫败了痞老板的阴谋,保护了经济命脉!
</div>
<div style='color:#E2E8F0; font-size:15px; line-height:1.8;'>
经过古典密码、现代加密、哈希校验、隐写取证等一系列实战演练,<br>
你已经完全掌握了密码学的核心防线。<br>
<b style='color:#60A5FA; font-size: 18px;'>现在,你是一名为网络安全保驾护航的“高级密码学卫士”了!</b>
</div>
</div>
""", unsafe_allow_html=True)
st.markdown("<div style='margin-top:30px;'></div>", unsafe_allow_html=True)
if st.button("🔄 重置演练场,再玩一次", use_container_width=True):
st.session_state.ctf_level = 1
rerun_app()
已托管到gitee仓库

二、项目背景
在学习 Python 之前,我曾经参加过学校的密码学科普相关活动。当时我尝试用漫画的方式展示密码学知识,这种方式比较直观,也容易吸引同学阅读。但是在后续复盘时,我发现单纯依靠漫画进行知识讲解仍然有一些不足。
例如,哈希算法的“雪崩效应”、对称加密和非对称加密的区别、HTTP 明文通信的风险、密码强度和暴力破解之间的关系,这些内容如果只靠文字或图片说明,学习者往往只能停留在“看懂了”的层面,缺少亲手操作和实时反馈。
因此,在本学期《Python 程序设计》课程结课项目中,我希望把密码学科普内容做成一个可以互动的 Web 平台。用户不只是阅读知识点,而是可以输入明文、设置密钥、观察密文变化、修改文本后对比哈希值、模拟攻击者抓包、尝试密码强度评估,并通过 CTF 闯关的方式巩固所学内容。
本项目的目标不是实现工业级密码系统,而是用 Python 语言和 Streamlit 框架搭建一个轻量级、交互式、可部署的密码学科普实验平台。通过项目开发,我既复习了 Python 基础语法,也练习了第三方库调用、Web 页面交互、状态管理、表单提交、数据可视化和云服务器部署等内容。
--
三、项目总体介绍
本项目采用 Streamlit 作为 Web 开发框架,将多个密码学科普实验整合到同一个页面应用中。系统使用顶部导航菜单切换不同功能模块,整体设计成“控制中心”的风格,让用户在模拟网络攻防情境中完成学习。
项目主要包括以下功能:
- 监控主站与态势感知大屏
- 古典密码机交互实验
- 现代加密与解密闭环演示
- SHA-256 哈希完整性校验
- 图像隐写与逆向取证演示
- 零知识证明互动实验
- 通信雷达与中间人攻击演示
- 密码强度与暴力破解时间评估
- CTF 网络攻防闯关模式
这些模块之间既相互独立,又围绕“密码学如何保护信息安全”这一主题形成完整的学习路线。用户可以先在单个实验中理解概念,再在最后的闯关模式中综合运用。
四、实验环境与技术选型
4.1 开发与部署环境
| 类型 | 环境 |
|---|---|
| 本地开发系统 | Windows 11 |
| 开发工具 | PyCharm |
| Web 框架 | Streamlit |
| 云端部署 | 华为云弹性云服务器 ECS |
| 服务器系统 | Ubuntu 22.04 LTS |
| 访问方式 | 浏览器访问服务器公网 IP 的 8501 端口 |
4.2 主要依赖库
| 依赖 | 作用 |
|---|---|
streamlit |
构建 Web 页面和交互组件 |
streamlit-option-menu |
实现顶部导航菜单 |
streamlit.components.v1 |
嵌入自定义 HTML、CSS 和 JavaScript |
pandas |
构造图表数据 |
numpy |
生成随机数、模拟随机挑战和伪随机乱码 |
base64 |
模拟加密结果的编码与传输 |
hashlib |
计算 SHA-256 哈希值 |
math |
计算密码熵和破解时间 |
time |
控制进度条和模拟等待过程 |
4.3 技术选择原因
我选择 Streamlit 的主要原因是它适合快速开发交互式 Python 应用。相比传统前后端分离项目,Streamlit 不需要手动编写复杂的路由、接口和页面通信逻辑,可以直接把 Python 计算结果展示到网页上。这对课程实践项目比较合适,能够把更多精力放在功能实现和知识展示上。
同时,Streamlit 也支持通过 components.html() 嵌入 HTML、CSS 和 JavaScript。项目中监控雷达、像素风页面样式、深色主题界面等效果,就是通过自定义前端代码完成的。这样既保留了 Python 开发的便利性,也增强了页面表现力。
五、系统结构设计
项目采用单文件结构,核心代码集中在 app.py 中。虽然整体是单文件应用,但代码内部按照功能进行了分区,主要包括以下几层:
-
全局配置层:设置页面标题、图标、布局和初始状态。

-
全局样式层:注入 CSS,统一页面背景、字体、按钮、输入框和面板样式。

-
前端组件层:封装 Three.js 动态雷达等自定义组件。

-
路由控制层:使用
option_menu实现顶部导航。

-
功能模块层:每个导航选项对应一个独立实验模块。

-
状态管理层:使用
st.session_state保存用户操作结果和闯关进度。

5.1 系统运行流程
用户首次进入页面时,系统显示项目启动界面。点击启动按钮后,st.session_state.started 被设置为 True,页面进入主系统。


进入主系统后,用户可以通过顶部菜单选择不同功能模块。每个模块内部使用 Streamlit 组件收集输入,点击按钮后触发计算逻辑,并将结果展示在页面上。对于需要保留结果的功能,例如现代加密结果、零知识证明验证次数、CTF 闯关进度等,系统使用 session_state 保存状态,避免页面刷新后立即丢失。

5.2 状态管理设计
Streamlit 的页面会在用户交互后重新运行脚本,因此如果不保存状态,很多操作结果会被刷新掉。本项目使用 st.session_state 管理以下信息:
| 状态变量 | 作用 |
|---|---|
started |
判断用户是否已经进入主系统 |
modern_result |
保存现代加密模块生成的密文 |
cipher_result |
保存古典密码加密结果 |
cipher_log |
保存古典密码模块的操作日志 |
zkp_rounds |
保存零知识证明连续验证次数 |
zkp_confidence |
保存系统对证明者身份的信任度 |
radar_captured |
保存通信雷达抓包结果 |
ctf_level |
保存 CTF 当前关卡进度 |
通过这些状态变量,项目实现了更连续的用户体验,而不是每点击一次按钮就完全回到初始状态。
六、功能模块实现
6.1 监控主站与态势感知大屏
监控主站是项目的入口页面,主要用于营造网络安全实验场景。页面左侧是动态 3D 雷达,右侧是安全状态、当前载荷、风险预警等信息,下方展示模拟网络流量图和快捷操作按钮。

这一模块的重点不是复杂计算,而是提升项目整体沉浸感。为了实现动态雷达效果,我使用 streamlit.components.v1.html() 嵌入 Three.js 代码,在浏览器端渲染粒子点阵。这样动画运行在用户浏览器中,不会占用太多服务器计算资源。
核心实现思路如下:
def render_cartoon_radar():
components.html("""
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(60, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const geometry = new THREE.BufferGeometry();
const positions = new Float32Array(350 * 3);
for (let i = 0; i < 350; i++) {
positions[i * 3] = (Math.random() - 0.5) * 15;
positions[i * 3 + 1] = (Math.random() - 0.5) * 15;
positions[i * 3 + 2] = (Math.random() - 0.5) * 15;
}
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
const material = new THREE.PointsMaterial({ color: 0x45F3FF, size: 0.12 });
const particles = new THREE.Points(geometry, material);
scene.add(particles);
function animate() {
requestAnimationFrame(animate);
particles.rotation.y += 0.002;
particles.rotation.x += 0.001;
renderer.render(scene, camera);
}
animate();
</script>
""", height=350)
通过这个模块,我学习到 Streamlit 不只能做简单表单,也可以和前端技术结合,做出更有表现力的交互页面。
6.2 古典密码机交互实验
古典密码模块实现了凯撒密码和维吉尼亚密码。用户可以输入明文,选择加密方式,并设置对应密钥。系统会输出加密后的密文,同时在日志区域显示操作过程。

凯撒密码的基本思路是对英文字母进行固定偏移。例如偏移量为 3 时,A 会变成 D,B 会变成 E。在实现时,我只对英文字母进行偏移处理,对中文、数字和标点符号保留原样。这样可以避免中文输入导致程序报错或输出混乱。
result = ""
for char in user_input:
if char.isalpha():
ascii_offset = 65 if char.isupper() else 97
result += chr((ord(char) - ascii_offset + shift) % 26 + ascii_offset)
else:
result += char
维吉尼亚密码则使用一个英文单词作为密钥,根据密钥中每个字母的位置决定偏移量。与凯撒密码相比,它不再使用固定偏移,而是让不同位置的字母使用不同偏移量,因此比简单凯撒密码更难直接破解。
这个模块帮助我巩固了字符串遍历、条件判断、ASCII 编码转换、异常处理等 Python 基础知识。
6.3 现代加密与解密闭环演示
现代加密模块设计成“发送端”和“接收端”两个部分。发送端输入明文和密钥,系统生成密文;接收端输入密文和密钥,系统尝试还原明文。

在课程实践项目中,我没有实现真正完整的 AES 或 RSA 底层数学算法,而是使用 Base64 编码模拟“加密后不可直接阅读”的效果。程序会把密钥和明文组合后编码,接收端解码后检查密钥是否一致。如果密钥正确,就显示原文;如果密钥错误,就生成一串随机乱码。
combined = f"{m_key}_||_{m_input}"
encoded = base64.b64encode(combined.encode()).decode()
st.session_state.modern_result = f"AES_ENC_{encoded}"
接收端解密时,程序会检查用户输入的密钥:
decoded_combined = base64.b64decode(raw_encoded).decode()
original_key, original_msg = decoded_combined.split("_||_", 1)
if dec_key == original_key:
st.success("密钥匹配成功")
st.info(original_msg)
else:
fake_garbage = "".join(np.random.choice(list("!@#$%^&*()_+<>?[]{}"), 30))
st.code(fake_garbage, language="text")
这一设计虽然不是工业级加密,但非常适合科普展示。用户可以清楚看到:密钥正确时才能还原明文,密钥错误时得到的内容没有意义。这样能够直观说明密钥在加密通信中的重要性。
6.4 哈希完整性校验
哈希校验模块用于演示 SHA-256 算法和“雪崩效应”。系统先给出一段原始文本,并计算它的 SHA-256 哈希值。用户可以修改文本中的任意字符,再次计算哈希值并与原始哈希对比。

import hashlib
original_hash = hashlib.sha256(original_text.encode("utf-8")).hexdigest()
current_hash = hashlib.sha256(user_text_input.encode("utf-8")).hexdigest()
只要文本发生一点变化,新的哈希值就会和原哈希明显不同。这个现象可以帮助用户理解为什么哈希常用于文件完整性校验、数字签名和防篡改检测。
这个模块的实现比较简洁,但科普效果很好。因为用户亲自修改一个标点或一个字后,会立刻看到哈希值发生巨大变化,比单纯解释“雪崩效应”更直观。
6.5 图像隐写与逆向取证
图像隐写模块用于演示信息隐藏思想。项目中将它设计成“注入端”和“提取端”两个部分。注入端用于把文字信息隐藏到图片中,提取端用于从隐写图片中还原隐藏内容。
本模块使用的是 LSB 隐写思想。LSB 指最低有效位,即图片像素颜色值中影响最小的一位。通过修改这些最低位,可以在肉眼几乎看不出差异的情况下隐藏少量文本信息。

为了增强交互感,提取过程加入了进度条和状态提示:
progress_bar = st.progress(0)
status_text = st.empty()
for i in range(100):
time.sleep(0.012)
progress_bar.progress(i + 1)
if i == 20:
status_text.markdown("正在扫描 RGB 通道...")
elif i == 50:
status_text.markdown("检测到异常 LSB 偏移...")
elif i == 80:
status_text.markdown("正在拼装明文数据...")
通过这个模块,用户可以理解信息安全不只包括“加密”,还包括“隐藏”。隐写并不是让信息变成乱码,而是让别人不知道信息存在。
6.6 零知识证明互动实验
零知识证明是比较抽象的密码学概念。如果直接讲数学证明,普通学习者很难理解。因此我采用“阿里巴巴洞穴”模型进行演示。
在这个模型中,证明者声称自己知道打开洞穴中间暗门的密码。验证者不需要知道密码内容,只需要随机指定证明者从 A 出口或 B 出口出来。如果证明者真的知道密码,就总能按要求从指定出口出来;如果不知道密码,只能靠猜,连续通过多轮挑战的概率会越来越低。

代码中使用随机数模拟验证者的随机挑战:
target_exit = np.random.choice(["A出口", "B出口"])
prob_guessing = (0.5) ** st.session_state.zkp_rounds
st.session_state.zkp_confidence = (1 - prob_guessing) * 100
当验证次数增加时,系统对证明者身份的信任度逐渐提升。这个过程展示了零知识证明的核心思想:证明者能够证明“我知道秘密”,但不需要把秘密本身告诉验证者。
6.7 通信雷达与中间人攻击演示
通信雷达模块模拟网络抓包过程,用来对比 HTTP 明文通信和 HTTPS 加密通信的差别。
当用户选择 HTTP 明文通道时,系统显示攻击者可以直接看到通信内容;当用户选择 HTTPS 加密通道时,系统只显示类似密文或编码后的字符串。通过这种对比,用户能够直观看到加密通信的重要性。

if "明文" in target_channel:
st.session_state.radar_captured = {
"proto": "HTTP / TCP",
"port": "80",
"payload": "明文聊天内容..."
}
else:
st.session_state.radar_captured = {
"proto": "TLS v1.3 / TCP",
"port": "443",
"payload": "U2FsdGVkX1+vP8Q3D9G9yK2T4Z1L0M5N6P7Q8R9S0T1..."
}
这个模块的重点是帮助用户建立安全意识:在公共网络环境下,明文传输存在被监听和篡改的风险,而加密通道可以有效保护传输内容。
6.8 密码强度与暴力破解评估
密码强度模块根据用户输入的密码,计算其长度、字符种类、组合空间和信息熵,再估算暴力破解所需时间。
程序会判断密码是否包含小写字母、大写字母、数字和特殊符号,并据此估算字符池大小:

pool = 0
if any(c.islower() for c in password):
pool += 26
if any(c.isupper() for c in password):
pool += 26
if any(c.isdigit() for c in password):
pool += 10
if any(not c.isalnum() for c in password):
pool += 32
entropy = len(password) * math.log2(pool) if len(password) > 0 else 0
然后,系统假设攻击者每秒可以尝试 10^10 次,根据组合空间估算破解时间:
hash_rate = 1e10
total_combinations = 2 ** entropy
crack_seconds = total_combinations / hash_rate if entropy > 0 else 0
这个模块让我对“密码强度”有了更具体的理解。强密码并不只是看起来复杂,更重要的是长度足够、字符组合空间足够大,能够让暴力破解成本变得非常高。
6.9 CTF 网络攻防闯关模式
CTF 模块是整个项目的综合练习部分。它把前面学到的知识设计成连续关卡,用户需要依次完成挑战才能通关。
当前关卡主要包括:
- 凯撒密码解密:根据偏移量还原密文。
- 哈希指纹匹配:根据 SHA-256 前缀选择正确文件。
- 密码强度提升:选择正确的密码安全策略。
- 通关反馈:完成后显示胜利页面并播放 Streamlit 气球动画。

关卡进度通过 st.session_state.ctf_level 保存:
if "ctf_level" not in st.session_state:
st.session_state.ctf_level = 1
st.progress(st.session_state.ctf_level * 25)
当用户答对题目后,系统更新关卡进度并重新运行页面:
if ans1.strip().upper() == "PLANKTON":
st.session_state.ctf_level = 2
rerun_app()
这个模块把知识讲解变成了实践挑战,增强了项目的趣味性,也让前面的功能不只是单独展示,而是形成了完整的学习闭环。
七、开发过程中遇到的问题与解决方法
7.1 深色背景下文字显示不清
项目采用深色像素风界面,但 Streamlit 原生组件在不同主题下可能会使用默认文字颜色,导致部分文本在深色背景下看不清。


解决方法是在全局 CSS 中统一设置段落、标签、输入框、上传框等组件的字体颜色,并对按钮、文本框和下拉菜单单独设置背景色和边框色。这样可以保证页面在不同浏览器环境下显示更稳定。
7.2 页面频繁刷新导致交互体验不好
Streamlit 的运行机制是用户每次交互都会重新执行脚本。如果文本框直接绑定计算逻辑,用户输入过程中页面可能频繁刷新,影响体验。

解决方法是将部分输入区域放入表单或按钮触发逻辑中,让用户点击提交后再统一计算。这样既减少了无意义刷新,也降低了云端部署时的渲染压力。
7.3 云服务器部署后出现渲染异常
项目部署到云服务器后,部分页面在普通浏览器模式下偶尔出现渲染异常,而无痕模式下正常。经过排查,问题可能与浏览器插件、翻译插件或深色模式插件修改 DOM 结构有关。


解决方法是尽量减少对原生 DOM 结构的依赖,并建议访问者使用无痕模式或关闭会修改网页结构的插件。同时,在代码中对样式进行了更强的覆盖,减少外部插件造成的影响。
7.4 中文输入和特殊字符处理问题
古典密码算法主要针对英文字母设计。如果直接对所有字符进行 ASCII 偏移,中文和特殊符号可能出现乱码或异常。
解决方法是在加密逻辑中判断字符类型,只对英文字母进行加密,对中文、数字、空格和标点符号保持不变。这样既符合古典密码的算法特点,也提升了程序的容错能力。
7.5 页面样式和代码维护问题
项目后期加入了较多自定义 CSS 和 HTML,页面效果更丰富,但代码也变得更长。如果随意修改,容易造成缩进错误、标签缺失或样式覆盖异常。

解决方法是将代码按功能分区,并使用清晰的注释划分模块。例如全局样式、雷达组件、主站页面、古典密码、哈希校验等部分分别放置。这样后续修改时更容易定位问题。
八、项目亮点
8.1 将抽象知识转化为可操作实验
密码学知识往往比较抽象,尤其是哈希、零知识证明、密码熵等概念。如果只靠文字解释,理解门槛较高。本项目把这些概念做成可以操作的实验,让用户通过输入、点击、对比结果来理解原理。
8.2 用故事化场景增强学习兴趣
项目没有直接做成普通工具箱,而是设计成一个网络安全控制中心。用户在页面中像是在执行一次安全防御任务,这种场景化设计能够降低学习的枯燥感。
8.3 形成从学习到闯关的闭环
前面的模块负责讲解和演示,最后的 CTF 模块负责检测和巩固。用户不是看完就结束,而是需要用前面学到的知识完成挑战。这种设计让项目更像一个完整的课程实践作品。
8.4 综合运用了多种 Python 能力
项目涉及字符串处理、条件判断、异常捕获、随机数生成、哈希计算、数学计算、数据可视化、状态管理和 Web 页面渲染等内容。它不是单一算法练习,而是对本学期 Python 学习成果的一次综合应用。
8.5 部署到公网环境
项目不只在本地运行,还部署到了华为云服务器中,用户可以通过浏览器访问。这一过程让我接触到真实项目上线时会遇到的问题,例如端口访问、浏览器兼容、页面性能和插件干扰等。

ip公网地址:121.36.108.119
Streamlit 默认运行端口:8501
最终访问地址:http://121.36.108.119:8501
九、不足与改进方向
9.1 加密算法深度还可以继续提升
目前现代加密模块主要使用 Base64 编码模拟加密过程,并没有真正实现 AES、RSA 等算法的底层逻辑。后续可以引入 cryptography 等专业库,实现更真实的密钥生成、加密、解密和签名验证。
9.2 图像隐写功能还可以更完整
当前隐写模块主要围绕 LSB 思想进行科普展示,适合 PNG 等无损图片。对于 JPG 这类有损压缩格式,隐藏信息可能会被破坏。后续可以继续研究基于频域的隐写方法,提高抗压缩能力。
9.3 数据无法长期保存
项目主要依赖 session_state 保存临时状态,刷新页面或重启服务后数据会丢失。后续可以接入 SQLite 数据库,保存用户闯关记录、实验日志和排行榜。
9.4 移动端适配仍需优化
当前界面主要面向电脑宽屏设计,部分复杂面板在手机端显示不够理想。后续可以进一步优化响应式布局,让模块在小屏幕上也能清晰展示。
9.5 代码结构可以进一步拆分
目前项目为了方便课程提交,采用单文件结构。随着功能增加,app.py 文件会越来越长。后续可以将不同模块拆分成多个 Python 文件,例如 pages、utils、components 等目录,使代码结构更清楚。
十、实践收获
通过本次课程实践,我完成了一个基于 Python 的交互式密码学科普平台。这个项目从最初的想法,到本地开发,再到云服务器部署,经历了完整的开发流程。
在技术方面,我更加熟悉了 Python 的字符串处理、哈希计算、随机模拟、数学计算和第三方库使用方法。同时,我也学习了 Streamlit 的页面组件、状态管理和自定义 HTML 嵌入方式。以前我对 Web 应用的理解更多停留在“网页展示”层面,这次项目让我真正体会到前端交互和后端计算之间的联系。
在调试方面,我遇到了文字颜色不清、中文处理、页面刷新、云端渲染异常、浏览器插件干扰等问题。这些问题并不完全来自语法错误,而是来自真实运行环境。解决这些问题的过程让我认识到,写程序不仅要让代码能运行,还要让用户在不同环境下都能比较顺利地使用。
在内容设计方面,我也意识到科普项目不能只追求功能数量,还要考虑用户是否容易理解。比如零知识证明如果直接讲定义会很抽象,但换成洞穴挑战模型后,用户更容易明白其中的逻辑。哈希算法如果只写公式也不直观,但让用户亲手修改一个字符,就能马上看到哈希值变化。
总的来说,这次项目让我把 Python 知识和密码学科普结合了起来,也让我体会到编程是一种把想法变成真实作品的工具。虽然项目还有很多可以改进的地方,但它已经实现了我最初的目标:让密码学知识不只停留在文字中,而是可以被看见、被操作、被体验。
十一、课程总结
通过这一学期的Python课程学习,我经历了一次从对编程懵懂无知,到初步掌握代码逻辑的蜕变。从一开始面对黑色终端的不知所措,到现在能够独立阅读并编写出具有基础功能的Python程序,我对编程的核心思想有了更深刻的体会。
课程的推进非常系统。最开始,我们从Python的语言特点、环境搭建、变量、数据类型以及严格的缩进格式学起。这让我意识到,写代码就像在学习一门极其严谨的外语。紧接着学习的输入输出(input/print)、条件判断(if/else)和循环控制(for/while),让我第一次体会到了程序的魅力——只要把人类的逻辑拆解得足够清晰,计算机就能任劳任怨地替我们完成那些繁琐的重复性工作。
在掌握了基础语法后,课程引入了列表、元组、字典等核心数据结构。刚接触时,我常常弄混它们的具体使用场景,但通过不断的敲代码练习,我逐渐明白了列表适合管理有序数据,而字典则是用“键值对”精准查找信息的利器。函数部分更是让我豁然开朗,我懂得了如何将重复繁杂的代码进行“封装”,这不仅让代码变得整洁易读,也培养了我模块化解决问题的思路。
课程后半段的文件操作、模块使用和Socket网络编程,进一步打开了我的视野。文件操作让程序有了“记忆”,数据不再是运行完就消失;模块的学习让我懂得“站在巨人的肩膀上”去利用现成的工具;而Socket网络编程虽然有些烧脑,但它让我真切地感受到了客户端与服务器是如何跨越网络进行通信的,让我明白Python绝不仅仅局限于本地的简单计算。
除了硬核的技术知识,贯穿整个学期的Git代码托管和博客撰写任务也让我受益匪浅。Git让我学会了现代程序员必备的版本控制技能,看着代码被妥善托管,心里多了一份踏实;而写博客则逼着我将碎片化的知识进行系统性梳理。用自己的话把知识重述一遍,不仅是极好的复习,更帮我沉淀了学习成果。
总的来说,这门课教给我的不仅是Python语法,更是一种“分析问题、拆解步骤、代码实现、调试排错”的工程师思维。面对满屏幕刺眼的红色报错信息(Bug),我从最初的焦虑烦躁慢慢变得平和,学会了耐心阅读错误提示去定位问题。未来,我会继续多敲代码、多加练习,把这学期打下的基础真正转化为自己解决问题的能力。
十二、课程建议
结合这学期的学习体验,对于课程的后续优化,我有以下几点建议:
- 引入贴近实际应用场景的自动化小案例:
目前的练习偏向于基础语法的巩固。如果能在课程中穿插几个简单的自动化办公案例(例如:使用Python批量重命名本地文件、读取和处理简单的Excel/CSV文件信息),会让初学者更直观地感受到Python在日常生活中的强大威力,从而极大提升学习的成就感和兴趣。 - 增加主流第三方库的拓展与科普:
Python最大的优势在于其庞大丰富的生态圈。建议在课程末尾,适当花一点时间做个“前沿科普”,走马观花地向大家展示一下Pandas(数据分析)或Pygame(小游戏)等热门第三方库能做些什么。即使不深入教学,也能为同学们期末后的自主学习指明方向。 - 增加小组合作项目:
在平时的实验或者实践中,可以分小组实验,增强合作。
浙公网安备 33010602011771号