从零实现安全通信协议:对称与非对称加密、密钥交换及防重放实战
1. 安全协议到底在解决什么问题1.1 从一个真实踩坑案例说起去年帮一个做物联网设备的朋友排查问题他们的设备在出厂测试阶段一切正常到了客户现场却频繁出现指令被篡改的情况。抓包一看设备与网关之间的通信完全是明文传输任何人拿到通信链路上的数据都能随意修改。这就是典型的“没有安全协议兜底”的场景。安全协议本质上是一套通信双方约定好的规则集合它规定了数据怎么封装、怎么验证身份、怎么保证不被篡改、怎么防止重放。你可以把它理解成两个人之间约定的暗号体系——不仅要对上暗号还要确保暗号本身没有被第三方偷看或伪造。很多人觉得安全协议离自己很远觉得那是安全工程师才需要关心的事。但实际情况是只要你写的代码涉及网络通信无论是HTTP接口调用、MQTT消息推送、还是WebSocket实时通信底层都离不开安全协议的支撑。区别只在于有些是框架帮你封装好了有些需要你自己动手实现。1.2 安全协议的三大核心目标不管具体是哪种安全协议它们都在围绕三个核心目标做文章机密性——确保数据在传输过程中不被未授权的第三方读取。实现手段主要是加密把明文变成密文。这里有个常见误区很多人以为加密就是“加个密码”实际上现代加密体系分为对称加密和非对称加密两大类各有各的适用场景。完整性——确保数据在传输过程中没有被篡改。注意完整性不等于机密性。你可以让数据公开可见但必须保证它没有被改动过。实现手段主要是消息认证码和数字签名。身份认证——确保通信对方确实是它声称的那个身份。没有身份认证前面两个目标都是空中楼阁因为你根本不知道自己在跟谁通信。这三个目标不是孤立的实际的安全协议往往需要同时满足多个目标。比如TLS协议就同时实现了机密性、完整性和身份认证这也是它能成为互联网基础设施的原因。1.3 谁需要了解安全协议如果你符合以下任何一种情况安全协议的知识对你来说就是刚需做后端开发需要设计API接口的认证和加密方案做物联网开发设备与云端之间的通信安全需要自己保障做移动端开发本地数据存储和网络请求都涉及安全协议做运维需要配置和管理HTTPS、SSH等安全通道做安全测试需要理解协议层面的攻击面和防御手段即使你只是普通开发者理解安全协议的基本原理也能帮你在日常工作中做出更合理的架构决策。比如什么时候该用HTTPS、什么时候需要额外的应用层加密、Token该怎么设计才安全这些问题的答案都藏在安全协议的知识体系里。2. 主流安全协议的核心机制拆解2.1 对称加密与非对称加密的配合逻辑安全协议中最基础的构件是加密算法。对称加密如AES的特点是加密和解密用同一个密钥速度快适合加密大量数据。非对称加密如RSA、ECC的特点是公钥加密私钥解密速度慢但解决了密钥分发的问题。实际的安全协议不会只用其中一种而是把两者结合起来。典型的做法是用非对称加密来协商一个临时的对称密钥然后用这个对称密钥来加密实际传输的数据。这样既解决了密钥分发问题又保证了数据传输效率。注意非对称加密的性能开销比对称加密高出几个数量级。实测下来RSA-2048的加密速度大约只有AES-128的千分之一。所以千万不要用非对称加密去加密大文件或大量数据。这个“混合加密”的思路是TLS、SSH、IPSec等主流安全协议的共同基础。理解了这个思路再看具体协议的设计就会清晰很多。2.2 消息认证码与数字签名的区别消息认证码MAC和数字签名都用于验证数据的完整性但它们的适用场景不同。MAC使用对称密钥通信双方共享同一个密钥。发送方用密钥对消息计算出一个认证码接收方用同样的密钥验证。优点是计算速度快缺点是无法向第三方证明消息的来源——因为双方都有密钥理论上接收方自己也能伪造。数字签名使用非对称密钥发送方用私钥签名接收方用公钥验证。优点是具有不可否认性发送方无法抵赖。缺点是计算速度慢签名长度也更长。在实际协议设计中如果只需要通信双方互相验证MAC就够了。如果需要向第三方证明比如电子合同、区块链交易就必须用数字签名。2.3 密钥交换的几种典型方案密钥交换是安全协议中最精妙的部分。通信双方要在不安全的信道上协商出一个只有它们知道的密钥同时还要防止中间人攻击。基于非对称加密的密钥交换发送方用接收方的公钥加密一个临时密钥接收方用自己的私钥解密。这种方案简单直接但有一个前提——发送方必须确认拿到的公钥确实是接收方的而不是中间人替换过的。基于Diffie-Hellman的密钥交换双方各自生成一个临时密钥对交换公钥后各自计算出相同的共享密钥。这种方案的好处是即使长期私钥泄露之前的通信记录也不会被解密前向安全性。TLS 1.3强制使用这种方案。基于预共享密钥的密钥交换双方事先约定一个共享密钥直接用于通信加密。这种方案最简单但扩展性差适合设备数量固定的场景。选择哪种方案取决于你的具体需求。如果追求前向安全性Diffie-Hellman系列是首选。如果设备资源极其有限预共享密钥可能是唯一可行的选择。3. 从零实现一个简化版安全通信协议3.1 整体架构设计为了把前面讲的原理串起来我们设计一个简化版的安全通信协议。这个协议要满足三个目标机密性、完整性、身份认证。为了便于理解我们基于Python来实现使用标准库中的密码学原语。协议的整体流程分为三个阶段握手阶段双方交换公钥验证身份协商会话密钥数据传输阶段使用会话密钥加密和认证数据会话结束阶段清理密钥材料这个设计参考了TLS的核心思路但做了大幅简化便于理解和复现。3.2 握手阶段的实现细节握手阶段是整个协议最关键的部分。我们采用以下步骤import os import hashlib from cryptography.hazmat.primitives.asymmetric import ec from cryptography.hazmat.primitives.kdf.hkdf import HKDF from cryptography.hazmat.primitives import hashes, serialization # 第一步双方各自生成临时密钥对 def generate_keypair(): private_key ec.generate_private_key(ec.SECP256R1()) public_key private_key.public_key() return private_key, public_key # 第二步交换公钥后各自计算共享密钥 def derive_shared_secret(private_key, peer_public_key): shared_secret private_key.exchange(ec.ECDH(), peer_public_key) # 使用HKDF从共享密钥派生出会话密钥 derived_key HKDF( algorithmhashes.SHA256(), length32, saltNone, infobhandshake data, ).derive(shared_secret) return derived_key这里用的是ECDH密钥交换基于SECP256R1曲线。选择这条曲线的原因是它在安全性和性能之间取得了很好的平衡而且被广泛支持。实操心得临时密钥对必须每次会话重新生成绝对不能复用。复用临时密钥会破坏前向安全性攻击者一旦拿到长期私钥就能解密所有历史通信。3.3 数据传输阶段的加密与认证握手完成后双方有了相同的会话密钥。接下来用这个密钥来加密和认证数据。from cryptography.hazmat.primitives.ciphers.aead import AESGCM def encrypt_message(session_key, plaintext, associated_datab): # 每次加密生成随机nonce nonce os.urandom(12) aesgcm AESGCM(session_key) ciphertext aesgcm.encrypt(nonce, plaintext, associated_data) return nonce ciphertext def decrypt_message(session_key, encrypted_data, associated_datab): nonce encrypted_data[:12] ciphertext encrypted_data[12:] aesgcm AESGCM(session_key) plaintext aesgcm.decrypt(nonce, ciphertext, associated_data) return plaintext这里用的是AES-GCM模式它同时提供了加密和认证功能。GCM模式会生成一个认证标签接收方验证标签通过后才解密数据。如果数据被篡改解密会直接失败。选择AES-GCM而不是AES-CBC加HMAC的组合是因为GCM模式更简洁不容易出错。CBC模式需要自己处理填充和MAC实现不当容易引入安全漏洞。3.4 防重放攻击的处理上面的实现还有一个漏洞攻击者可以截获加密后的数据包然后原封不动地重放给接收方。接收方解密后发现内容合法就会执行相应操作。如果这个操作是“转账100元”后果就很严重了。防重放的标准做法是在协议中加入序列号或时间戳。我们选择序列号方案class SecureSession: def __init__(self, session_key): self.session_key session_key self.send_counter 0 self.recv_counter 0 def encrypt(self, plaintext): # 将序列号作为附加认证数据 aad self.send_counter.to_bytes(8, big) encrypted encrypt_message(self.session_key, plaintext, aad) self.send_counter 1 return encrypted def decrypt(self, encrypted_data): aad self.recv_counter.to_bytes(8, big) plaintext decrypt_message(self.session_key, encrypted_data, aad) self.recv_counter 1 return plaintext序列号作为附加认证数据参与认证但不加密。接收方验证序列号是否连续如果不连续就拒绝。这样攻击者重放旧数据包时序列号对不上接收方会直接丢弃。注意序列号必须参与认证否则攻击者可以篡改序列号来绕过检查。把序列号作为AAD传入GCM是最简洁的做法。4. 实际部署中的常见问题与排查技巧4.1 证书验证失败的排查思路在实际部署中证书问题是最常见的故障来源。以下是一个快速排查的检查清单问题现象可能原因排查方法握手失败提示证书无效证书过期检查证书有效期握手失败提示域名不匹配证书CN/SAN与访问域名不一致核对证书中的域名列表握手失败提示证书链不完整中间证书缺失用openssl s_client检查证书链部分客户端成功部分失败客户端信任库差异检查客户端的根证书列表排查证书问题时openssl s_client -connect host:port -showcerts是最常用的命令。它会打印出服务器返回的完整证书链一眼就能看出问题所在。4.2 性能瓶颈的定位与优化安全协议带来的性能开销主要来自三个方面握手阶段的非对称加密运算、数据传输阶段的对称加密运算、以及证书验证。握手阶段的优化手段包括启用会话复用TLS session resumption、使用更快的椭圆曲线如X25519、部署会话票据session ticket。实测下来启用会话复用后重复连接的握手时间可以从几十毫秒降到几毫秒。数据传输阶段的优化主要是选择合适的加密算法。AES-NI指令集在现代CPU上可以把AES加密速度提升到GB/s级别基本不会成为瓶颈。如果硬件不支持AES-NI可以考虑ChaCha20-Poly1305它在纯软件实现下性能更好。4.3 密钥管理的常见坑密钥管理是安全协议中最容易被忽视的环节。我见过太多项目把密钥硬编码在代码里、提交到代码仓库、或者用弱随机数生成密钥。密钥生成必须使用密码学安全的随机数生成器。Python中用os.urandom()或secrets模块不要用random模块。密钥存储生产环境的密钥应该放在密钥管理服务中或者至少加密后存储在配置文件里。绝对不要硬编码在源码中。密钥轮换长期使用同一个密钥会增加泄露风险。建议定期轮换密钥轮换周期根据数据敏感程度决定一般不超过90天。密钥销毁会话结束后会话密钥应该立即从内存中清除。Python中可以用del删除引用但要注意Python的垃圾回收机制不保证立即清除内存。对安全性要求极高的场景可以考虑用ctypes手动覆写内存。4.4 协议版本选择与兼容性处理不同版本的协议在安全性和兼容性上有不同的取舍。以TLS为例TLS 1.0/1.1已被主流浏览器标记为不安全应该禁用TLS 1.2是目前兼容性最好的版本支持广泛TLS 1.3安全性最高握手更快但部分老旧客户端不支持实际部署时建议服务端同时支持TLS 1.2和TLS 1.3让客户端协商决定。如果客户端群体可控比如内部系统可以直接只启用TLS 1.3。实操心得在升级协议版本时先用灰度发布的方式观察一段时间。我遇到过升级TLS 1.3后部分老旧设备无法连接的情况原因是这些设备的固件太老不支持新的加密套件。灰度发布可以让你在影响面扩大之前发现问题。5. 安全协议的攻防视角5.1 中间人攻击的防御要点中间人攻击是安全协议面临的最经典威胁。攻击者插入到通信双方之间分别与双方建立独立的加密通道然后转发甚至修改数据。防御中间人攻击的核心是身份认证。通信双方必须能够验证对方的身份而不是盲目信任拿到的公钥。在TLS中这通过证书链来实现——客户端验证服务器证书是否由受信任的CA签发。但证书体系本身也有弱点。如果CA被攻破或者客户端信任了恶意的根证书中间人攻击就能成功。所以高安全场景下通常会加上证书固定certificate pinning——客户端只信任特定的证书或公钥而不是整个CA体系。5.2 降级攻击与版本协商的安全设计降级攻击是指攻击者篡改握手过程中的版本协商信息迫使双方使用较弱的协议版本或加密算法。防御降级攻击的标准做法是在握手消息中加入版本相关的认证信息。TLS 1.3的做法是把所有握手消息的哈希值纳入密钥派生过程任何篡改都会导致密钥不一致握手失败。在自定义协议中同样的思路也适用把协商结果作为密钥派生的输入参数这样攻击者篡改协商结果就会导致双方派生出不同的密钥通信自然失败。5.3 侧信道攻击的防范思路侧信道攻击不直接破解加密算法而是通过观察加密操作的物理特征如执行时间、功耗、电磁辐射来推断密钥信息。timing attack是最常见的侧信道攻击。比如如果字符串比较函数在遇到第一个不匹配的字符时就返回攻击者可以通过测量比较时间来逐字节猜测正确的值。防御方法是使用常量时间比较。Python中可以用hmac.compare_digest()来安全地比较两个字节串。这个函数无论输入内容如何执行时间都是固定的。import hmac def secure_compare(a, b): return hmac.compare_digest(a, b)注意不要自己实现常量时间比较函数很容易出错。直接用标准库提供的hmac.compare_digest()。5.4 前向安全性的实际意义前向安全性Forward Secrecy是指即使长期密钥泄露攻击者也无法解密之前的通信记录。实现前向安全性的关键是每次会话使用独立的临时密钥且临时密钥在使用后立即销毁。这样即使攻击者拿到了服务器的长期私钥也无法推导出历史会话的临时密钥。TLS 1.3强制要求前向安全性所有密钥交换都必须使用临时密钥。TLS 1.2中需要显式配置ECDHE系列加密套件才能启用前向安全性。在实际部署中前向安全性会增加服务器的CPU开销因为每次握手都要做椭圆曲线运算但这个开销在现代硬件上完全可以接受。除非你的设备资源极其有限否则都应该启用前向安全性。6. 不同场景下的安全协议选型建议6.1 Web应用场景Web应用的标准方案是HTTPS底层就是TLS。配置要点包括使用TLS 1.2及以上版本、启用HSTS、配置合理的加密套件优先级、开启OCSP Stapling。对于API接口除了传输层的TLS通常还需要应用层的认证机制如OAuth 2.0或JWT。这两层安全机制是互补的不能互相替代。6.2 物联网设备场景物联网设备的特点是计算资源有限、网络环境复杂、部署数量大。安全协议的选型需要特别考虑资源消耗。轻量级的方案包括DTLS基于UDP的TLS、MQTT over TLS、以及专门为物联网设计的OSCORE协议。如果设备资源极其有限可以考虑使用预共享密钥模式省去非对称加密的开销。实操心得物联网设备的安全更新是个大问题。很多设备部署后就没有更新机制一旦协议出现漏洞就无法修复。所以在选型时一定要考虑协议的可持续更新能力。6.3 内部服务间通信场景微服务架构下服务之间的通信安全同样重要。常见的方案包括mTLS双向TLS、服务网格如Istio自动注入mTLS、以及基于JWT的服务间认证。mTLS的优势是双向认证服务端和客户端都要验证对方的证书。这在零信任架构中特别重要——不信任网络边界每个请求都要验证。6.4 离线或弱网环境场景在弱网或离线环境下TLS握手可能会因为超时而失败。优化手段包括启用会话复用、使用0-RTT模式TLS 1.3、或者使用基于UDP的QUIC协议。QUIC协议把TLS 1.3集成到了传输层减少了握手往返次数。在弱网环境下QUIC的连接建立速度明显优于TCPTLS的组合。安全协议的选型没有银弹需要根据具体场景在安全性、性能、兼容性之间做权衡。理解每种协议的设计取舍才能做出合理的决策。

相关新闻

Unity安卓串口通信实战:USB Host桥接实现手机直连单片机

Unity安卓串口通信实战:USB Host桥接实现手机直连单片机

简介:一份面向 Unity 开发者的安卓串口收发代码包,用于解决 Unity 应用与 Arduino、树莓派等外部硬件设备的数据交互问题。压缩包共 30 个文件,大小约 124KB,包含 JNI 桥接相关的 C/C 源文件(.c/.h)、Andro…

2026/10/17 18:25:55 阅读更多 →
LiteVGGT实测:轻量Transformer实现10倍加速视觉定位与三维重建

LiteVGGT实测:轻量Transformer实现10倍加速视觉定位与三维重建

做视觉定位和三维重建的朋友,这两年应该没少被VGGT刷屏。它把多视角图像的相机位姿估计、内参预测和3D点生成全部塞进一个端到端Transformer,一个前向网络跑完,场景结构直接出来,省掉了特征匹配、RANSAC、全局优化这一整套传统SfM…

2026/10/17 18:25:55 阅读更多 →
Linux 上安装 JDK 的三种方式与环境变量配置、多版本切换指南

Linux 上安装 JDK 的三种方式与环境变量配置、多版本切换指南

1. 装之前先想清楚:系统、版本和需求怎么定把JDK装到Linux上这件事,很多人第一反应就是网上搜命令复制粘贴,装完java -version能输出版本号就觉得完事了。但实际接触过几台服务器、几个项目之后你会明白,安装本身五分钟不到&#…

2026/10/17 18:24:54 阅读更多 →

最新新闻

2026本溪软件检测第三方公司排行榜TOP5:软件检测+功能性能测试+信息安全测评+软件验收报告 机构地址及联系方式推荐

2026本溪软件检测第三方公司排行榜TOP5:软件检测+功能性能测试+信息安全测评+软件验收报告 机构地址及联系方式推荐

本溪软件检测市场近年日渐繁荣,机构鳞次栉比、良莠并存。街巷楼宇间,既有挂牌多年的实体老店,亦有冠名“国家级”却难觅办公实景的空壳平台。为帮本地开发者、政务单位及中小企客户拨云见日,小编历时三周实地踏访,核验…

2026/10/17 23:55:51 阅读更多 →
CentOS 7启动失败:I/O error与元数据损坏修复

CentOS 7启动失败:I/O error与元数据损坏修复

服务器通电后一直停在开机阶段,反复重启依然进不去系统,屏幕上滚动的不是熟悉的登录提示符,而是大段的 Buffer I/O error、EXT4-fs error 和 “metadata corruption detected” 这类报错。我敢说大部分运维遇到这个画面心里都会沉一下&#x…

2026/10/17 23:55:51 阅读更多 →
NCP1337准谐振电源设计:无线圈谷底检测与实战避坑指南

NCP1337准谐振电源设计:无线圈谷底检测与实战避坑指南

简介:本资源是一篇面向电源工程师与电子设计从业者的专业技术文章,聚焦NCP1337增强型准谐振PWM控制器的原理剖析与工程实践,解决高效率、低EMI隔离电源在工业控制场景下的设计难点。文中系统阐述无线圈去磁检测与软跳周期两大核心技术&#x…

2026/10/17 23:55:51 阅读更多 →
Flutter扩展库鸿蒙适配实战:daily_extensions百道扩展切面落地

Flutter扩展库鸿蒙适配实战:daily_extensions百道扩展切面落地

有人在群里发了一条消息:某项目在鸿蒙上跑 Flutter,启动明显卡顿,日志里铺满了平台通道的重复调用;紧接着有人说他们团队为了适配鸿蒙,把项目里所有跟设备相关的逻辑重写了一遍;然后又有人问 daily_extensi…

2026/10/17 23:55:51 阅读更多 →
RectTransform 全攻略:锚点、sizeDelta 与 UI 自适应

RectTransform 全攻略:锚点、sizeDelta 与 UI 自适应

1. 先把那些让你UI乱跑的根源说清楚:RectTransform到底多出来了什么做Unity界面开发的同学,十有八九都在RectTransform上调过半天参数。我也一样,早期做UI时最迷惑的就是:明明在Inspector里把Width和Height都填好了,运…

2026/10/17 23:55:51 阅读更多 →
JMEE论文精解:实体与关系联合抽取的图注意力建模范式

JMEE论文精解:实体与关系联合抽取的图注意力建模范式

1. 这篇EMNLP 2018论文到底在解决什么真问题?“Jointly Multiple EE via Attention-based Graph Information Aggregation”——光看标题,很多人第一反应是:又一篇堆砌术语的NLP论文?Attention、Graph、Jointly、Multiple EE……这…

2026/10/17 23:54:50 阅读更多 →

日新闻

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

DeepSeek选型与部署:V3/R1、Ollama、API全攻略

简介:《DeepSeek使用方法.pdf》是一份面向DeepSeek入门及进阶用户的完整使用指南,重点围绕V3与R1两款模型的能力差异与适用场景展开,帮助读者根据任务类型与成本选择合适的模型。资源共1个PDF文件,压缩包大小约311KB,篇…

2026/10/17 0:00:25 阅读更多 →
200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

200+Deepseek润色指令拆解:从底层原理到场景落地的完整指南

简介:这是一份聚焦DeepSeek提示词优化的《200Deepseek润色指令》PDF文档,共收录两百余条可直接套用的润色指令,涵盖语法修正、风格统一、逻辑梳理、表达精炼等维度,并针对商务写作、学术论文、广告文案等场景给出差异化建议。文档…

2026/10/17 0:00:25 阅读更多 →
OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

OpenCV轮廓法物体计数系统:轻量、可控、可落地的工业级实现

简介:本资源是一份基于OpenCV的本科课程设计完整报告,面向计算机视觉初学者、图像处理学习者及高校通信/信息类专业学生,解决物体智能计数与检测结果结构化记录的实际问题。项目覆盖图像预处理、轮廓检测、边界框绘制、轨迹栏交互式参数调节、…

2026/10/17 0:00:25 阅读更多 →

周新闻

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

流感时间序列预测实战:ARIMA/LSTM全流程拆解与避坑指南

简介:基于 ARIMA、LSTM、Transformer 等模型的流感时间序列预测 Python 源码,面向计算机相关专业课程设计与期末大作业学生,以及项目实战学习者。内容覆盖预处理、平稳性检验、定阶、残差分析、多模型对比预测的完整时序建模流程,…

2026/10/16 21:35:51 阅读更多 →
影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别

影刀RPA新手教程:键盘模拟输入实战——输入文本与模拟按键的区别 做影刀RPA自动化,十个新手有八个栽在"往输入框里填东西"这件事上:要么填不进去,要么填了一半,要么直接把原来内容追加在后面。这背后的根因&…

2026/10/17 1:44:59 阅读更多 →
影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容

影刀RPA新手教程:阅文起点小说数据采集实战——书籍信息与章节内容 1. 认识影刀:什么场景该用RPA采小说数据 起点中文网的页面结构相对稳定——分类榜单、书籍详情、章节内容三块独立页面,跳转链路清晰。这种场景非常适合影刀自动化&#x…

2026/10/16 15:04:24 阅读更多 →

月新闻

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

我发现了一个新思路:用 Remotion + Claude Code 像写代码一样自动化生成短视频

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 21:54:38 阅读更多 →
Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

Windows下 Codex 中 Chrome 和 Computer Use 插件不可用问题排查及解决参考方式:TaoToken 统一 Key 配置与验证

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/17 17:22:31 阅读更多 →
黑夜航拍船只数据集训练YOLOV5模型全流程解析

黑夜航拍船只数据集训练YOLOV5模型全流程解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

2026/10/16 14:23:36 阅读更多 →