iOS|iOS BLE蓝牙开发数据传输协议详解 常用算法(AES加密 HMAC_hash PRF)

前言 这段时间参与了一款与蓝牙外设交互的项目, 以前没有涉及过数据传输方面的开发, 踩了不少坑, 同时也学到了很多东西. 此时, 项目也即将进入尾声, 有时间把这些记录一二. 本人才疏学浅, 如有错误,大佬轻喷.
BLE4.0开发 这方面网上的Demo一大堆, 暂时不做太多的赘述, 只对坑点做一个摘要.

  1. 需求使然, 要对设备的接近远离有一个比较精确的计算, 使用的方案是对蓝牙的信号强度进行分析. 然而, 信号强度的波动值较大, 很难得出较为精确的值, 于是乎需要较多的信号值进行计算, iOS可以通过[self.peripheral readRSSI]来读取信号值强度, 但是该方法最快1s只能返回一次, 如果需要更快速的获取信号值强度, 执行scanForPeripheralsWithServices方法设置options参数@{CBCentralManagerScanOptionAllowDuplicatesKey:@(YES)}, 也许iBeacon会是个不错的选择, 但是这边硬件并不支持,也没有进行实际的测试.
  2. 获取蓝牙外设mac地址的问题, 众所周知的隐私问题, 目前iOS并不能获取到. 解决方法是让硬件工程师把mac地址写入到广播包中的kCBAdvDataManufacturerData这个key中,在发现外设的回调centralManager: didDiscoverPeripheral: advertisementData: RSSI:中的advertisementData参数中获取. (一定要写在对应kCBAdvDataManufacturerData的字段中, 发现该设备广播包中没有这个key, 让硬件工程师换一个字段再试试, 各个厂家的蓝牙模块不一样, 很可能硬件工程师写错了)
数据传输 【iOS|iOS BLE蓝牙开发数据传输协议详解 常用算法(AES加密 HMAC_hash PRF)】首先是平台方面的人定好了数据传输协议, 我们按协议进行拼接, 然后使用拼接好的数据与外设进行交互. 数据传输协议一般分为包头和包体, 包体中也许还会进行类似的嵌套. 协议中会定义传输 的数据类型, 比如拼接过程中需要传入包体的长度(无符号双字节整型), 我们一般会用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 = https://www.it610.com/article/[[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= https://www.it610.com/article/[[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 = https://www.it610.com/article/[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, 这边对两种类型的keyiv都做了实现, 按实际情景使用.
- (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]; // iv char 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 | kCCOptionECBMode ECB模式iv向量填NULL kCCOptionPKCS7Padding: 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

    推荐阅读