蓝牙版的手表与App通讯身份验证
我也不知道这篇文章算什么类型的,不是Android的,也不完全是Web的,更像是一种解决方案。
涉及到App,涉及到Web后台,也涉及到手表端。
要求
- 把协议暴露给第三方开发人员(客户端),但不是任意的App符合协议就可以连接上手表
- 可以控制客户的访问权限授权时长,比如说一年的使用权
思考
问题点在手表不能上网,但又要能识别连接上来的是不是自己授权的app
这种情况其实古代就有,古代通讯不发达,拿着信你看到就知道我什么来头。
首先App与手表连接上以后,需要一个校验的过程,也就是请出示你的通关文碟,这个文碟上有盖章,能证明你的是真的。
在这个基础上,基本上实现的方式就有了。
流程
- 有新客户时,找管理员从后台注册好该客户的App信息。注册完以后,生成应用ID和密钥
- 客户开发时,使用此密钥和应用ID向后台请求一个token,此token的载荷中包含应用信息,谁家的,有效期是多久之类的。
- 跟手表连接上以后,向手表提交此token
- 手表对token进行解析,判断是否在服务有效期内,不在返回结果,在服务器内,生成一个通讯码,之后每次通讯时需要app携带上。
是否有满足前面的需求呢?
可以满足的,手表的后台保留密钥即可,后台生成,手表校验。内容为描述信息,如果被篡改了,那么校验信息就对不上了。
示例
这里我举个例子我就不用Hmac256了,我直接用MD5表演一个给大家看,因为MD5的库我有。
后台创建应用
管理员在后台创建应用,后台生成应用的ID和应用秘钥。把这两个数据给到开发者。
应用启动时,携带应用ID和秘钥向后台发起请求,后台返回token
后台创建token
后台收到客户端的请求,根据应用ID和秘钥,查询这个应用是否在服务器内,如果不在服务器内不创建令牌,直接告诉客户端请续费。如果在服务期内,生成令牌。
令牌生成的方式不唯一,可以参考以下规则:
-
载荷信息(payload):这里我放一个客户名称和是否可用,实际的载荷内容转为base64
{"n":"sob","e":"true","t":"1660099157265"}
n是名称,e是是否可用,t服务器当前的时间,用于变动内容,每次请求都不一样的md5值
-
分割点(.)
-
校验信息(verify):校验信息是对前面内容进行校验,防止篡改
- 校验方式:使用简单的MD5摘要即可加上盐值(key),盐值由手表和后台私有,不公开。具体的校验方式参考后面的示例代码
- 实际校验值为前面5位随机数字和大小写字母+32位MD5+后面3到5位干扰码
示例:
- 载荷信息的base64值
{"n":"sob","e":"true","t":"1660099157265"}
的base64值
eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9
- 后台根据内容生成校验值
生成代码:
import java.util.Base64;
import java.security.MessageDigest;
import java.math.BigInteger;
import java.util.Random;
public class Test{
public static void main(String[] agrs){
Random random = new Random();
//eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9
String salt = "key";
byte[] secretBytes = null;
String content = salt+"eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9"+salt;
try {
secretBytes = MessageDigest.getInstance("md5").digest(content.getBytes());
String md5code = new BigInteger(1, secretBytes).toString(16);
for (int i = 0; i < 32 - md5code.length(); i++) {
md5code = "0" + md5code;
}
int randomSize = random.nextInt(3,5);
System.out.println("randomSize ==> " + randomSize);
String token = "eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9"+"."+getCode(5)+md5code+getCode(randomSize);
System.out.println("token ==> " + token);
} catch (Exception e) {
}
}
public static String getCode(int n) {
char arr[] = new char[n];
int i = 0;
while (i < n) {
char ch = (char) (int) (Math.random() * 124);
if (ch >= 'A' && ch <= 'Z' || ch >= 'a' && ch <= 'z' || ch >= '0' && ch <= '9') {
arr[i++] = ch;
}
}
return new String(arr);
}
}
生成的令牌如下:
eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9.PXcGub736738572c9f4da784ed49f805bd5eelSM9
- 手表对内容进行校验,可以参考附录中的代码
校验成功,说明内容没有被篡改,内容是对的,然后解析出内容里的客户名称和是否可用
把生成的返回给App,App把令牌发送给手表,由手表校验,判断是否跟App进行通讯
手表校验令牌
校验代码:
#include "md5.h"
#include <string.h>
#include <stdio.h>
int main(){
//后台返回的token值
//eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9.PXcGub736738572c9f4da784ed49f805bd5eelSM9
//对内容进行校验PXcGub736738572c9f4da784ed49f805bd5eelSM9实际内容是b736738572c9f4da784ed49f805bd5ee
unsigned char decrypt32[64]={0};
unsigned char decrypt[16]={0};
char temp[8]={0};
int i;
//key
char key[] = "key";
int key_len = strlen(key);
MD5_CTX md5c;
MD5Init(&md5c); //初始化
char content[] = "eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9";
int content_len = strlen(content);
printf("content len ==> %d\n",content_len);
int input_len = key_len+content_len+key_len;
char input_content[input_len];
memcpy(input_content,key,key_len);
memcpy(input_content+key_len,content,content_len);
memcpy(input_content+key_len+content_len,key,key_len);
printf("input content ==> %s \n",input_content);
//生成值
MD5Update(&md5c,(unsigned char *)input_content,input_len);
MD5Final(&md5c,decrypt);
strcpy((char *)decrypt32,"");
//处理结果
for(i=0;i<16;i++){
sprintf(temp,"%02x",decrypt[i]);
strcat((char *)decrypt32,temp);
}
//得到的decrypt32为:b736738572c9f4da784ed49f805bd5ee
//说明eyJuIjoic29iIiwiZSI6InRydWUiLCJ0IjoiMTY2MDA5OTE1NzI2NSJ9没有被篡改,可以使用
//此内容是base64编码,内容为: {"n":"sob","e":"true","t":"1660099157265"}
printf("md5:%s\n",decrypt32);
return 0;
}
你可以发现,校验出来的值是一样的,说明内内容没有篡改,可以把内用解码出来使用。判断到是可用的,进行通讯可以,如果不可用,断开连接就行。
okay,以上就是前天要水的文章了,因为快乐水喝完了就不想写了。如今还有半个多小时下班,把它水完。