Skip to content

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 编译下载与上载。
  • 真要"别人读不到我的逻辑",靠的是工程文件加密 + 不在脚本里写密钥,而不是靠代码混淆。

LPPLC 工业物联网云平台 · 软硬件与云平台全栈自研