[i=s] 本帖最后由 WT_0213 于 2025-3-14 09:34 编辑 [/i]
使用小安派BW21-CBV-Kit开发板实现丐版 Windows Hello 人脸功能。
一、什么是 Windows Hello?
Windows Hello是登录 Windows 设备的一种更个性化、更安全的方式。 使用Windows Hello可以使用面部识别、指纹或 PIN 登录,而不是使用密码。
这些选项有助于更轻松地登录电脑,因为你的 PIN 仅与一台设备相关联,并且使用 Microsoft 帐户备份用于恢复。
可以使用“设置”应用配置和管理Windows Hello。
设置 Windows Hello
请按照以下步骤设置 Windows Hello。
- 选择**“开始”
>“设置”
>“帐户”>“登录”选项**。
- 在**“登录方式下”**,你将看到使用 Windows Hello 登录的三个选项:
- 选择**“面部识别 (Windows Hello)”**以使用电脑的红外摄像头或外部红外摄像头设置通过面部识别登录。
- 选择**“指纹识别 (Windows Hello)”**以设置使用指纹读取器登录。
- 选择**“PIN (Windows Hello)”**以设置使用 PIN 登录。

Windows Hello技术简析
3D活体检测
==硬件组成==:
- 近红外(NIR)摄像头
- 泛光照明器(Flood Illuminator)
- 点阵投影器(Dot Projector)
==工作原理==:
def 活体验证():
第一阶段:深度图构建
红外图像 = 捕捉940nm波长图像()
点阵图案 = 投射30000个不可见光点()
深度图 = 计算点阵形变(点阵图案)
第二阶段:生理特征验证
脉搏检测 = 分析皮下血液流动(红外序列图像)
眼球运动 = 追踪虹膜微震颤()
return 深度图 and 脉搏检测 and 眼球运动
非对称加密体系
==密钥生成流程==:
sequenceDiagram
用户->>TPM: 生物特征数据
TPM->>TPM: 生成RSA 2048密钥对
TPM->>系统: 公钥
TPM->>TPM: 私钥永久锁定
系统->>AD: 注册公钥
==认证过程==:
- 系统发送随机挑战码(Challenge)
- TPM用私钥签名挑战码
- 服务器用公钥验证签名
安全防护机制
1. 分层防御体系
层级 |
防护措施 |
破解难度 |
L1 |
防照片/视频回放 |
★☆☆☆☆ |
L2 |
防3D打印面具 |
★★☆☆☆ |
L3 |
防热成像复制 |
★★★☆☆ |
L4 |
防物理攻击TPM |
★★★★☆ |
2. 数据存储规范
struct BiometricData {
uint8_t 加密模板[512]; // AES-GCM加密
uint32_t 版本号;
uint64_t 时间戳; // 防止重放攻击
uint8_t HMAC签名; // SHA-256校验
};
与传统方案的对比
1. 认证流程差异
步骤 |
传统密码方案 |
Windows Hello |
身份验证 |
字符串比对 |
生物特征+密码学证明 |
数据传输 |
密码明文传输 |
数字签名挑战响应 |
存储方式 |
密码哈希存储 |
非对称密钥对存储 |
防重放攻击 |
无 |
时间戳+随机数 |
2. 安全性能指标
指标 |
传统PIN码 |
Windows Hello |
暴力破解时间 |
2小时 |
10^18年 |
中间人攻击成功率 |
63% |
0.0001% |
凭证泄露影响范围 |
所有系统 |
单设备 |
二、 HID设备模拟原理
HID设备模拟原理是通过软件或硬件手段生成符合HID协议规范的数据流,使计算机系统将其识别为真实物理输入设备并响应操作指令,其核心实现机制包含以下要点:
协议与驱动交互原理
- ==协议规范匹配==
模拟设备需遵循HID协议定义的数据格式和通信规则,包括设备描述符、接口描述符、报告描述符等元数据配置例如,设备类(bDeviceClass)需设置为0x03以标识为HID设备,并通过报告描述符定义数据用途与格式。
- ==操作系统驱动模型==
操作系统通过硬件抽象层(HAL)和内置的HID类驱动直接解析标准HID设备数据,无需额外驱动。模拟设备通过注册为系统级输入设备,与原生驱动交互完成指令映射。
实现方式分类
- ==硬件模拟==
- 使用微控制器(如Arduino)或专用芯片(如USB Rubber Ducky)模拟USB HID设备,通过固件生成符合协议的电气信号和数据包
- 蓝牙HID模拟通过协议栈封装输入指令,如蓝牙低功耗(BLE)设备伪装成键盘/鼠标,通过GATT服务传输操作指令
- ==软件模拟==
- 虚拟驱动层拦截系统输入事件,构造虚拟HID设备并注入输入信号,例如通过Windows的HID API动态创建虚拟设备节点
- 远程控制场景中,通过传输协议(如USB over IP)转发外设操作数据至受控端,在系统层面重放为本地输入事件
数据映射与传输机制
- ==输入信号编码==
用户操作(如按键、移动)需转换为HID报告描述符定义的结构化数据。例如,键盘按键对应特定Usage Page和Usage ID,通过中断传输模式发送至主机
- ==传输通道控制==
HID设备仅支持控制传输(设备枚举、配置)和中断传输(实时输入),模拟设备需实现端点管理及带宽分配策略以保证低延迟
典型应用与限制
- ==正向应用==:自动化测试脚本执行、无障碍辅助设备开发
- ==安全风险==:恶意设备模拟可绕过系统安全机制执行隐蔽操作(如BadUSB攻击)
- ==性能瓶颈==:高频率操作(如游戏手柄)需优化中断传输速率与数据包压缩算法以避免丢帧
HID设备模拟本质是通过协议逆向与系统交互机制的深度适配,结合软硬件协同实现输入信号的精准复现与控制
三、BW21-CBV-Kit实现Windows Hello 人脸
1. 整体设计思路
摄像头->>BW21-CBV-Kit: 捕获RGB图像
BW21-CBV-Kit->>BW21-CBV-Kit: 运行人脸识别算法
识别成功
BW21-CBV-Kit->>PC: HID发送Esc进入密码解锁界面
BW21-CBV-Kit->>PC: 模拟键盘输入预设密码完成解锁

2.基础设置
面容解锁前置条件。

上传好项目代码后,打开windows 设置。搜索蓝牙,打开蓝牙、然后点击“添加蓝牙或其他设备”

点击“蓝牙”,找到名称为AMEBA_HELLO的蓝牙设备并连接

系统配置就完成了。接下来回到Arduino IDE 或者打开串口工具。
波特率设置115200,发送 REG=XXX 。其中XXX是你要设置面容名称

将BW21-CBV-Kit对准自己的脸。或者图片【为什么图片呢,因为它目前还没有活体检测】录入人脸信息。录入人脸后会输出
13:57:01.881 ->
>>> MBFACENET FPS = 2.54
13:57:01.881 ->
Total number of faces detected = 1
13:57:01.881 ->
Face 0 name xxx(人脸名称): 798 1169 292 786
人脸信息录入以后只要检测到录入过的人脸。就会触发。 Esc -> 输入密码 -> 回车的操作。 解锁速度还挺快。


3. 完整代码
#include "BLEHIDDevice.h"
#include "BLEHIDKeyboard.h"
#include "BLEDevice.h"
#include "WiFi.h"
#include "StreamIO.h"
#include "VideoStream.h"
#include "RTSP.h"
#include "NNFaceDetectionRecognition.h"
#include "VideoStreamOverlay.h"
#include <AmebaServo.h>
#include "AmebaFatFS.h"
char filename[] = "pw.txt";
String password = "";
#define CHANNELVID 0 // Channel for RTSP streaming
#define CHANNELJPEG 1 // Channel for taking snapshots
#define CHANNELNN 3 // RGB format video for NN only available on channel 3
// Customised resolution for NN
#define NNWIDTH 576
#define NNHEIGHT 320
VideoSetting configVID(VIDEO_FHD, 30, VIDEO_H264, 0);
VideoSetting configJPEG(VIDEO_FHD, CAM_FPS, VIDEO_JPEG, 1);
VideoSetting configNN(NNWIDTH, NNHEIGHT, 10, VIDEO_RGB, 0);
NNFaceDetectionRecognition facerecog;
RTSP rtsp;
StreamIO videoStreamer(1, 1);
StreamIO videoStreamerFDFR(1, 1);
StreamIO videoStreamerRGBFD(1, 1);
char ssid[] = ""; // your network SSID (name)
char pass[] = ""; // your network password
int status = WL_IDLE_STATUS;
bool unLock = false;
uint32_t img_addr = 0;
uint32_t img_len = 0;
long counter = 0;
// File Initialization
AmebaFatFS fs;
char buf[128];
char path[128];
BLEHIDKeyboard keyboardDev;
BLEAdvertData advdata;
void setup() {
fs.begin();
printf("write something to \"%s\"\r\n", filename);
sprintf(path, "%s%s", fs.getRootPath(), filename);
File file = fs.open(path);
password = file.readString();
file.close();
printf("==== content ====\r\n");
printf("%s", buf);
printf("==== end ====\r\n");
fs.end();
Serial.begin(115200);
advdata.addFlags();
advdata.addCompleteName("AMEBA_HELLO");
advdata.addAppearance(GAP_GATT_APPEARANCE_HUMAN_INTERFACE_DEVICE);
advdata.addCompleteServices(BLEUUID(HID_SERVICE_UUID));
BLEHIDDev.init();
BLE.init();
BLE.configAdvert()->setAdvData(advdata);
BLE.setDeviceName("AMEBA_HELLO");
BLE.setDeviceAppearance(GAP_GATT_APPEARANCE_HUMAN_INTERFACE_DEVICE);
BLE.configSecurity()->setPairable(true);
BLE.configSecurity()->setAuthFlags(GAP_AUTHEN_BIT_BONDING_FLAG);
BLE.configServer(3);
BLE.addService(BLEHIDDev.hidService());
BLE.addService(BLEHIDDev.battService());
BLE.addService(BLEHIDDev.devInfoService());
BLE.beginPeripheral();
// Attempt to connect to Wifi network:
while (status != WL_CONNECTED) {
Serial.print("Attempting to connect to WPA SSID: ");
Serial.println(ssid);
status = WiFi.begin(ssid, pass);
// wait 2 seconds for connection:
delay(2000);
}
// Configure camera video channels with video format information
Camera.configVideoChannel(CHANNELVID, configVID);
Camera.configVideoChannel(CHANNELJPEG, configJPEG);
Camera.configVideoChannel(CHANNELNN, configNN);
Camera.videoInit();
// Configure RTSP with corresponding video format information
rtsp.configVideo(configVID);
rtsp.begin();
// Configure Face Recognition model
facerecog.configVideo(configNN);
facerecog.modelSelect(FACE_RECOGNITION, NA_MODEL, DEFAULT_SCRFD, DEFAULT_MOBILEFACENET);
facerecog.begin();
facerecog.setResultCallback(FRPostProcess);
// Configure StreamIO object to stream data from video channel to RTSP
videoStreamer.registerInput(Camera.getStream(CHANNELVID));
videoStreamer.registerOutput(rtsp);
if (videoStreamer.begin() != 0) {
Serial.println("StreamIO link start failed");
}
// Start data stream from video channel
Camera.channelBegin(CHANNELVID);
Camera.channelBegin(CHANNELJPEG);
// Configure StreamIO object to stream data from RGB video channel to face detection
videoStreamerRGBFD.registerInput(Camera.getStream(CHANNELNN));
videoStreamerRGBFD.setStackSize();
videoStreamerRGBFD.setTaskPriority();
videoStreamerRGBFD.registerOutput(facerecog);
if (videoStreamerRGBFD.begin() != 0) {
Serial.println("StreamIO link start failed");
}
// Start video channel for NN
Camera.channelBegin(CHANNELNN);
// Start OSD drawing on RTSP video channel
OSD.configVideo(CHANNELVID, configVID);
OSD.begin();
// Restore any registered faces saved in flash
facerecog.restoreRegisteredFace();
}
void loop() {
if (Serial.available() > 0) {
String input = Serial.readString();
input.trim();
if (input.startsWith(String("REG="))) {
String name = input.substring(4);
facerecog.registerFace(name);
} else if (input.startsWith(String("DEL="))) {
String name = input.substring(4);
facerecog.removeFace(name);
} else if (input.startsWith(String("RESET"))) {
facerecog.resetRegisteredFace();
} else if (input.startsWith(String("BACKUP"))) {
facerecog.backupRegisteredFace();
} else if (input.startsWith(String("RESTORE"))) {
facerecog.restoreRegisteredFace();
} else if(input.startsWith(String("PW="))){
password = input.substring(3);
File file = fs.open(path);
file.println(password);
file.close();
printf("write finish\r\n\r\n");
}
}
if ((unLock == true) && BLE.connected()) {
unLock = false;
Serial.println("Sending keystrokes");
keyboardDev.keyReleaseAll();
delay(100);
keyboardDev.keyPress(HID_KEY_ESCAPE);
delay(300);
keyboardDev.keyReleaseAll();
delay(300);
keyboardDev.keySequence("123456");
delay(100);
keyboardDev.keyPress(HID_KEY_ENTER);
delay(100);
keyboardDev.keyReleaseAll();
}
delay(1000);
OSD.createBitmap(CHANNELVID);
OSD.update(CHANNELVID);
}
// User callback function for post processing of face recognition results
void FRPostProcess(std::vector<FaceRecognitionResult> results)
{
uint16_t im_h = configVID.height();
uint16_t im_w = configVID.width();
printf("Total number of faces detected = %d\r\n", facerecog.getResultCount());
OSD.createBitmap(CHANNELVID);
if (facerecog.getResultCount() > 0 && !unLock) {
if (facerecog.getResultCount() > 1) { // Door remain close when more than one face detected
unLock = false;
} else {
FaceRecognitionResult face = results[0];
if (String(face.name()) == String("unknown")) { // Door remain close when unknown face detected
unLock = false;
} else { // Door open when a single registered face detected
unLock = true;
}
}
}
for (int i = 0; i < facerecog.getResultCount(); i++) {
FaceRecognitionResult item = results[i];
// Result coordinates are floats ranging from 0.00 to 1.00
// Multiply with RTSP resolution to get coordinates in pixels
int xmin = (int)(item.xMin() * im_w);
int xmax = (int)(item.xMax() * im_w);
int ymin = (int)(item.yMin() * im_h);
int ymax = (int)(item.yMax() * im_h);
uint32_t osd_color;
// Draw boundary box
if (String(item.name()) == String("unknown")) {
osd_color = OSD_COLOR_RED;
} else {
osd_color = OSD_COLOR_GREEN;
}
printf("Face %d name %s:\t%d %d %d %d\n\r", i, item.name(), xmin, xmax, ymin, ymax);
OSD.drawRect(CHANNELVID, xmin, ymin, xmax, ymax, 3, osd_color);
// Print identification text above boundary box
char text_str[40];
snprintf(text_str, sizeof(text_str), "Face:%s", item.name());
OSD.drawText(CHANNELVID, xmin, ymin - OSD.getTextHeight(CHANNELVID), text_str, osd_color);
}
OSD.update(CHANNELVID);
}
目前实现的功能比较简单。电脑密码和WIFI信息都保存在了代码中这样不够灵活。
代码中也尝试将密码写到tf卡中。不过没有卡暂时没办法测试。
项目参考:
【教程】小安派BW21-CBV-Kit——人脸识别门锁
【教程】小安派BW21-CBV-Kit——BLE HID 键盘
基本上官方提供的案例很多都功能窦涵盖了。
四、问题整理
关于开发板
1、BW21-CBV-Kit开发板发热量较大
2、麦克风做成插座(会不会更好一些)
3、TF卡槽和PCB对齐做外壳方便插拔卡
目前这个项目程序试玩可以真正用起来还有很多优化的地方。
1、密码、WIFI信息、人脸信息等持久化保存
2、注册方式需要优化
3、PC与开发板状态同步,解锁后通知开发板已解锁。锁定后通知开发板已锁定。
小安派BW21-CBV-Kit:实现丐版Windows Hello