前言
这段时间参与了一款与蓝牙外设交互的项目, 以前没有涉及过数据传输方面的开发, 踩了不少坑, 同时也学到了很多东西. 此时, 项目也即将进入尾声, 有时间把这些记录一二. 本人才疏学浅, 如有错误,大佬轻喷.
BLE4.0开发
这方面网上的Demo一大堆, 暂时不做太多的赘述, 只对坑点做一个摘要.
需求使然, 要对设备的接近远离有一个比较精确的计算, 使用的方案是对蓝牙的信号强度进行分析. 然而, 信号强度的波动值较大, 很难得出较为精确的值, 于是乎需要较多的信号值进行计算, iOS可以通过[self.peripheral readRSSI]
来读取信号值强度, 但是该方法最快1s只能返回一次, 如果需要更快速的获取信号值强度, 执行scanForPeripheralsWithServices
方法设置options参数@{CBCentralManagerScanOptionAllowDuplicatesKey:@(YES)}
, 也许iBeacon会是个不错的选择, 但是这边硬件并不支持,也没有进行实际的测试.获取蓝牙外设mac地址的问题, 众所周知的隐私问题, 目前iOS并不能获取到. 解决方法是让硬件工程师把mac地址写入到广播包中的kCBAdvDataManufacturerData
这个key
中,在发现外设的回调centralManager: didDiscoverPeripheral: advertisementData: RSSI:
中的advertisementData
参数中获取. (一定要写在对应kCBAdvDataManufacturerData
的字段中, 发现该设备广播包中没有这个key
, 让硬件工程师换一个字段再试试, 各个厂家的蓝牙模块不一样, 很可能硬件工程师写错了)
数据传输
首先是平台方面的人定好了数据传输协议, 我们按协议进行拼接, 然后使用拼接好的数据与外设进行交互.
数据传输协议一般分为包头和包体, 包体中也许还会进行类似的嵌套. 协议中会定义传输 的数据类型, 比如拼接过程中需要传入包体的长度(无符号双字节整型), 我们一般会用int
取到长度length, 这时候需要把int
转化为两个Byte.
// int转两个字节Byte+ (NSData *)dataFromShort:(short)value {Byte bytes[2] = {};for (int i = 0; i < 2; i++) {int offset = 16 - (i + 1) * 8;bytes[i] = (Byte) ((value >> offset) & 0xff);}NSData *data = [[NSData alloc] initWithBytes:bytes length:2];return data;}// int转四个字节Byte+ (NSData *)dataFromInt:(int)value {Byte bytes[4] = {};for (int i = 0; i < 4; i++) {bytes[i] = (Byte)(value >> (24 - i * 8));}NSData *data= [[NSData alloc] initWithBytes:bytes length:4];return data;}
更多:
一般也会有时间戳, 需要拼接这里提供两种格式的时间戳.
// 6个字节的时间戳+ (NSData *)currentTimeData {Byte bytes[6];NSCalendar *calendar = [NSCalendar currentCalendar];NSUInteger unitFlags = NSCalendarUnitYear | NSCalendarUnitMonth | NSCalendarUnitDay | NSCalendarUnitHour | NSCalendarUnitMinute | NSCalendarUnitSecond;NSDateComponents *dateComponent = [calendar components:unitFlags fromDate:[NSDate date]];int year =(int) [dateComponent year];int month = (int) [dateComponent month];int day = (int) [dateComponent day];int hour = (int) [dateComponent hour];int minute = (int) [dateComponent minute];int second = (int) [dateComponent second];bytes[0] = year - 2000;bytes[1] = month;bytes[2] = day;bytes[3] = hour;bytes[4] = minute;bytes[5] = second;return [[NSData alloc] initWithBytes:bytes length:6];}// 4个字节的时间戳+ (NSData *)timestampData {int time = [[NSDate date] timeIntervalSince1970];return [NSData dataFromInt:time];}
常用算法
PRF算法
首先来看一下PRF算法,这个之前一直想在网上download
一份, 奈何实在没有找到. 猜测是前端一般不会用到, 安卓同事倒是从平台处得到了封装好的jar包可以使用. iOS这边只能自己动手实现, 下面先看一下PRF算法的实现原理:
实现如下:
+ (NSData *)tf_prfSecret:(NSData *)secret label:(NSData *)label seed:(NSData *)seed {// 讲label与seed进行拼接NSMutableData *seedData = [NSMutableData data];[seedData appendData:label];[seedData appendData:seed];return [self tf_prfSecret:secret seed:seedData];}+ (NSData *)tf_prfSecret:(NSData *)secret seed:(NSData *)seed {NSMutableData *prfData = [NSMutableData data];NSMutableData *mutableData = [NSMutableData dataWithData:seed];NSData *AnData = [NSData dataWithData:seed];// 需要prf算法得出的长度// kStaticPrfMinimumLength: 根据需求需要写入while (prfData.length < kStaticPrfMinimumLength) {AnData = [self hmacSHA256WithSecret:secret content:AnData];mutableData = [NSMutableData dataWithData:AnData];[mutableData appendData:seed];NSData *hmacData = [self hmacSHA256WithSecret:secret content:mutableData];[prfData appendData:hmacData];}return prfData;}
HMAC_SHA256算法
PRF算法中, HMAC_hash算法是可选的, 这边使用的是SHA256, 实现如下:
// hmac sha256算法+ (NSData *)hmacSHA256WithSecret:(NSData *)secret content:(NSData *)content {unsigned char cHMAC[CC_SHA256_DIGEST_LENGTH];CCHmac(kCCHmacAlgSHA256, secret.bytes, secret.length, content.bytes, content.length, cHMAC);NSData *HMACData = [NSData dataWithBytes:cHMAC length:sizeof(cHMAC)];// 将data按string输出// const unsigned char *buffer = (const unsigned char *)[HMACData bytes];// NSMutableString *HMAC = [NSMutableString stringWithCapacity:HMACData.length * 2];// for (int i = 0; i < HMACData.length; ++i){// [HMAC appendFormat:@"%02x", buffer[i]];return HMACData;}
AES128加密
网上这样的加密真是一大堆,这边因为是与硬件数据传输, 所以对数据进行的加密的密钥与iv
向量也大概率是直接便是Data而不是常用的NSString, 这边对两种类型的key
与iv
都做了实现, 按实际情景使用.
- (NSData *)tf_encryptAES128WithKey:(NSString *)key iv:(NSString *)iv {return [self tf_AES128Operation:kCCEncrypt key:key iv:iv];}- (NSData *)tf_decryptAES128WithKey:(NSString *)key iv:(NSString *)iv {return [self tf_AES128Operation:kCCDecrypt key:key iv:iv];}- (NSData *)tf_encryptAES128WithKeyData:(NSData *)keyData ivData:(NSData *)ivData {return [self tf_AES128Operation:kCCEncrypt keyData:keyData ivData:ivData];}- (NSData *)tf_decryptAES128WithKeyData:(NSData *)keyData ivData:(NSData *)ivData {return [self tf_AES128Operation:kCCDecrypt keyData:keyData ivData:ivData];}/**** @param operation kCCEncrypt:加密 kCCDecrypt:解密* @param key 公钥* @param iv 偏移量** @return 加密或者解密的NSData*/- (NSData *)tf_AES128Operation:(CCOperation)operation key:(NSString *)key iv:(NSString *)iv {char keyBytes[kCCKeySizeAES128 + 1]; //kCCKeySizeAES128是加密位数 可以替换成256位的// bzero函数:从字符串第一位开始置0, 第二个参数代表置0的位数// 相当于memset(keyBytes,0x00,sizeof(keyBytes));bzero(keyBytes, sizeof(keyBytes));[key getCString:keyBytes maxLength:sizeof(keyBytes) encoding:NSUTF8StringEncoding];// ivchar ivBytes[kCCBlockSizeAES128 + 1];bzero(ivBytes, sizeof(ivBytes));[iv getCString:ivBytes maxLength:sizeof(ivBytes) encoding:NSUTF8StringEncoding];return [self tf_cryptAES128Operation:operation keyBytes:keyBytes ivBytes:ivBytes];}- (NSData *)tf_AES128Operation:(CCOperation)operation keyData:(NSData *)keyData ivData:(NSData *)ivData {char keyBytes[kCCKeySizeAES128 + 1];bzero(keyBytes, sizeof(keyBytes));[keyData getBytes:keyBytes length:sizeof(keyBytes)];char ivBytes[kCCKeySizeAES128 + 1];bzero(ivBytes, sizeof(ivBytes));[ivData getBytes:ivBytes length:sizeof(ivBytes)];return [self tf_cryptAES128Operation:operation keyBytes:keyBytes ivBytes:ivBytes];}- (NSData *)tf_cryptAES128Operation:(CCOperation)operation keyBytes:(void *)keyBytes ivBytes:(void *)ivBytes {size_t bufferSize = self.length + kCCBlockSizeAES128;void *buffer = malloc(bufferSize);size_t numBytesCrypted = 0;/*CCOptions 默认为CBC加密选择ECB加密填: kCCOptionPKCS7Padding | kCCOptionECBModeECB模式iv向量填NULLkCCOptionPKCS7Padding: 7填充直接填0x0000: 就是No padding填充*/CCCryptorStatus cryptorStatus = CCCrypt(operation, kCCAlgorithmAES128,kCCOptionPKCS7Padding,keyBytes,kCCKeySizeAES128,ivBytes,self.bytes,self.length,buffer,bufferSize,&numBytesCrypted);if(cryptorStatus == kCCSuccess) {NSLog(@"Crypt Successfully");NSData *result = [NSData dataWithBytesNoCopy:buffer length:numBytesCrypted];/* 转16进制字符串Byte *resultBytes = (Byte *)result.bytes;NSMutableString *outPut = [[NSMutableString alloc] initWithCapacity:result.length * 2];for (int i = 0; i < result.length; i++) {[outPut appendFormat:@"%02x", resultBytes[i]];}*/return result;} else {NSLog(@"Crypt Error");free(buffer);return nil;}}
附上 源码Demo
如果觉得《iOS BLE蓝牙开发数据传输协议详解 常用算法(AES加密 HMAC_hash PRF)》对你有帮助,请点赞、收藏,并留下你的观点哦!