主题
B9. 数据加密与校验框架(防篡改 / 防窃听 / 防窥视)
综合例程。加密原语见
11-03-crypto;校验/编码见11-02-encoder、11-05-json。 这一页把"怎么把一段数据安全地送出去"拼成可直接照搬的框架。
适用:定制版(AES/MD5 需 crypto 模块)| 依赖模块:crypto json mqtt | 难度:★★☆
三个层次,按需选用:
| 层次 | 目的 | 手段 |
|---|---|---|
| 完整性校验 | 怕传输中被改/截断 | CRC16(纯 Lua,全机型可用) |
| 防篡改签名 | 服务端要验"确实是我发的" | MD5 签名(crypto,定制版) |
| 防窃听 | 线上不想出现明文 | AES-CBC/CTR(crypto,定制版) |
| 防窥视(弱) | 不让人一眼看懂寄存器 | 梯形图/ Lua 端异或混淆(非真加密) |
框架 1:CRC16 校验(纯 Lua,全机型可用)
lua
-- Modbus 风格 CRC16(0xFFFF 初值 / 0xA001 多项式 / 低字节在前)
function crc16(buf)
local init = 0xFFFF
local poly = 0xA001
local ret = init
for j = 1, #buf do
local byte = buf:byte(j)
ret = (ret ~ byte) & 0xFFFF
for i = 1, 8 do
if (ret & 0x0001) > 0 then
ret = (ret >> 1)
ret = (ret ~ poly) & 0xFFFF
else
ret = (ret >> 1)
end
end
end
local hi = (ret >> 8) & 0xFF
local lo = ret & 0xFF
ret = ((hi << 8) | lo)
return string.format("%04X", ret) -- 返回 "XXXX" 十六进制串
end
-- 用法:把待发报文算个校验,拼到末尾一起发出去
local frame = "\01\03\00\00\00\02" -- 示例:读保持寄存器
local crc = crc16(frame)
print("校验码 = " .. crc) -- 例如 "F40B"要点:这是标准 CRC16/MODBUS,和 com_modbus 用的算法一致;自己算可以贴在任何自定义帧上。 验证方法:拿一段已知报文(如 01 03 00 00 00 02)算出来应为 F40B,与在线 CRC 计算器对得上即正确。
框架 2:MD5 签名(防篡改,定制版)
lua
-- 给上报报文加 MD5 签名,服务器用同 secret 校验完整性
local function sign(body, secret)
return crypto.md5(body .. secret)
end
local payload = json.encode({ temp = plc.df(100), humi = plc.df(102) })
mqtt.publish(mqc, "data/up", payload .. "|" .. sign(payload, "mySecret"), 0, 0)要点:crypto.md5 返回 32 位十六进制串;固件只有 MD5,没有 SHA,对接老平台/做接口签名够用,别用于密码存储。 验证方法:服务端用相同 mySecret 算 MD5 比对一致即验签通过。 注意事项:MD5 不抗碰撞,敏感场景用框架 3 的 AES。
框架 3:AES 加密上报(防窃听,定制版)
lua
local KEY = "0123456789abcdef" -- 16 字节 = AES-128;别硬编码在脚本里(可被上位机读出)
local function seal(plain)
local iv = crypto.random_bytes(16) -- 每次用新随机 IV
local enc = crypto.aes_cbc_encrypt(plain, KEY, iv)
return crypto.base64_encode(iv .. enc) -- IV 不保密,拼在密文前
end
local function unseal(b64)
local raw = crypto.base64_decode(b64)
return crypto.aes_cbc_decrypt(raw:sub(17), KEY, raw:sub(1, 16))
end
mqtt.publish(mqc, "secure/data", seal(json.encode({ temp = plc.df(100) })), 0, 0)要点:aes_cbc_encrypt 自动 PKCS7 填充,明文任意长度;IV 必须 16 字节,同密钥下每次换 IV;CTR 模式加解密是同一个函数(crypto.aes_ctr),nonce 同样绝不可重复。 验证方法:定制版上加密,服务端用同 KEY/IV 解密还原。 注意事项:标准 ESP32 固件与软 PLC 模拟器不含 crypto(调用即 nil);密钥请用 plc.kv_set/fdb 在部署时写入,或走一次性配网下发,切勿写在 main.lua 里。
框架 4:梯形图 / Lua 端弱混淆(仅防窥视,非加密)
真加密走框架 3。下面只是让寄存器里的数据"不让人一眼看懂",防君子不防小人,别当安全机制用。
Lua 端异或混淆:
lua
local KEY = "LP" -- 混淆密钥,随便定
function xor_scramble(s, key)
local out = {}
for i = 1, #s do
out[i] = string.char(s:byte(i) ~ key:byte(((i - 1) % #key) + 1))
end
return table.concat(out)
end
local hidden = xor_scramble("hello", KEY) -- 存到寄存器前先混淆梯形图端混淆(用算术指令对数据做异或/加盐,例如把 D0 异或 K173 存到 D10):
LD D0
XOR K173 ; 与常数异或,结果落 D10
OUT D10解混淆时再异或一次 K173 还原。
程序本身防读取(部署层)
- Lua 源码可被上位机读出来,所以密钥、密码不要硬编码在
main.lua(见框架 3 注意事项)。 - 梯形图/ Lua 下发时,上位机工具支持加密工程文件、不暴露源码;具体见
04-04 编译下载与上载。 - 真要"别人读不到我的逻辑",靠的是工程文件加密 + 不在脚本里写密钥,而不是靠代码混淆。