术语解释
HTTP无状态的超文本传输协议,明文传输
SSL使用加密技术保护数据安全性,安全性被质疑,现在逐步被 TLS 取代
TLS传输层安全性,SSL 的后续版本,提供比 SSL 更强的加密和身份验证。有多个版本 TLS 1.0/1.1/1.2/1.3 等
HTTPSHTTP 的安全版本,使用 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’就不一样了,验签失败。

签名证明了三件事

  1. 不可否认性 : 只有持有私钥的人能产生这个签名,赖不掉。
  2. 完整性 : 签名包含了消息的hash,消息被篡改一个字节,hash就对不上了。
  3. 不可伪造性 : 没有私钥的人,无法为任意消息算出有效的签名

引入CA

  1. 发送方将自己的个人信息以及公钥,用CA的私钥进行签名,得到证书。
  2. 发送方将需要发送的原始信息和证书以及个人签名一起发送。
  3. 接受方,收到信息后,
    1. 用CA的公钥验证发送发的证书,验证通过(证书确实是发送方的证书,并且经过CA签名的,未经篡改,证书中的公钥确实是发送发的公钥);
    2. 拿到发送方的公钥(从证书中提取)
    3. 用提取得到的公钥验证发送方附带的个人签名,确认确实是发送方发送的。

证书

证书结构

X.509证书结构

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
版本:V3
序列号:12:34:56:78 (唯一序列号)
签名算法:ECDSA-SHA
签发者 :
	C =CN,
	O=domain-name,
	CN = domain-name Factory CA

有效期
	Not Befor : 2026-07-24
	Not After:2036-07-21

主题(Subject)
	CN  = DEV-20260725-00001
	 O = domain-name
	 
持有人公钥:
	Algorithm : EC P-256 (prime256v1)
	Key : 04:0a:9f:22:b4:26:...
	
签名 : 签名者用私钥对以上信息进行签名(HASH取摘要,对摘要用私钥进行加密)

比如以下这个设备证书

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# openssl x509 -in ./device.crt -text -noout
Certificate:
    Data:
        Version: 3 (0x2)
        Serial Number:
            52:23:64:76:be:87:9c:90:2a:7a:a8:13:25:5b:ef:4e:8e:04:d8:02
        Signature Algorithm: ecdsa-with-SHA256
        Issuer: C=CN, O=domain-name, CN=domain-name Factory CA
        Validity
            Not Before: Jul 24 14:55:48 2026 GMT
            Not After : Jul 21 14:55:48 2036 GMT
        Subject: CN=DEV-20260718-00001, O=domain-name
        Subject Public Key Info:
            Public Key Algorithm: id-ecPublicKey
                Public-Key: (256 bit)
                pub:
                    04:0a:9f:22:b4:26:cc:ed:6e:c4:6c:5f:67:c4:34:
                    b1:86:53:ab:b1:26:9c:fd:9b:aa:54:8c:ad:d3:10:
                    a3:0c:de:a7:f9:d5:c9:66:cb:ec:59:67:a0:01:f2:
                    58:6b:b4:03:e8:ce:ce:13:05:90:4b:45:ce:07:b3:
                    56:3d:e3:8d:cd
                ASN1 OID: prime256v1
                NIST CURVE: P-256
        X509v3 extensions:
            X509v3 Subject Key Identifier:
                AF:7C:A8:CE:D6:C6:D9:A7:E6:18:51:8F:7E:0F:3A:F5:BD:6B:9C:99
            X509v3 Authority Key Identifier:
                EF:7B:9C:DA:90:EB:8E:1A:87:2F:61:34:C2:4F:8A:30:5D:E0:E7:CE
    Signature Algorithm: ecdsa-with-SHA256
    Signature Value:
        30:45:02:20:78:ea:6c:67:20:f5:4a:b2:2c:0e:1e:85:4c:f6:
        ab:16:a0:e4:58:14:9b:30:c8:e0:3d:22:28:c5:3e:54:53:18:
        02:21:00:b8:81:b0:19:c7:05:e5:eb:d4:82:a7:af:28:b3:d5:
        ff:16:61:1b:64:30:a3:f0:27:33:ca:7c:58:8e:fe:db:77

证书 = 公钥 + 身份 + CA签名

编号全程含义
CNCommon Name实体的名字
OOrganization组织/公司名
CCountry国家代码

证书生成命令

生成证书的过程:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
  # 1. 生成设备 EC P-256 密钥对
  openssl ecparam -genkey -name prime256v1 -out device.key

  # 2. 生成 CSR (证书签名请求),CN 填设备 SN
  openssl req -new -key device.key -out device.csr  -subj "/CN=DEV-20260718-00001/O=domain-name"

  # 3. 工厂 CA 签发证书(10 年)
  openssl x509 -req -days 3650 -in device.csr \
    -CA factory-ca.crt -CAkey factory-ca.key \
    -sha256 -out device.crt

  # 4. 构建证书链(设备 + 中间 CA)
  cat device.crt factory-ca.crt > chain.crt

查看证书中的信息:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
  # 基本信息(主题、签发者、有效期)
  openssl x509 -in device.crt -noout -subject -issuer -dates

  # 指纹
  openssl x509 -in device.crt -noout -fingerprint -sha256

  # 完整内容
  openssl x509 -in device.crt -text -noout

  # 验证证书链
  openssl verify -CAfile root-ca.crt -untrusted factory-ca.crt device.crt

  # 查看私钥
  openssl ec -in device.key -text -noout

证书链

生成顺序:Root → Factory → Device 信任方向:Device → Factory → Root (反过来验证)

第 1 步:生成 Root CA(自签名)

1
2
3
4
5
6
  # 生成根私钥
  openssl ecparam -genkey -name prime256v1 -out root-ca.key

  # 自己给自己签发
  openssl req -new -x509 -days 10950 -key root-ca.key -out root-ca.crt \
    -subj "/C=CN/O=domain-name/CN=domain-name Root CA"

产物:root-ca.key + root-ca.crt。 root-ca.crt 里 Issuer = Subject = domain-name Root CA(自己签自己)。

第 2 步:生成 Factory CA(由 Root 签发)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 生成工厂私钥
openssl ecparam -genkey -name prime256v1 -out factory-ca.key

# 生成 CSR (证书请求文件)
openssl req -new -key factory-ca.key -out factory-ca.csr \
  -subj "/C=CN/O=domain-name/CN=domain-name Factory CA"

# Root 用自己的私钥签发 Factory 证书
openssl x509 -req -days 7300 -in factory-ca.csr \
  -CA root-ca.crt -CAkey root-ca.key \
  -sha256 -out factory-ca.crt

此时 factory-ca.crt 里 Issuer = Root CA, Subject = Factory CA。 Root 用自己的私钥在 Factory 证书上签了名。

第 3 步:生成 Device 证书(由 Factory 签发)

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
# 生成设备私钥
openssl ecparam -genkey -name prime256v1 -out device.key

# 生成 CSR(CN = 设备 SN,这就是设备身份)
openssl req -new -key device.key -out device.csr \
  -subj "/CN=DEV-20260718-00001/O=domain-name"

#  Factory 用自己的私钥签发设备证书
openssl x509 -req -days 3650 -in device.csr \
  -CA factory-ca.crt -CAkey factory-ca.key \
  -sha256 -out device.crt

此时 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: 加密通道建立 ✅

私钥和证书的关系

  1. 私钥和证书是一对的
  2. 私钥:设备自己藏着,绝不出设备,TLS握手时用来签名
  3. 证书: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