主题
crypto — 加密模块
模块名:crypto
提供 AES 加密、Base64 编解码、MD5 摘要和随机字节。全部是库级函数,数据以字节串形式传递。
哪些机型有:LP3232、LP3288(ESP32-S3 平台),LP8910、LP8911、LP8912、LP8913、LP8915、LP8916、LP8922(4G 平台),LP2300、LP2323(STM32H750 平台),以及上位机的内置软设备与软 PLC 模拟器。
哪些机型没有:经典 ESP32 平台的机型(LP3200–LP3223 等)与 STM32F103 平台的机型(LP1616、LP2222、LP2248–LP2252、LP2923、LP2925、LP2927 等)没有这个模块,调用 crypto.xxx(...) 会得到 attempt to index a nil value。
跨机型通用写法 —— 先判断再调用,没有的机型退回 plc.aes_encode:
lua
if crypto then
local enc = crypto.aes_cbc_encrypt(data, key, iv) -- 密钥与 IV 由脚本自己定
else
local enc = plc.aes_encode(data) -- 退回设备固化密钥的 CBC
end能力边界(所有平台一致):AES 三种模式(ECB / CBC / CTR),密钥长度 128/192/256 位;摘要只有 MD5,不支持任何 SHA 系列;虽然定义了 MODE_GCM 常量,但没有任何平台实现 GCM 加解密函数。
随机字节的强度按平台不同:ESP32-S3 用芯片硬件随机数,上位机与软 PLC 模拟器用操作系统提供的随机数,4G 机型(LP89xx 系列)用的是模组 SDK 的伪随机接口 —— 用它生成长期使用的密钥或 IV 时,4G 机型弱于其它平台。
函数总览
| 函数 | 作用 |
|---|---|
crypto.aes_ecb_encrypt | AES-ECB 加密(无填充) |
crypto.aes_ecb_decrypt | AES-ECB 解密 |
crypto.aes_cbc_encrypt | AES-CBC 加密(自动 PKCS7 填充) |
crypto.aes_cbc_decrypt | AES-CBC 解密(自动去填充) |
crypto.aes_ctr | AES-CTR 流加密/解密(同一个函数) |
crypto.base64_encode | Base64 编码 |
crypto.base64_decode | Base64 解码 |
crypto.md5 | 计算 MD5 摘要 |
crypto.random_bytes(integer) | 生成硬件随机字节 |
常量
| 常量 | 值 | 说明 |
|---|---|---|
crypto.MODE_ECB | 0 | ECB 模式标识 |
crypto.MODE_CBC | 1 | CBC 模式标识 |
crypto.MODE_CTR | 2 | CTR 模式标识 |
crypto.MODE_GCM | 3 | GCM 模式标识。只有常量,没有对应函数 |
这些常量只作标识用,各加解密函数按名字区分模式,不需要传模式参数。
crypto.aes_ecb_encrypt
lua
crypto.aes_ecb_encrypt(plaintext, key) --> stringAES-ECB 模式加密。不做填充。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | plaintext | string | 是 | — | 明文,长度必须是 16 字节的整数倍 |
| 2 | key | string | 是 | — | 密钥,必须为 16、24 或 32 字节(AES-128/192/256) |
返回值
| 类型 | 说明 |
|---|---|
| string | 密文,长度与明文相同 |
说明
- 密钥长度非法、或明文长度不是 16 的整数倍时会直接报错。
- ECB 对相同明文块产生相同密文块,安全性弱。新设计一律优先用 CBC 或 CTR,ECB 只用于对接老系统。
crypto.aes_ecb_decrypt
lua
crypto.aes_ecb_decrypt(ciphertext, key) --> stringAES-ECB 模式解密。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | ciphertext | string | 是 | — | 密文,长度必须是 16 字节的整数倍 |
| 2 | key | string | 是 | — | 密钥,16 / 24 / 32 字节 |
返回值
| 类型 | 说明 |
|---|---|
| string | 明文,长度与密文相同 |
crypto.aes_cbc_encrypt
lua
crypto.aes_cbc_encrypt(plaintext, key, iv) --> stringAES-CBC 模式加密。内部自动做 PKCS7 填充,明文可任意长度。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | plaintext | string | 是 | — | 明文,任意长度 |
| 2 | key | string | 是 | — | 密钥,16 / 24 / 32 字节 |
| 3 | iv | string | 是 | — | 初始向量,必须正好 16 字节 |
返回值
| 类型 | 说明 |
|---|---|
| string | 密文,长度为填充后的 16 字节整数倍 |
说明
- 填充是固定 PKCS7,无法关闭;即使明文已是 16 字节整数倍也会额外补一整块,所以密文总比明文长。
- IV 不需要保密,但同一密钥下每次加密都应换 IV,可用
crypto.random_bytes(16)生成后随密文一起发给对方。
crypto.aes_cbc_decrypt
lua
crypto.aes_cbc_decrypt(ciphertext, key, iv) --> stringAES-CBC 模式解密,自动校验并去除 PKCS7 填充。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | ciphertext | string | 是 | — | 密文,长度必须是 16 字节的整数倍 |
| 2 | key | string | 是 | — | 密钥,16 / 24 / 32 字节 |
| 3 | iv | string | 是 | — | 加密时用的同一个 IV,16 字节 |
返回值
| 类型 | 说明 |
|---|---|
| string | 去掉填充后的明文 |
说明
- 填充校验是常量时间实现,可抵御 Padding Oracle 类攻击;填充非法时报错。
crypto.aes_ctr
lua
crypto.aes_ctr(data, key, nonce) --> stringAES-CTR 流模式。加密和解密是同一操作,对密文再调用一次即还原。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | data | string | 是 | — | 明文或密文,任意长度,无需填充 |
| 2 | key | string | 是 | — | 密钥,16 / 24 / 32 字节 |
| 3 | nonce | string | 是 | — | 计数器初值,必须正好 16 字节 |
返回值
| 类型 | 说明 |
|---|---|
| string | 结果,长度与输入相同 |
示例
lua
local key = "0123456789abcdef"
local nonce = crypto.random_bytes(16)
local enc = crypto.aes_ctr("hello lpplc", key, nonce)
local dec = crypto.aes_ctr(enc, key, nonce) -- 同一函数还原
print(dec) -- hello lpplc说明
- 同一 key 下 nonce 绝不可重复,重复会导致密钥流复用被破解。每条消息用新的随机 nonce。
crypto.base64_encode
lua
crypto.base64_encode(data) --> stringBase64 编码(mbedTLS 实现)。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | data | string | 是 | — | 原始字节串 |
返回值
| 类型 | 说明 |
|---|---|
| string | Base64 字符串 |
说明
- 与
encoder.tobase64功能相同,任选其一。
crypto.base64_decode
lua
crypto.base64_decode(str) --> stringBase64 解码。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | str | string | 是 | — | Base64 字符串 |
返回值
| 类型 | 说明 |
|---|---|
| string | 解码后的字节串 |
crypto.md5
lua
crypto.md5(data) --> string计算 MD5 摘要。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | data | string | 是 | — | 输入数据 |
返回值
| 类型 | 说明 |
|---|---|
| string | 32 个字符的十六进制摘要字符串 |
示例
lua
print(crypto.md5("hello")) -- 5d41402abc4b2a76b9719d911017c592说明
- MD5 已不抗碰撞,不要用于数字签名或密码存储。做接口签名、完整性校验或对接要求 MD5 的老平台可以用。
- 固件里没有 SHA 系列函数,需要 SHA256 只能在服务端做。
crypto.random_bytes
lua
crypto.random_bytes(length) --> string用 ESP32 硬件随机数发生器生成指定长度的随机字节。
参数
| # | 参数 | 类型 | 必填 | 默认值 | 说明 |
|---|---|---|---|---|---|
| 1 | length | integer | 是 | — | 要生成的字节数 |
返回值
| 类型 | 说明 |
|---|---|
| string | 随机字节串 |
示例
lua
local iv = crypto.random_bytes(16) -- 给 CBC 生成 IV说明
- 走硬件 RNG,比
math.random适合做密钥、IV、nonce、会话 token。
常见用法
CBC 加密上报,IV 随密文一起发出
lua
local KEY = "0123456789abcdef" -- 16 字节 = AES-128
local function seal(plain)
local iv = crypto.random_bytes(16)
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(mqttc, "secure/data", seal(json.encode({ temp = plc.df(100) })), 0, 0)接口签名
lua
local function sign(body, secret)
return crypto.md5(body .. secret)
end注意事项
- 密钥不要硬编码在 main.lua 里。脚本可被上位机读出,密钥应通过
plc.kv_set、fdb在部署时写入,或走一次性配网下发。 - ECB 不做填充且相同明文出相同密文,新设计请用 CBC 或 CTR。
- CTR 的 nonce 在同一密钥下绝不可重复。
- 只有 MD5,没有 SHA;有
MODE_GCM常量但没有 GCM 函数,不要按 GCM 设计协议。 - AES 是对称加密。需要非对称(RSA/ECC)的场景固件不支持,请在服务端实现。
例程
⚠ 下面示例只在带
crypto的机型上可跑(见页首的机型清单);不带该模块的机型请退回plc.aes_encode写法。统一模板「场景 / 完整代码 / 要点 / 验证方法 / 注意事项」。
例程 1:CBC 加密上报,IV 随密文一起发
适用:带 crypto 的机型(见页首清单) | 依赖模块:crypto json mqtt | 难度:★★☆
场景:把温度 JSON 用 AES-128-CBC 加密后再走 MQTT,明文不出现在线上。
完整代码(可直接下载运行):
lua
local KEY = "0123456789abcdef" -- 16 字节 = AES-128
local function seal(plain)
local iv = crypto.random_bytes(16)
local enc = crypto.aes_cbc_encrypt(plain, KEY, iv)
return crypto.base64_encode(iv .. enc) -- IV 不保密,拼在前面
end
mqtt.publish(mqttc, "secure/data", seal(json.encode({ temp = plc.df(100) })), 0, 0)要点:aes_cbc_encrypt 自动 PKCS7 填充,明文任意长度;每次用新随机 IV。
验证方法:带 crypto 的机型上能加密,服务端用同 KEY/IV 解密还原。
注意事项:标准固件无此模块(调用即 nil);IV 必须 16 字节;同密钥下每次换 IV。
例程 2:接口签名(MD5)
适用:带 crypto 的机型(见页首清单) | 依赖模块:crypto json | 难度:★☆☆
场景:给上报报文加 MD5 签名,服务器校验完整性。
完整代码(可直接下载运行):
lua
local function sign(body, secret)
return crypto.md5(body .. secret)
end
local payload = json.encode({ temp = plc.df(100) })
mqtt.publish(mqttc, "data/up", payload .. "|" .. sign(payload, "mySecret"), 0, 0)要点:crypto.md5 返回 32 位十六进制串。
验证方法:服务端用相同 secret 算 MD5 比对一致。
注意事项:MD5 不抗碰撞,别用于密码存储;固件只有 MD5 没有 SHA。
例程 3:注意事项——GCM 只有常量没有函数
适用:带 crypto 的机型(见页首清单) | 依赖模块:crypto | 难度:★☆☆
完整代码(可直接下载运行):
lua
-- ❌ 错误:有 crypto.MODE_GCM 常量,但没有对应的 GCM 加解密函数
local enc = crypto.aes_gcm_encrypt(...) -- 不存在
-- ✅ 正确:用 CBC 或 CTR
local iv = crypto.random_bytes(16)
local enc = crypto.aes_cbc_encrypt(plain, KEY, iv)要点:MODE_GCM = 3 只是常量,没有对应函数;对称加密不支持非对称(RSA/ECC)。
验证方法:正确写法能加解密。
注意事项:按 GCM 设计协议会失败;密钥别硬编码在脚本里(可被上位机读出)。