1.项目目标及意义
2.硬件准备和硬件驱动实现
3.软件和服务器实现
4.通信安全设计
5.总结
识别功能: | 蓝牙开发,WIFI开发,人脸识别开发, |
---|---|
网络功能: | 网络连接,加密通信 |
本地存储功能: | 设备端保护用户个人信息,服务器只能收到密文或部分明文 |
安全功能: | 加密算法,用户隐私保护 |
包括蓝牙模块(TB03F)、wifi模块(esp8266)、门禁控制器(电插锁+继电器),RT1062开发板(带摄像头),
用于RT1062开发板和手机微信小程序的直接通信,传输开门指令。
代码使用了安信可提供的模块透传代码,即蓝牙接收到的数据直接发送给RT1062开发板。数据传输逻辑如下图所示
模块烧录软件TB-TOOL和透传固件从安信可官网获取
https://docs.ai-thinker.com/blue_tooth
Wi-Fi模块(esp8266)
用途:Wi-Fi模块与路由器连接,实现RT1062开发板上云,实现网络连接,方便后续通过微信小程序与服务器控制开发板。
Wi-Fi模块如下图所示:
通过VSCODE和PlatformIO插件的方式,实现了对Wi-Fi模块二次开发,
PlatformIO的框架地址:
https://github.com/platformio
PlatformIO的下载安装方式:
在VSCODE的插件市场下载即可。
基础的使用教程查看官网:
https://docs.platformio.org/en/latest/
在Wi-Fi模块上实现MQTT协议,和透传功能,具体流程如下图所示
/*
Basic ESP8266 MQTT example
This sketch demonstrates the capabilities of the pubsub library in combination
with the ESP8266 board/library.
It connects to an MQTT server then:
- publishes "hello world" to the topic "outTopic" every two seconds
- subscribes to the topic "inTopic", printing out any messages
it receives. NB - it assumes the received payloads are strings not binary
- If the first character of the topic "inTopic" is an 1, switch ON the ESP Led,
else switch it off
It will reconnect to the server if the connection is lost using a blocking
reconnect function. See the 'mqtt_reconnect_nonblocking' example for how to
achieve the same result without blocking the main loop.
To install the ESP8266 board, (using Arduino 1.6.4+):
- Add the following 3rd party board manager under "File -> Preferences -> Additional Boards Manager URLs":
http://arduino.esp8266.com/stable/package_esp8266com_index.json
- Open the "Tools -> Board -> Board Manager" and click install for the ESP8266"
- Select your ESP8266 in "Tools -> Board"
*/
#include <DNSServer.h>
#include <ESP8266WebServer.h>
#include <ESP8266WiFi.h>
#include <PubSubClient.h>
// Update these with values suitable for your network.
//mqtt连接历程
// const char *ssid = "babybed";
// const char *password = "babybed123";
const char *ssid = "access_control";
const char *password = "access123!@#";
const char *mqtt_server = "118.178.180.56";
// const char *pub_topic = "/babybed_status";
// const char *sub_topic = "/babybed_command";
const char *pub_topic = "/Access_control_system_device";
const char *sub_topic = "/Access_control_system_command";
WiFiClient espClient;
PubSubClient client(espClient);
unsigned long lastMsg = 0;
#define MSG_BUFFER_SIZE (80)
char msg[MSG_BUFFER_SIZE];
int value = 0;
#define LED_BUILTIN 0
void setup_wifi()
{
delay(10);
// We start by connecting to a WiFi network
Serial.println();
Serial.print("Connecting to ");
Serial.println(ssid);
WiFi.mode(WIFI_STA);
WiFi.begin(ssid, password);
while (WiFi.status() != WL_CONNECTED)
{
delay(500);
Serial.print(".");
}
//randomSeed(micros());
Serial.println("");
Serial.println("WiFi connected");
Serial.println("IP address: ");
Serial.println(WiFi.localIP());
}
void callback(char *topic, byte *payload, int length)
{
// Serial.print("Message arrived [");
// Serial.print(sub_topic);
// Serial.print("] ");
for (int i = 0; i < length; i++)
{
Serial.print((char)payload[i]);
}
Serial.println();
}
void reconnect()
{
// Loop until we're reconnected
while (!client.connected())
{
Serial.print("Attempting MQTT connection...");
// Create a random client ID
String clientId = "ESP8266Client-";
clientId += String(random(0xffff), HEX);
// Attempt to connect
if (client.connect(clientId.c_str()))
{
Serial.println("connected");
// Once connected, publish an announcement...
client.publish(pub_topic, "hello world");
// ... and resubscribe
client.subscribe(sub_topic);
}
else
{
Serial.print("failed, rc=");
Serial.print(client.state());
Serial.println(" try again in 5 seconds");
// Wait 5 seconds before retrying
delay(5000);
}
}
}
void setup()
{
//pinMode(BUILTIN_LED, OUTPUT); // Initialize the BUILTIN_LED pin as an output
Serial.begin(115200);
setup_wifi();
client.setServer(mqtt_server, 1883);
//client.subscribe("/test1");此处订阅没用,因为未连接mqtt服务器。
client.setCallback(callback);
//test
}
String RXString = "";
int count = 0;
int flag = 0;
char c[80]="";
void loop()
{
if (!client.connected())
{
reconnect();
}
client.loop();
while (Serial.available() > 0) //判断
{
RXString += char(Serial.read()); //https://www.cnblogs.com/anandexuechengzhangzhilu/p/10719546.html
delay(2);
wdt_reset();//feed watchdog https://www.cnblogs.com/Mysterious/p/4817054.html
}
if (RXString.length() > 0)
{
//Serial.println(RXString); //打印接收到的字符
strcpy(c,RXString.c_str());//https://blog.csdn.net/dagjj/article/details/108185706
//snprintf(msg, MSG_BUFFER_SIZE, "head%s", c); //client.publish的payload不能为string,通过snprintf处理即可。添加了报头报尾
snprintf(msg, MSG_BUFFER_SIZE, "%s", c);
client.publish(pub_topic, msg);
RXString = "";
wdt_reset();
}
}
通过上述代码,实现了80位字节大小以内的数据透传任务。
代码
#defineMSG_BUFFER_SIZE(80)
用来控制数据透传的长度,设置为变量可以节约空间,可以根据数据和指令长度灵活修改。
开发方式使用MCUXpressoIDE,按照教程移植了TencentOS Tiny内核。
项目工程代码如下
https://share.weiyun.com/P3PhAyxI
使用了SM4.c文件对来自蓝牙和串口的数据加密
SM4代码如下
#include "string.h"
#include <stdio.h>
#include <stdlib.h>
#include "sm4.h"
#define u32 unsigned long
const u32 TBL_SYS_PARAMS[4] = {
0xa3b1bac6,
0x56aa3350,
0x677d9197,
0xb27022dc};
/******************************??????CK???****************************************/
const u32 TBL_FIX_PARAMS[32] = {
0x00070e15, 0x1c232a31, 0x383f464d, 0x545b6269,
0x70777e85, 0x8c939aa1, 0xa8afb6bd, 0xc4cbd2d9,
0xe0e7eef5, 0xfc030a11, 0x181f262d, 0x343b4249,
0x50575e65, 0x6c737a81, 0x888f969d, 0xa4abb2b9,
0xc0c7ced5, 0xdce3eaf1, 0xf8ff060d, 0x141b2229,
0x30373e45, 0x4c535a61, 0x686f767d, 0x848b9299,
0xa0a7aeb5, 0xbcc3cad1, 0xd8dfe6ed, 0xf4fb0209,
0x10171e25, 0x2c333a41, 0x484f565d, 0x646b7279};
/******************************SBox????****************************************/
const u8 TBL_SBOX[256] = {
0xd6, 0x90, 0xe9, 0xfe, 0xcc, 0xe1, 0x3d, 0xb7, 0x16, 0xb6, 0x14, 0xc2, 0x28, 0xfb, 0x2c, 0x05,
0x2b, 0x67, 0x9a, 0x76, 0x2a, 0xbe, 0x04, 0xc3, 0xaa, 0x44, 0x13, 0x26, 0x49, 0x86, 0x06, 0x99,
0x9c, 0x42, 0x50, 0xf4, 0x91, 0xef, 0x98, 0x7a, 0x33, 0x54, 0x0b, 0x43, 0xed, 0xcf, 0xac, 0x62,
0xe4, 0xb3, 0x1c, 0xa9, 0xc9, 0x08, 0xe8, 0x95, 0x80, 0xdf, 0x94, 0xfa, 0x75, 0x8f, 0x3f, 0xa6,
0x47, 0x07, 0xa7, 0xfc, 0xf3, 0x73, 0x17, 0xba, 0x83, 0x59, 0x3c, 0x19, 0xe6, 0x85, 0x4f, 0xa8,
0x68, 0x6b, 0x81, 0xb2, 0x71, 0x64, 0xda, 0x8b, 0xf8, 0xeb, 0x0f, 0x4b, 0x70, 0x56, 0x9d, 0x35,
0x1e, 0x24, 0x0e, 0x5e, 0x63, 0x58, 0xd1, 0xa2, 0x25, 0x22, 0x7c, 0x3b, 0x01, 0x21, 0x78, 0x87,
0xd4, 0x00, 0x46, 0x57, 0x9f, 0xd3, 0x27, 0x52, 0x4c, 0x36, 0x02, 0xe7, 0xa0, 0xc4, 0xc8, 0x9e,
0xea, 0xbf, 0x8a, 0xd2, 0x40, 0xc7, 0x38, 0xb5, 0xa3, 0xf7, 0xf2, 0xce, 0xf9, 0x61, 0x15, 0xa1,
0xe0, 0xae, 0x5d, 0xa4, 0x9b, 0x34, 0x1a, 0x55, 0xad, 0x93, 0x32, 0x30, 0xf5, 0x8c, 0xb1, 0xe3,
0x1d, 0xf6, 0xe2, 0x2e, 0x82, 0x66, 0xca, 0x60, 0xc0, 0x29, 0x23, 0xab, 0x0d, 0x53, 0x4e, 0x6f,
0xd5, 0xdb, 0x37, 0x45, 0xde, 0xfd, 0x8e, 0x2f, 0x03, 0xff, 0x6a, 0x72, 0x6d, 0x6c, 0x5b, 0x51,
0x8d, 0x1b, 0xaf, 0x92, 0xbb, 0xdd, 0xbc, 0x7f, 0x11, 0xd9, 0x5c, 0x41, 0x1f, 0x10, 0x5a, 0xd8,
0x0a, 0xc1, 0x31, 0x88, 0xa5, 0xcd, 0x7b, 0xbd, 0x2d, 0x74, 0xd0, 0x12, 0xb8, 0xe5, 0xb4, 0xb0,
0x89, 0x69, 0x97, 0x4a, 0x0c, 0x96, 0x77, 0x7e, 0x65, 0xb9, 0xf1, 0x09, 0xc5, 0x6e, 0xc6, 0x84,
0x18, 0xf0, 0x7d, 0xec, 0x3a, 0xdc, 0x4d, 0x20, 0x79, 0xee, 0x5f, 0x3e, 0xd7, 0xcb, 0x39, 0x48};
//4???????????long?
void four_uCh2uLong(u8 *in, u32 *out)
{
int i = 0;
*out = 0;
for (i = 0; i < 4; i++)
*out = ((u32)in[i] << (24 - i * 8)) ^ *out;
}
//???long??4???????
void uLong2four_uCh(u32 in, u8 *out)
{
int i = 0;
//?32?unsigned long??????
for (i = 0; i < 4; i++)
*(out + i) = (u32)(in >> (24 - i * 8));
}
//??,?????????
u32 move(u32 data, int length)
{
u32 result = 0;
result = (data << length) ^ (data >> (32 - length));
return result;
}
//??????,???Sbox???????,??????L???L'
u32 func_key(u32 input)
{
int i = 0;
u32 ulTmp = 0;
u8 ucIndexList[4] = {0};
u8 ucSboxValueList[4] = {0};
uLong2four_uCh(input, ucIndexList);
for (i = 0; i < 4; i++)
{
ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
}
four_uCh2uLong(ucSboxValueList, &ulTmp);
ulTmp = ulTmp ^ move(ulTmp, 13) ^ move(ulTmp, 23);
return ulTmp;
}
//?????????,???Sbox???????,???????L
u32 func_data(u32 input)
{
int i = 0;
u32 ulTmp = 0;
u8 ucIndexList[4] = {0};
u8 ucSboxValueList[4] = {0};
uLong2four_uCh(input, ucIndexList);
for (i = 0; i < 4; i++)
{
ucSboxValueList[i] = TBL_SBOX[ucIndexList[i]];
}
four_uCh2uLong(ucSboxValueList, &ulTmp);
ulTmp = ulTmp ^ move(ulTmp, 2) ^ move(ulTmp, 10) ^ move(ulTmp, 18) ^ move(ulTmp, 24);
return ulTmp;
}
//????(??????????,16???????,?????0??16??????)
//len:????(??????) key:??(16??) input:??????? output:???????
void encode_fun(u8 len, u8 *key, u8 *input, u8 *output)
{
int i = 0, j = 0;
u8 *p = (u8 *)malloc(50); //????50?????
u32 ulKeyTmpList[4] = {0}; //?????u32??
u32 ulKeyList[36] = {0}; //?????????????FK????????
u32 ulDataList[36] = {0}; //????????
/***************************???????********************************************/
four_uCh2uLong(key, &(ulKeyTmpList[0]));
four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));
ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];
for (i = 0; i < 32; i++) //32???????
{
//5-36?32????
ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
}
/***********************************??32?32???????**********************************/
for (i = 0; i < len; i++) //????????p???
*(p + i) = *(input + i);
for (i = 0; i < 16 - len % 16; i++) //???16??0??16????
*(p + len + i) = 0;
for (j = 0; j < len / 16 + ((len % 16) ? 1 : 0); j++) //??????,?????????(????????16???????,????,??16???????,17???0?32?????????,????)
{
/*????????*/
four_uCh2uLong(p + 16 * j, &(ulDataList[0]));
four_uCh2uLong(p + 16 * j + 4, &(ulDataList[1]));
four_uCh2uLong(p + 16 * j + 8, &(ulDataList[2]));
four_uCh2uLong(p + 16 * j + 12, &(ulDataList[3]));
//??
for (i = 0; i < 32; i++)
{
ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[i + 4]);
}
/*????????*/
uLong2four_uCh(ulDataList[35], output + 16 * j);
uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
}
free(p);
}
//????(?????????,???????????,???????????)
//len:???? key:?? input:???????? output:????????
void decode_fun(u8 len, u8 *key, u8 *input, u8 *output)
{
int i = 0, j = 0;
u32 ulKeyTmpList[4] = {0}; //?????u32??
u32 ulKeyList[36] = {0}; //?????????????FK????????
u32 ulDataList[36] = {0}; //????????
/*???????*/
four_uCh2uLong(key, &(ulKeyTmpList[0]));
four_uCh2uLong(key + 4, &(ulKeyTmpList[1]));
four_uCh2uLong(key + 8, &(ulKeyTmpList[2]));
four_uCh2uLong(key + 12, &(ulKeyTmpList[3]));
ulKeyList[0] = ulKeyTmpList[0] ^ TBL_SYS_PARAMS[0];
ulKeyList[1] = ulKeyTmpList[1] ^ TBL_SYS_PARAMS[1];
ulKeyList[2] = ulKeyTmpList[2] ^ TBL_SYS_PARAMS[2];
ulKeyList[3] = ulKeyTmpList[3] ^ TBL_SYS_PARAMS[3];
for (i = 0; i < 32; i++) //32???????
{
//5-36?32????
ulKeyList[i + 4] = ulKeyList[i] ^ func_key(ulKeyList[i + 1] ^ ulKeyList[i + 2] ^ ulKeyList[i + 3] ^ TBL_FIX_PARAMS[i]);
}
/*??32?32???????*/
for (j = 0; j < len / 16; j++) //??????,?????????
{
/*????????*/
four_uCh2uLong(input + 16 * j, &(ulDataList[0]));
four_uCh2uLong(input + 16 * j + 4, &(ulDataList[1]));
four_uCh2uLong(input + 16 * j + 8, &(ulDataList[2]));
four_uCh2uLong(input + 16 * j + 12, &(ulDataList[3]));
//??
for (i = 0; i < 32; i++)
{
ulDataList[i + 4] = ulDataList[i] ^ func_data(ulDataList[i + 1] ^ ulDataList[i + 2] ^ ulDataList[i + 3] ^ ulKeyList[35 - i]); //??????????????????
}
/*????????*/
uLong2four_uCh(ulDataList[35], output + 16 * j);
uLong2four_uCh(ulDataList[34], output + 16 * j + 4);
uLong2four_uCh(ulDataList[33], output + 16 * j + 8);
uLong2four_uCh(ulDataList[32], output + 16 * j + 12);
}
}
//????????16????
void print_hex(u8 *data, int len)
{
int i = 0;
char alTmp[16] = {'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'a', 'b', 'c', 'd', 'e', 'f'};
for (i = 0; i < len; i++)
{
printf("%c", alTmp[data[i] / 16]);
printf("%c", alTmp[data[i] % 16]);
putchar(' ');
}
putchar('\n');
}
char HexToChar(uint8_t hex)
{
char ch = 0;
if (hex > 0x0f)
return -1;
if ((hex >= 0) && (hex <= 0x09))
ch = hex + '0';
else if ((hex >= 0x0a) && (hex <= 0x0f))
ch = (hex & 0x0f) + ('a' - 0x0a);
return ch;
}
void ArryToString(uint8_t *data, uint8_t data_len, uint8_t *string)
{
uint8_t *p_string = string;
for (uint8_t i = 0; i < data_len; i++)
{
*(p_string++) = HexToChar(data[i] >> 4);
*(p_string++) = HexToChar(data[i] & 0x0f);
}
*p_string = '\0';
}
int StringToHex(char *str, unsigned char *out, unsigned int *outlen)
{
char *p = str;
char high = 0, low = 0;
int tmplen = strlen(p), cnt = 0;
tmplen = strlen(p);
while (cnt < (tmplen / 2))
{
high = ((*p > '9') && ((*p <= 'F') || (*p <= 'f'))) ? *p - 48 - 7 : *p - 48;
low = (*(++p) > '9' && ((*p <= 'F') || (*p <= 'f'))) ? *(p)-48 - 7 : *(p)-48;
out[cnt] = ((high & 0x0f) << 4 | (low & 0x0f));
p++;
cnt++;
}
if (tmplen % 2 != 0)
out[cnt] = ((*p > '9') && ((*p <= 'F') || (*p <= 'f'))) ? *p - 48 - 7 : *p - 48;
if (outlen != NULL)
*outlen = tmplen / 2 + tmplen % 2;
return tmplen / 2 + tmplen % 2;
}
// int main()
// {
// /* Write C code in this online editor and run it. */
// char Plaintext[24] = "I LOVE YOU123456789a212";
// printf("原文:%s \r\n", Plaintext);
// //int len=((sizeof(Plaintext)%16)+1)*16; //获得大于等于明文长度的最小的16的倍数
// char encoderesult[32]={0};
// char strresult[48]={0}; //转换后长度增加,溢出了,
// char decoderesult[32]={0};
// u8 key[16] = {0x6A, 0x79, 0x75, 0x69, 0x6f, 0x74, 0x63, 0x65, 0x6e, 0x74, 0x65, 0x72, 0x30, 0x30, 0x30, 0x30};
// encode_fun(32, key, Plaintext, encoderesult);
// decode_fun(32, key, encoderesult, decoderesult);//第一位变量(长度)必须为16倍数
// ArryToString(encoderesult,sizeof(encoderesult),strresult);
// printf("原文:%s \r\n密文:", Plaintext);
// for (int i = 0; i < 32; i++)
// {
// printf("%x", encoderesult[i]);
// }
// printf("\r\n转换成字符串后:%s\r\n", strresult);
// printf("解密后:%s\r\n", decoderesult);
// return 0;
// }
串口在接收到来自蓝牙的“开锁指令”后,sm4算法加密“开锁指令”,转发到wifi模块,进而上传到服务器。
用途:通过继电器来控制门锁开关
介绍:门锁采用KOB的电吸锁,通电时上锁,断电时开锁,配合继电器即可实现开发板控制门禁。
服务器使用Linux服务器,CentOS系统,通信协议选择MQTT,部署EMQ X开源 MQTT 消息服务器。
基于MQTT协议的相关框架有许多,主流的有EMQX和VerneMQ。二者都具备负载均衡、集群、SSL/TLS、插件等功能。在对比二者之后,由于VerneMQ并非开源软件,考虑到系统后续的灵活性。本项目选择EMQX开发。
EMQX服务器安装部署流程如下:
EMQX安装方式多样,可以采用linux脚本一键部署,在linux系统执行下列命令即可。
curl https://repos.emqx.io/install_emqx.sh | bash
也可以选择包管理器安装,以CentOS为例:
1.$ sudo yum install -y yum-utils device-mapper-persistent-data lvm2
2.$ sudo yum-config-manager --add-repo https://repos.emqx.io/emqx-ce/redhat/centos/7/emqx-ce.repo
3.$ sudo yum install emqx
三步即可完成EMQX在服务器上的安装
安装完成后,在linux系统的终端执行下列命令
$ emqx start
emqx 4.0.0 is started successfully!
$ emqx_ctl status
Node 'emqx@127.0.0.1' is started
emqx v4.0.0 is running
出现上述反馈说明,EMQX已经启动,默认通讯端口18083,也可以在EMQX后台修改。
通过MQTT连接测试工具MQTTX进行测试,在设置中,输入服务器和端口号,在subscribe中输入任意主题,在publish订阅同样的主题,任意输入一段文字,subscribe中出现相同信息,说明EMQX服务器正常运行。
如下图所示:
前端微信小程序包括三个页面:
2.蓝牙搜索连接页面
3.门禁控制页面
在蓝牙连接后进入登陆页面
包括开门和关门按钮,点击按钮后会通过蓝牙,向设备发送开锁指令和关锁指令
系统整体采用加密通讯的方式,使用SM4算法加密,加密通信方式如下:
为了防止开锁命令固定导致的重放攻击,开锁明文即为用户名(在注册用户时确保用户名唯一),将明文传输到开发板后,进行加密,然后将开锁密文发送到服务器进行对比,如果数据对比成功,服务器向设备发送开锁指令
本项目由于时间紧张,最初设想过于发散,导致一些预期功能,如人脸验证,本地周期开关锁等功能,未能实现,未能充分发挥开发板的AI能力。
但是作为一个门禁系统,借助TencentOS Tiny系统,蓝牙、WIFI、MQTT等多种协议,基本功能已经实现,同时使用国密SM4算法保证了系统的安全性和用户隐私。
如果后续有时间的话,会对项目做进一步完善。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。
原创声明:本文系作者授权腾讯云开发者社区发表,未经许可,不得转载。
如有侵权,请联系 cloudcommunity@tencent.com 删除。