QMK Secure 功能完全指南:解锁序列、自动锁定与安全状态管理
2026/9/14 21:39:45 网站建设 项目流程

QMK Secure 功能完全指南:解锁序列、自动锁定与安全状态管理

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

Secure(安全锁定)是 QMK 固件中一项防止"未经用户干预"的意外交互的安全功能:键盘在锁定状态下会忽略所有按键输入,用户必须按顺序按下预配置的矩阵位置序列(Unlock Sequence)才能解锁,解锁后还会在一段可配置的空闲时间后自动重新锁定。本文基于当前仓库中 docs/features/secure.md 文档,并结合quantum/secure.cquantum/process_keycode/process_secure.c及单元测试 tests/secure/test_secure.cpp 的实现细节,系统讲解该功能的启用方式、密钥码、配置项、状态查询函数与状态切换钩子,帮助你安全地在自己的键位方案中集成锁定能力。

重要提示:Secure 功能目前并未实现任何加密/解密能力,它只是一道"虚拟挂锁"。如果你的场景需要强硬件/软件级别的安全方案,Secure 不能作为替代品。这一点在官方文档中明确标注,本功能的价值在于防止误触与未经许可的交互,而不是提供真正的数据保护。

功能机制:三态状态机与解锁序列

从源码看,Secure 的核心是一个三态状态机,定义在 quantum/secure.h:

typedef enum { SECURE_LOCKED, // 已锁定:忽略所有键盘输入 SECURE_PENDING, // 解锁请求已发起,正在等待用户完成解锁序列 SECURE_UNLOCKED, // 已解锁:键盘恢复正常输入 } secure_status_t;

默认情况下,固件上电后处于SECURE_LOCKED状态。解锁遵循以下规则:

  • 解锁序列(Unlock Sequence):用户必须执行一组预配置的按键操作。该序列可以配置为多个键,按顺序按下这些键对应的矩阵位置即可完成解锁。
  • 锁定期间忽略所有输入:只要处于锁定状态,键盘的任何按键输入都会被吞掉,不会产生 HID 报告。
  • 错误尝试立即回退:解锁过程中只要按错一个键,状态立即回退到SECURE_LOCKED,用户必须从头再来。

这些行为在 quantum/process_keycode/process_secure.c 中实现。preprocess_secure会在按键处理的最早阶段被调用(见 quantum/quantum.c),一旦检测到secure_is_unlocking()(即SECURE_PENDING状态),所有按键事件都被拦截并转发给secure_keypress_event(row, col)做序列比对,同时直接返回false阻止后续任何按键处理:

bool preprocess_secure(uint16_t keycode, keyrecord_t *record) { if (secure_is_unlocking()) { // !pressed 会触发已按住的键(例如层切换键),导致解锁请求过早失败 if (record->event.pressed) { secure_keypress_event(record->event.key.row, record->event.key.col); } // 在序列完成之前禁用普通按键 return false; } return true; }

序列比对逻辑位于 quantum/secure.c:内部维护一个static uint8_t offset作为序列进度指针,每次按键与SECURE_UNLOCK_SEQUENCE中对应位置的{row, col}匹配则推进;一旦全部匹配完成即调用secure_unlock(),否则offset清零并调用secure_lock()回退到锁定态:

void secure_keypress_event(uint8_t row, uint8_t col) { static const uint8_t sequence[][2] = SECURE_UNLOCK_SEQUENCE; static const uint8_t sequence_len = ARRAY_SIZE(sequence); static uint8_t offset = 0; if ((sequence[offset][0] == row) && (sequence[offset][1] == col)) { offset++; if (offset == sequence_len) { offset = 0; secure_unlock(); } } else { offset = 0; secure_lock(); } }

可以推断:由于序列匹配是基于"矩阵坐标"而非键值(keycode)进行,因此解锁序列与键位映射(keymap)无关,无论某个位置上映射了什么功能键,只要按下了该物理位置即被计为序列的一步。这也意味着解锁序列无法区分同一个物理按键在不同层上的不同含义——它只关心"物理上按了哪个位置"。

进入解锁序列前会清理按键状态

细心的读者会发现,quantum/quantum.c 中定义了secure_hook_quantum:当状态切换到SECURE_PENDING时,固件会主动调用clear_keyboard()layer_clear()。其注释解释了原因:如果在状态切换瞬间某些键仍被按住,这些键可能不会被正确释放,进而导致按键、修饰键(mods)和图层(layers)卡死。因此在进入解锁流程前强制清理键盘与图层状态,避免解锁后出现"幽灵键"。

启用 Secure:rules.mk 配置

在键盘或键位的rules.mk中加入以下内容即可启用:

SECURE_ENABLE = yes

SECURE_ENABLE同时出现在 builddefs/show_options.mk 的可选特性列表中,属于 QMK 的可选编译特性。启用后,编译系统会包含quantum/secure.cquantum/process_keycode/process_secure.c,并在quantum/quantum.c中激活对应的钩子调用。

密钥码(Keycodes)

Secure 提供 4 个专用密钥码,均可在 quantum/keycodes.h 中找到定义,别名位于同文件的 quantum/keycodes.h:

密钥码别名说明
QK_SECURE_LOCKSE_LOCK立即回到锁定状态
QK_SECURE_UNLOCKSE_UNLK强制解锁,不执行解锁序列
QK_SECURE_TOGGLESE_TOGG直接在锁定/解锁之间切换,不执行解锁序列
QK_SECURE_REQUESTSE_REQ请求用户执行解锁序列(进入SECURE_PENDING,开始监听序列)

这些密钥码的处理逻辑位于 quantum/process_keycode/process_secure.c 的process_secure函数中,并且仅在**按键释放(!record->event.pressed)**时触发,避免按下瞬间误触发:

bool process_secure(uint16_t keycode, keyrecord_t *record) { #ifndef SECURE_DISABLE_KEYCODES if (!record->event.pressed) { if (keycode == QK_SECURE_LOCK) { secure_lock(); return false; } if (keycode == QK_SECURE_UNLOCK) { secure_unlock(); return false; } if (keycode == QK_SECURE_TOGGLE) { secure_is_locked() ? secure_unlock() : secure_lock(); return false; } if (keycode == QK_SECURE_REQUEST) { secure_request_unlock(); return false; } } #endif return true; }

从源码结构可以推断:如果你想彻底关闭这几个密钥码(例如改用纯函数驱动、或避免用户误触发),可以在编译时定义SECURE_DISABLE_KEYCODES,此时process_secure将直接放行所有按键,Secure 密钥码不再生效,但解锁序列与状态机功能仍然可用。

配置项详解

Secure 的 3 个配置宏均通过编译期#define设置,默认值定义在 quantum/secure.c:

宏定义默认值说明
SECURE_UNLOCK_TIMEOUT5000用户执行解锁序列的超时时间(毫秒),超时自动回锁;0表示禁用超时
SECURE_IDLE_TIMEOUT60000解锁后的空闲超时(毫秒),超时自动回锁;0表示禁用空闲自动回锁
SECURE_UNLOCK_SEQUENCE{ { 0, 0 } }描述连续按键序列的矩阵坐标数组,按顺序逐一匹配{ row, col }

默认的SECURE_UNLOCK_SEQUENCE = { { 0, 0 } }即"仅需按下矩阵 (0, 0) 位置一次"。

如何自定义解锁序列

由于该宏是 C 数组形式,需要在config.h中这样覆盖:

// 例如要求依次按下 (0,0)、(1,0)、(2,0) 三个位置才能解锁 #define SECURE_UNLOCK_SEQUENCE \ { { 0, 0 }, { 1, 0 }, { 2, 0 } }

需要注意:

  • 序列长度完全由数组中元素个数决定(源码中使用ARRAY_SIZE(sequence)自动计算,见 quantum/secure.c),因此可以配置任意数量的键;
  • 匹配基于矩阵坐标{row, col},与具体键值无关;
  • 配置的坐标必须真实存在于你的矩阵中,否则该步永远无法被触发,导致序列永远无法完成。

超时与空闲自动回锁的实现

secure_task()(quantum/secure.c)在每次键盘扫描周期中由 quantum/keyboard.c 调用,负责两件事:

  1. 解锁序列超时:处于SECURE_PENDING时,若距离secure_request_unlock()记录的时间戳超过SECURE_UNLOCK_TIMEOUT,自动执行secure_lock()回锁;
  2. 空闲自动回锁:处于SECURE_UNLOCKED时,若距离最近一次活动超过SECURE_IDLE_TIMEOUT,自动执行secure_lock()
void secure_task(void) { #if SECURE_UNLOCK_TIMEOUT != 0 // 处理解锁超时 if (secure_status == SECURE_PENDING) { if (timer_elapsed32(unlock_time) >= SECURE_UNLOCK_TIMEOUT) { secure_lock(); } } #endif #if SECURE_IDLE_TIMEOUT != 0 // 处理空闲超时 if (secure_status == SECURE_UNLOCKED) { if (timer_elapsed32(idle_time) >= SECURE_IDLE_TIMEOUT) { secure_lock(); } } #endif }

需要特别强调的是,当SECURE_UNLOCK_TIMEOUT = 0时,宏展开后的代码块被预处理指令完全剔除,因此0表示"禁用该超时",而不是"立即超时"。

保持设备持续解锁:secure_activity_event()

解锁后,任何用户活动都会刷新空闲计时器。在键盘代码或自定义钩子中调用secure_activity_event()(quantum/secure.h),即可告知 Secure 子系统"仍有用户活动,设备应保持解锁状态"。其实现位于 quantum/secure.c:仅在SECURE_UNLOCKED状态下重置idle_time,其他状态下调用无效。

一个典型用法是:从各类钩子中周期性上报活动。官方文档建议结合 docs/custom_quantum_functions.md 中介绍的各种钩子(如matrix_scan_userprocess_record_user等)调用该函数,例如在matrix_scan_user中检测到旋钮转动、传感器事件等非键盘活动时刷新空闲计时,避免因"长时间没有按键"而被错误回锁。

状态查询与操作函数

Secure 在 quantum/secure.h 中暴露了完整的状态查询与操作 API:

函数说明
secure_is_locked()检查设备当前是否处于锁定状态(宏,等价于status == SECURE_LOCKED
secure_is_unlocking()检查解锁序列是否正在进行中(宏,等价于status == SECURE_PENDING
secure_is_unlocked()检查设备当前是否处于解锁状态(宏,等价于status == SECURE_UNLOCKED
secure_lock()立即锁定设备
secure_unlock()强制解锁设备——绕过用户解锁序列
secure_request_unlock()开始监听解锁序列(仅在SECURE_LOCKED状态下生效并进入 PENDING)
secure_activity_event()标记用户活动发生,设备应保持解锁状态

其中前三个查询是定义在 quantum/secure.h 中的宏,底层统一调用secure_get_status()

#define secure_is_locked() (secure_get_status() == SECURE_LOCKED) #define secure_is_unlocking() (secure_get_status() == SECURE_PENDING) #define secure_is_unlocked() (secure_get_status() == SECURE_UNLOCKED)

几个值得注意的源码级行为细节:

  • secure_request_unlock()(quantum/secure.c)只有在当前状态是SECURE_LOCKED时才会切换到SECURE_PENDING并记录解锁起始时间;若已处于解锁态,调用它不会产生任何状态变化;
  • secure_unlock()(quantum/secure.c)在切换状态的同时会记录当前时间作为空闲计时起点,这样解锁后立即开始计算SECURE_IDLE_TIMEOUT
  • secure_lock()/secure_unlock()/secure_request_unlock()在改变状态后都会调用内部钩子secure_hook()(quantum/secure.c),依次触发secure_hook_quantumsecure_hook_kb

状态切换钩子:secure_hook_user

Secure 还提供了一套状态变更回调链,用于在状态切换时执行自定义逻辑。相关声明见 quantum/secure.h:

void secure_hook_quantum(secure_status_t secure_status); // quantum 层钩子 bool secure_hook_kb(secure_status_t secure_status); // 键盘层钩子 bool secure_hook_user(secure_status_t secure_status); // 用户层钩子

从 quantum/secure.c 可以看到,secure_hook_usersecure_hook_kb都是__attribute__((weak))弱符号,默认实现返回true,且secure_hook_kb的默认实现会转发调用secure_hook_user。因此典型的用户侧用法是:在键盘的keymap.c中定义自己的secure_hook_user,每当状态变化时执行动作,例如在锁定/解锁时播放提示音、切换指示灯颜色或更新 OLED 显示:

bool secure_hook_user(secure_status_t secure_status) { switch (secure_status) { case SECURE_LOCKED: // 例如:锁定指示灯变红、关闭 OLED、触发蜂鸣 break; case SECURE_PENDING: // 例如:进入解锁流程时清屏提示"请输入解锁序列" break; case SECURE_UNLOCKED: // 例如:解锁指示灯变绿 break; } return true; }

同时secure_hook_quantum(quantum/quantum.c)会在进入SECURE_PENDING时自动清空键盘与图层状态,防止按住键导致的卡键问题——这是框架层面的默认行为,用户无需重复实现。

单元测试验证

仓库在 tests/secure/test_secure.cpp 中为 Secure 功能提供了完整的 GoogleTest 单元测试套件,覆盖了核心状态转换与边界场景,可作为理解行为契约的权威参考:

  • test_lock:验证secure_unlock()secure_is_unlocked()为真、secure_lock()后为假;
  • test_unlock_timeout:验证SECURE_IDLE_TIMEOUT + 1毫秒空闲后自动回锁;
  • test_unlock_request:按顺序敲击key_a → key_b → key_c → key_d完成序列后成功解锁;
  • test_unlock_request_fail:序列前多敲一个错误键key_e,解锁失败并保持锁定;
  • test_unlock_request_timeout:验证SECURE_UNLOCK_TIMEOUT + 1毫秒后解除 PENDING 状态并回到锁定;
  • test_unlock_request_fail_mid:在序列中间(key_a, key_b之后)敲入错误键key_e,验证回退到锁定且不再处于解锁流程中;
  • test_unlock_request_fail_out_of_order:验证乱序按键无法解锁。

这些测试直接印证了文档所述的三条核心行为:锁定期间不产生按键报告(测试中以EXPECT_NO_REPORT/EXPECT_EMPTY_REPORT断言)、错误尝试回退到锁定状态、超时自动回锁。如果你要修改 Secure 的默认行为,运行该测试套件(qmk test下的 secure 用例)可以快速验证是否破坏既有契约。

完整集成示例

综合以上内容,一个最小可用的集成方案如下:

  1. rules.mk中启用:

    SECURE_ENABLE = yes
  2. config.h中配置解锁序列与超时:

    // 解锁序列:依次按下 (0,0) → (1,1) → (2,2) #define SECURE_UNLOCK_SEQUENCE \ { { 0, 0 }, { 1, 1 }, { 2, 2 } } // 15 秒内未完成序列则回锁 #define SECURE_UNLOCK_TIMEOUT 15000 // 解锁后空闲 5 分钟自动回锁 #define SECURE_IDLE_TIMEOUT 300000
  3. keymap.c中放置控制密钥码,例如:

    // 某层中放置:SE_REQ(请求解锁)、SE_LOCK(主动回锁) [1] = LAYOUT(..., SE_REQ, ..., SE_LOCK, ...)
  4. 可选:定义secure_hook_user响应状态变化,并在matrix_scan_user等钩子中按需调用secure_activity_event()刷新空闲计时。

需要留意的是:配置矩阵坐标前,请对照你键盘的MATRIX_ROW_PINS/MATRIX_COL_PINS与 keymap 定义确认对应物理位置;SECURE_UNLOCK_SEQUENCE使用的是矩阵行列号而非键值,误配会导致序列无法完成。

总结

Secure 功能以"锁定状态机 + 矩阵坐标解锁序列 + 双超时自动回锁"为骨架,为 QMK 键盘提供了一套轻量级的防误触/防未授权交互方案。其实现高度模块化:状态机与超时逻辑集中在 quantum/secure.c,按键拦截与密钥码处理在 quantum/process_keycode/process_secure.c,对外暴露的查询/操作函数与钩子在 quantum/secure.h,并有配套单元测试保障行为契约。最后再次强调官方文档中的警告:Secure 不含任何加密解密能力,不能替代真正的硬件/软件级安全方案——请根据你的实际威胁模型决定是否使用它。

【免费下载链接】qmk_firmwareOpen-source keyboard firmware for Atmel AVR and Arm USB families项目地址: https://gitcode.com/GitHub_Trending/qm/qmk_firmware

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询