非对称加密与 mTLS 身份认证
| 术语 | 解释 |
|---|---|
| HTTP | 无状态的超文本传输协议,明文传输 |
| SSL | 使用加密技术保护数据安全性,安全性被质疑,现在逐步被 TLS 取代 |
| TLS | 传输层安全性,SSL 的后续版本,提供比 SSL 更强的加密和身份验证。有多个版本 TLS 1.0/1.1/1.2/1.3 等 |
| HTTPS | HTTP 的安全版本,使用 SSL/TLS 协议在 HTTP 的基础上提供加密和安全性。双方先用非对称加密协商出密钥,后续通信使用对称加密 |
| X.509 | 定义了数字证书的格式标准和使用规范 |
加/解密过程
sequenceDiagram
participant Bob
participant Alice
Alice ->> Bob : Alice的公钥公开
Note right of Bob : 明文 XXX
Note right of Bob : 用Alice的公钥对明文加密
Note right of Bob : 得到密文 YYYY
Bob ->> Alice : 密文
Note right of Alice: 用Alice的私钥对密文进行解密
Note right of Alice : 得到解密后的明文 XXX
中间的密文,如果没有私钥,就没办法进行破解
签名/验签过程
graph LR
subgraph 发送方
direction TB
A[原始信息]
B[Hash运算]
C["摘要(Digest)"]
D["签名(Signature)"]
A -->|MD5/SHA-256| B
B --> C
C -->|用私钥加密| D
end
subgraph 接收方
direction TB
E[原始信息]
F[Hash运算]
G["摘要(Digest)"]
G'["摘要'(Digest')"]
H["签名(Signature)"]
I{判断是否相等}
E -->|MD5/SHA-256| F
F --> G
H -->|用公钥解密| G'
G --> I
G' --> I
end
D -->|发送含签名的信息| H
A -.->|原始信息也发送| E
a. 如果信息原文被篡改,则HASH后的摘要就会变,Digest和Digest’就不一样了,验签失败。 b. 如果签名被替换了的话,则公钥解密失败(解密出来的东西就不一样了),Digest和Digest’就不一样了,验签失败。
签名证明了三件事
- 不可否认性 : 只有持有私钥的人能产生这个签名,赖不掉。
- 完整性 : 签名包含了消息的hash,消息被篡改一个字节,hash就对不上了。
- 不可伪造性 : 没有私钥的人,无法为任意消息算出有效的签名
引入CA
- 发送方将自己的个人信息以及公钥,用CA的私钥进行签名,得到证书。
- 发送方将需要发送的原始信息和证书以及个人签名一起发送。
- 接受方,收到信息后,
- 用CA的公钥验证发送发的证书,验证通过(证书确实是发送方的证书,并且经过CA签名的,未经篡改,证书中的公钥确实是发送发的公钥);
- 拿到发送方的公钥(从证书中提取)
- 用提取得到的公钥验证发送方附带的个人签名,确认确实是发送方发送的。
证书
证书结构
X.509证书结构
| |
比如以下这个设备证书
| |
证书 = 公钥 + 身份 + CA签名
| 编号 | 全程 | 含义 |
|---|---|---|
| CN | Common Name | 实体的名字 |
| O | Organization | 组织/公司名 |
| C | Country | 国家代码 |
证书生成命令
生成证书的过程:
| |
查看证书中的信息:
| |
证书链
生成顺序:Root → Factory → Device 信任方向:Device → Factory → Root (反过来验证)
第 1 步:生成 Root CA(自签名)
产物:root-ca.key + root-ca.crt。 root-ca.crt 里 Issuer = Subject = domain-name Root CA(自己签自己)。
第 2 步:生成 Factory CA(由 Root 签发)
| |
此时 factory-ca.crt 里 Issuer = Root CA, Subject = Factory CA。 Root 用自己的私钥在 Factory 证书上签了名。
第 3 步:生成 Device 证书(由 Factory 签发)
| |
此时 device.crt 里 Issuer = Factory CA, Subject = CN=DEV-20260718-00001。
graph BT
subgraph "证书链(自上而下签发)"
Root["Root CA<br/>自签名<br/>Issuer=Root CA<br/>Subject=Root CA<br/>用 root-ca.key"]
Factory["Factory CA<br/>由 Root 签发<br/>Issuer=Root CA<br/>Subject=Factory CA<br/>用 factory-ca.key"]
Device["Device Cert<br/>由 Factory 签发<br/>Issuer=Factory CA<br/>Subject=CN=DEV-...00001<br/>用 device.key"]
end
Root -- "用 root-ca.key 签名" --> Factory
Factory -- "用 factory-ca.key 签名" --> Device
每台设备预置以下信息
| 文件名 | 解释 |
|---|---|
| device.key | 设备私钥 |
| device.crt | 设备证书 |
| chain.crt | 证书链,里边有device.crt和factory-ca.crt |
| ca.crt | 根证书(用于验证服务器身份) |
sequenceDiagram
participant Dev as 设备<br/>DEV-20260718-00001<br/>持有: device.key + chain.crt + ca.crt
participant MQTT Broker as MQTT Broker<br/>持有: server.key + server.crt<br/>信任: root-ca.crt
Note over Dev,MQTT Broker: ═══════ 1️⃣ 设备验证服务器 ═══════
Dev->>MQTT Broker: ClientHello
MQTT Broker-->>Dev: ServerHello + server.crt
Note over Dev: 设备验证服务器身份:<br/><br/>第1步: 用 ca.crt 验 server.crt 的签名<br/>openssl verify -CAfile ca.crt server.crt<br/><br/>第2步: 检查 server.crt 的 CN 是否 = domain-name.site<br/>(防止中间人用别的合法证书冒充)<br/><br/>✅ 签名有效 + CN 匹配 → 服务器身份确认
Note over Dev,MQTT Broker: ═══════ 2️⃣ 服务器验证设备 ═══════
MQTT Broker->>Dev: CertificateRequest<br/>(请出示你的证书)
Dev-->>MQTT Broker: chain.crt<br/>(device.crt + factory-ca.crt)
Note over MQTT Broker: 服务器验证设备身份:<br/><br/>第1步: 构建信任链<br/>device.crt 的 Issuer = Factory CA<br/>→ 用 factory-ca.crt 验 device.crt<br/>→ factory-ca.crt 的 Issuer = Root CA<br/>→ 用 root-ca.crt 验 factory-ca.crt<br/>→ root-ca.crt 在 MQTT Broker 信任库中 ✅<br/><br/>第2步: 提取设备身份<br/>CN = DEV-20260718-00001<br/>→ 这就是 MQTT username<br/><br/>✅ 证书链完整 → 设备身份确认
Dev->>MQTT Broker: 密钥交换
MQTT Broker-->>Dev: 加密通道建立 ✅
私钥和证书的关系
- 私钥和证书是一对的
- 私钥:设备自己藏着,绝不出设备,TLS握手时用来签名
- 证书:TLS握手是发送给服务器,包含设备公钥、身份、CA签名
mTLS完整认证流程
sequenceDiagram
participant D as 设备<br/>DEV-20260718-00001
participant E as MQTT Broker<br/>MQTT Broker<br/>:8883 (MQTTS)
participant B as 后端 FastAPI<br/>内部 :8000
participant P as PostgreSQL
Note over D,P: ═══ 阶段一: TLS 双向握手 (mTLS) ═══
D->>E: ClientHello<br/>TLS 版本 + 支持的密码套件
E-->>D: ServerHello<br/>选 ECDHE-ECDSA-AES256-GCM-SHA384<br/>+ 出示 server.crt<br/>("CN=domain-name.site")
Note over D: 验证服务器证书链:<br/>server.crt ← Root CA (ca.crt)<br/>✅ 签名有效, CN 匹配
E->>D: CertificateRequest<br/>(要求客户端提供证书)
D-->>E: 出示 chain.crt<br/>(device.crt + factory-ca.crt)
Note over E: 验证设备证书链:<br/>device.crt ← Factory CA ← Root CA<br/>✅ 签名有效<br/>提取 CN = "DEV-20260718-00001"<br/>→ 这就是 MQTT username
D-->>E: 密钥交换 + Finished<br/>✅ TLS 加密通道建立
Note over D,P: ═══ 阶段二: MQTT CONNECT ═══
D->>E: MQTT CONNECT<br/>username=DEV-20260718-00001<br/>password=(空, mTLS 已认证)
Note over E: 提取 username = CN<br/>查询 ACL 规则
E->>E: ACL 检查<br/>❓ 你能访问哪些主题?
Note over E: ACL 规则:<br/>✅ DEV-* → vending/{username}/#<br/>→ DEV-20260718-00001 只能访问<br/>vending/DEV-20260718-00001/#
E-->>D: CONNACK (连接成功)
Note over D,P: ═══ 阶段三: 设备注册/状态更新 ═══
E->>B: Webhook: POST /api/v1/mqtt/hooks/connected<br/>{"username":"DEV-20260718-00001","peercert":{...}}
B->>P: SELECT * FROM devices WHERE device_sn='DEV-20260718-00001'
alt 设备不存在 (首次连接)
P-->>B: 无记录
B->>P: INSERT INTO devices<br/>(device_sn, status='unregistered', is_active=true)
B->>B: 返回 "registered"
else 设备已禁用 (is_active=false)
P-->>B: is_active=false
B->>E: API: ban_device + disconnect_client
E-->>D: ❌ 断开连接
else 正常设备
P-->>B: is_active=true
B->>P: UPDATE devices SET last_online_at=now()
B->>B: 返回 "ok"
end
Note over D,P: ═══ 阶段四: 心跳通信 ═══
loop 每 15 秒
D->>E: PUBLISH vending/DEV-20260718-00001/status<br/>{"status":"online","signal_strength":22,...}
E->>B: 路由消息 (通配符订阅)
B->>B: SETEX device:heartbeat:DEV-20260718-00001 90 "{...}"
Note over B: Redis TTL=90s<br/>key 存在 → 在线
end
Note over D,P: ═══ 阶段五: 设备断开 ═══
D->>E: DISCONNECT / TCP 断开
Note over E: LWT (遗嘱消息) 触发
E->>B: PUBLISH vending/DEV-20260718-00001/status<br/>{"status":"offline"}
B->>B: Redis key 开始倒计时 90s
Note over B: 90s 后 key 自动过期<br/>设备状态 → offline