Tasmota 中的 Keeloq 加密算法库:从 NLF 位密码原理到 Jarolift 卷帘实战
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
导读
Keeloq 是 Microchip 广泛用于遥控钥匙(RKE,Remote Keyless Entry)与无线门禁系统的分组密码算法。本文以 Tasmota 仓库中的 KeeloqLib 为核心,完整讲解该库在 Arduino/ESP8266/ESP32 平台上的封装接口、加密/解密实现细节,并结合仓库内xdrv_36_keeloq.ino展示它如何被真实应用于 Jarolift 卷帘(rolladen)遥控信号的加解密与 433 MHz 射频发射,帮助读者既掌握 Keeloq 算法本身的原理,也能在自有项目中复用这套实现。
一、Keeloq 算法与 KeeloqLib 的定位
1.1 算法背景与许可注意事项
Keeloq 是一种 32 位分组密码,采用 64 位密钥,因实现轻量、功耗低而被大量用于无线门锁、车库门与卷帘遥控器。仓库 README(lib/lib_rf/KeeloqLib/README.md)给出了三点关键说明:
- 该实现已用 Microchip 官方 SDK 的多组测试用例做过比对验证,结果一致;
- Keeloq 算法本身没有专利;
- 但 Microchip 对“将 Keeloq 应用于安全免钥匙进入(secure keyless entry)”这一应用场景持有专利,因此在非 Microchip 处理器上用它做商业化的免钥匙进入产品并不明智,个人实验学习没有问题。
一句话总结:算法可自由研究,商用要谨慎(买 Microchip 的处理器)。这是在使用该库前必须先明确的边界。
1.2 库的组成与适用平台
KeeloqLib 位于 lib/lib_rf/KeeloqLib,目录结构如下:
- src/KeeloqLib.h:类声明与公共接口;
- src/KeeloqLib.cpp:528 轮加密/解密核心实现;
- tests/KeeloqLibTest/KeeloqLibTest.ino:Arduino 测试示例;
- keywords.txt:Arduino IDE 语法高亮映射;
- library.properties:Arduino 库元数据。
根据 library.properties,该库版本为 1.1,支持架构为esp8266,esp32,开发与测试环境为 Arduino IDE;同时它也能在普通 AVR Arduino 上编译运行(头文件按 Arduino 版本自动选择<Arduino.h>或<WProgram.h>)。
二、核心 API:64 位密钥、32 位数据加解密
2.1 接口定义
src/KeeloqLib.h 中定义了唯一一个类Keeloq,接口非常精简:
class Keeloq { public: Keeloq( const unsigned long keyHigh, const unsigned long keyLow ); unsigned long encrypt( const unsigned long data ); unsigned long decrypt( const unsigned long data ); private: unsigned long _keyHigh; unsigned long _keyLow; };参数说明:
| 参数/方法 | 类型 | 含义 |
|---|---|---|
keyHigh | unsigned long(32 位) | 64 位密钥的高 32 位 |
keyLow | unsigned long(32 位) | 64 位密钥的低 32 位 |
encrypt(data) | 入参/返回值均为unsigned long | 对 32 位明文加密,返回 32 位密文 |
decrypt(data) | 入参/返回值均为unsigned long | 对 32 位密文解密,还原 32 位明文 |
构造时两个 32 位变量拼成完整 64 位密钥;encrypt与decrypt对单个 32 位分组操作,且二者互为逆运算(即decrypt(encrypt(x)) == x)。
2.2 构造函数实现
src/KeeloqLib.cpp 中构造函数仅保存两个密钥字:
Keeloq::Keeloq(const unsigned long keyHigh, const unsigned long keyLow) : _keyHigh( keyHigh ), _keyLow( keyLow ) { }三、算法原理与 528 轮核心实现
Keeloq 本质是一个非线性反馈移位寄存器(NLFSR)结构的分组密码。库中定义了一个关键的查表常量:
#define KeeLoq_NLF (0x3A5C742EUL)这个 32 位常量0x3A5C742E就是 Keeloq 的非线性函数(NLF,NonLinear Function)查找表:其第i位(i = 0..31)作为 5 个输入位组合(1, 2, 4, 8, 16 加权)的输出真值。32 位常量共含 32 个输出位,正好覆盖 5 输入的 32 种组合。
3.1 加密:528 轮右移
encrypt 的实现:
unsigned long Keeloq::encrypt( const unsigned long data ) { unsigned long x = data; unsigned long r; int keyBitNo, index; unsigned long keyBitVal,bitVal; for ( r = 0; r < 528; r++ ) { keyBitNo = r & 63; if(keyBitNo < 32) keyBitVal = bitRead(_keyLow,keyBitNo); else keyBitVal = bitRead(_keyHigh, keyBitNo - 32); index = 1 * bitRead(x,1) + 2 * bitRead(x,9) + 4 * bitRead(x,20) + 8 * bitRead(x,26) + 16 * bitRead(x,31); bitVal = bitRead(x,0) ^ bitRead(x, 16) ^ bitRead(KeeLoq_NLF,index) ^ keyBitVal; x = (x>>1) ^ bitVal<<31; } return x; }逐行拆解:
- 密钥位选择:
keyBitNo = r & 63,即每 64 轮循环一次完整 64 位密钥;keyBitNo < 32时取_keyLow的第keyBitNo位,否则取_keyHigh的第keyBitNo - 32位; - NLF 索引计算:取当前状态
x的第 1、9、20、26、31 位,按 1/2/4/8/16 加权合成 0~31 的索引,查表KeeLoq_NLF; - 反馈位:
bitVal = bit0(x) ^ bit16(x) ^ NLF[index] ^ keyBit,即状态位 0、状态位 16、非线性函数输出与密钥位四者异或; - 移位:
x = (x>>1) ^ bitVal<<31,整体右移 1 位,并把反馈位送入最高位——这是标准 NLFSR 的移位反馈过程。
加密共迭代 528 轮(0x3A5C742E也常与 528 这个轮数一起出现在各类 Keeloq 实现中,528 = 8×64 + 16,即 8 轮完整密钥循环外加 16 轮)。
3.2 解密:528 轮左移与密钥逆序
decrypt 是加密的镜像过程:
for (r = 0; r < 528; r++) { keyBitNo = (15-r) & 63; ... index = 1 * bitRead(x,0) + 2 * bitRead(x,8) + 4 * bitRead(x,19) + 8 * bitRead(x,25) + 16 * bitRead(x,30); bitVal = bitRead(x,31) ^ bitRead(x, 15) ^ bitRead(KeeLoq_NLF,index) ^ keyBitVal; x = (x<<1) ^ bitVal; }与加密相比,解密有三处镜像对应:
| 维度 | 加密 | 解密 |
|---|---|---|
| 密钥位序 | r & 63(正序) | (15-r) & 63(逆序,且偏移 15) |
| NLF 输入位 | 位 1, 9, 20, 26, 31 | 位 0, 8, 19, 25, 30(整体下移 1 位) |
| 异或的线性位 | 位 0 与位 16 | 位 31 与位 15(对称位置) |
| 移位方向 | 右移x>>1 | 左移x<<1 |
这体现了分组密码“加解密同构”的设计思想:只要把移位方向、密钥使用顺序和位索引对称翻转,同一个 NLF 表即可完成解密。
四、测试示例与自验证
4.1 示例程序
tests/KeeloqLibTest/KeeloqLibTest.ino 提供了一个开箱即用的 Arduino 测试,串口波特率 9600:
#include <KeeloqLib.h> void setup( void ) { Serial.begin(9600); Keeloq k(0x01020304,0x05060708); const unsigned long p = 6623281UL; const unsigned long enc = k.encrypt(p); const unsigned long dec = k.decrypt(enc); Serial.print("Plaintext : "); Serial.println(p,DEC); Serial.print("After encrypt: "); Serial.println(enc,HEX); Serial.print("After decrypt: "); Serial.println(dec,DEC); }预期行为:
- 密钥:
keyHigh = 0x01020304,keyLow = 0x05060708; - 明文:
6623281(十进制); enc输出 32 位密文(十六进制);dec必须重新还原为6623281,以此验证加解密互为逆运算。
由于该实现与 Microchip SDK 输出一致,只要dec == p,即可确认当前平台的实现与官方算法兼容。这个模式也可以扩展为已知明文/密文对的回归测试:固定密钥后,把enc的十六进制结果记录下来,后续改动后重新跑一遍比对。
4.2 依赖与编译
测试示例依赖<KeeloqLib.h>,在 Arduino IDE 中把 lib/lib_rf/KeeloqLib 目录放入libraries路径即可;在 PlatformIO 中则把该目录加入lib_deps或lib_extra_dirs。库头文件会根据编译目标自动选择<Arduino.h>(Arduino 1.0+)或<WProgram.h>(旧版本)。
五、仓库实战:Tasmota 的 Jarolift 卷帘驱动(xdrv_36_keeloq.ino)
KeeloqLib 在 Tasmota 中的真实用途是驱动 Jarolift 卷帘遥控协议,对应驱动文件 xdrv_36_keeloq.ino。这是理解该库价值的最佳落地案例。
5.1 硬件与配置前提
从驱动头部注释可以确认硬件要求:
- 使用硬件 SPI 与CC1101433 MHz 射频收发芯片;
- 需要两个用户可配置 GPIO:
GPIO_CC1101_GDO0与GPIO_CC1101_GDO2; - 实际只有
GPIO_CC1101_GDO0被使用(由 CC1101 库决定必须为 GPIO05),GDO2为“假”GPIO,仅作占位。
编译开关为USE_KEELOQ,在 my_user_config.h 中可见其定义说明://#define USE_KEELOQ // Add support for Jarolift rollers by Keeloq algorithm (+4k5 code),即启用后固件约增加 4.5 KB 代码。需要特别注意的是:
- tasmota_globals.h 中在部分 ESP32 配置下会
#undef USE_KEELOQ,原因是所用 cc1101 库与 ESP32 不兼容; - tasmota_configurations.h 与 tasmota_configurations_ESP32.h 中的多数配置集也默认禁用它;
- xsns_121_tfa_marbella.ino 中明确
#error "USE_TFA_MARBELLA and USE_KEELOQ both drive the CC1101 and cannot be combined"——二者共享 CC1101,不能同时启用。
因此实际运行前提以 ESP8266 为主,且不能与 TFA Marbella 传感器功能共存。
5.2 主密钥与设备密钥的派生:Keeloq 的第一个用途
Jarolift 协议使用“主密钥(master key)→ 设备密钥(device key)”的两级派生。在 CmdSet 中,用户通过命令KeeloqSet写入主密钥的高 32 位、低 32 位、序列号与计数器:
Settings->keeloq_master_msb = param[0]; Settings->keeloq_master_lsb = param[1]; Settings->keeloq_serial = param[2]; Settings->keeloq_count = param[3];随后 GenerateDeviceCryptKey 用主密钥构造Keeloq对象,并对“序列号 + 固定标志位”做解密来派生出该设备专用的 64 位设备密钥:
Keeloq k(Settings->keeloq_master_msb, Settings->keeloq_master_lsb); jaroliftDevice.device_key_msb = k.decrypt(jaroliftDevice.serial | 0x60000000L); jaroliftDevice.device_key_lsb = k.decrypt(jaroliftDevice.serial | 0x20000000L);这里把serial | 0x60000000与serial | 0x20000000两个 32 位数据分别解密,得到设备密钥的高、低半字。这是 Keeloq 双向性的典型应用:主密钥参与“加密式”的密钥派生,反方向使用decrypt。
5.3 遥控数据帧的构造:Keeloq 的第二个用途
CmdSendButton 中按钮值取值约定为:0x8上、0x4停止、0x2下、0x1学习。真正组帧在 CreateKeeloqPacket:
Keeloq k(jaroliftDevice.device_key_msb, jaroliftDevice.device_key_lsb); unsigned int result = (jaroliftDevice.disc << 16) | jaroliftDevice.count; jaroliftDevice.pack |= jaroliftDevice.serial & 0xfffffffL; jaroliftDevice.pack |= (jaroliftDevice.button & 0xfL) << 28; jaroliftDevice.pack <<= 32; jaroliftDevice.enc = k.encrypt(result); jaroliftDevice.pack |= jaroliftDevice.enc;可以看到:
- 明文
result = (disc << 16) | count,即“频道标识(默认0x0100)占高 16 位 + 滚动计数器占低 16 位”; - 用设备密钥(而非主密钥)对
result做encrypt,得到 32 位密文; - 最终 64 位发送包
pack高 32 位为“序列号(28 位) + 按钮(4 位)”,低 32 位为 Keeloq 密文。
这个结构完整展示了 Keeloq 在真实 RKE 协议中的标准用法:把易变的计数器与频道标识加密成认证码,与明文序列号、按钮位一起构成空中帧。滚动计数器每次发送后自增并回写Settings->keeloq_count(见 CmdSendButton),确保重放攻击失效。
5.4 空中发射与命令一览
组帧完成后,驱动通过SendSyncPreamble(13)发送 13 组同步前导,再逐位调用 SendBit 按 400 µs/800 µs 的脉宽编码发送 72 位数据(每帧重复发送 2 次),并使用noInterrupts()/interrupts()保护发送时序。
驱动对外暴露三条控制台命令(见 kJaroliftCommands):
| 命令 | 函数 | 作用 |
|---|---|---|
KeeloqSet msb,lsb,serial,count | CmdSet | 写入主密钥高/低 32 位、序列号、计数器并派生设备密钥 |
KeeloqSendButton n | CmdSendButton | 按按钮值(0x8/0x4/0x2/0x1)构造并发射一帧 |
KeeloqSendRaw bits | CmndSendRaw | 直接按'1'/'0'字符串原样逐位发射(调试用) |
四个参数分别持久化到 tasmota_types.h 中定义的keeloq_master_msb、keeloq_master_lsb、keeloq_serial、keeloq_count四个uint32_t字段。
六、在自己的项目里复用 KeeloqLib
总结一套可复用的集成路径:
- 引入库:复制 lib/lib_rf/KeeloqLib 到你的 Arduino
libraries目录或 PlatformIO 的lib目录; - 初始化:
Keeloq k(keyHigh, keyLow);,其中两个 32 位参数拼接为 64 位密钥; - 加解密:
uint32_t enc = k.encrypt(plain);、uint32_t plain = k.decrypt(enc);; - 自测:仿照 KeeloqLibTest.ino 验证
decrypt(encrypt(x)) == x,再与 Microchip SDK 已知向量比对; - 协议集成:参考 xdrv_36_keeloq.ino 的模式——用主密钥解密派生子密钥、用子密钥加密计数器与标识组成帧;
- 注意平台限制:当前仓库中该库与 CC1101 的组合主要面向 ESP8266;部分 ESP32 配置集已
#undef USE_KEELOQ,且不能与USE_TFA_MARBELLA并存。
结语
从 528 轮 NLFSR 的位级实现,到 Arduino 测试样例,再到 Tasmota 中 Jarolift 卷帘驱动的完整落地,KeeloqLib 虽只有两个源文件,却把 Keeloq 算法的全部核心要素(64 位密钥、32 位分组、NLF 查表、双向加解密、密钥派生与滚动计数器组帧)浓缩其中。无论你是想学习分组密码的轻量实现,还是需要兼容 Microchip RKE 协议的无线项目,都可以直接以本仓库的 KeeloqLib 为起点,在个人实验范围内放心使用,同时谨记 README 中关于商业应用的专利提醒。
【免费下载链接】TasmotaAlternative firmware for ESP8266 and ESP32 based devices with easy configuration using webUI, OTA updates, automation using timers or rules, expandability and entirely local control over MQTT, HTTP, Serial or KNX. Full documentation at项目地址: https://gitcode.com/GitHub_Trending/ta/Tasmota
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考