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, 让硬件工程师换一个字段再试试, 各个厂家的蓝牙模块不一样, 很可能硬件工程师写错了)

数据传输

首先是平台方面的人定好了数据传输协议, 我们按协议进行拼接, 然后使用拼接好的数据与外设进行交互. 数据传输协议一般分为包头和包体, 包体中也许还会进行类似的嵌套. 协议中会定义传输 的数据类型, 比如拼接过程中需要传入包体的长度(无符号双字节整型), 我们一般会用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, 这边对两种类型的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

  • 1
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
以下是一个简单的安卓向Ble蓝牙4.2协议设备传输数据完整用例: 1. 初始化Ble蓝牙适配器并检查设备是否支持Ble蓝牙。 2. 扫描周围的Ble设备并连接到所需的设备。 3. 发现所需的服务和特征。 4. 启用所需的通知特征以接收数据。 5. 发送数据到写入特征。 6. 监听接收到的数据并进行处理。 7. 断开连接。 下面是一个示例代码片段: ```java // 导入必要的类和库 import android.bluetooth.BluetoothAdapter; import android.bluetooth.BluetoothDevice; import android.bluetooth.BluetoothGatt; import android.bluetooth.BluetoothGattCallback; import android.bluetooth.BluetoothGattCharacteristic; import android.bluetooth.BluetoothGattService; import android.bluetooth.BluetoothManager; import android.content.Context; import java.util.UUID; // 初始化Ble蓝牙适配器并检查设备是否支持Ble蓝牙 final BluetoothManager bluetoothManager = (BluetoothManager) getSystemService(Context.BLUETOOTH_SERVICE); final BluetoothAdapter bluetoothAdapter = bluetoothManager.getAdapter(); if (bluetoothAdapter == null || !bluetoothAdapter.isEnabled()) { // 蓝牙不可用 return; } // 设置所需的UUID和地址 final UUID serviceUuid = UUID.fromString("0000180f-0000-1000-8000-00805f9b34fb"); final UUID charUuid = UUID.fromString("00002a19-0000-1000-8000-00805f9b34fb"); final String deviceAddress = "00:11:22:33:44:55"; // 扫描设备并连接到所需的设备 final BluetoothDevice device = bluetoothAdapter.getRemoteDevice(deviceAddress); final BluetoothGatt gatt = device.connectGatt(this, false, new BluetoothGattCallback() { @Override public void onConnectionStateChange(BluetoothGatt gatt, int status, int newState) { if (newState == BluetoothGatt.STATE_CONNECTED) { // 连接成功后发现所需的服务和特征 gatt.discoverServices(); } else if (newState == BluetoothGatt.STATE_DISCONNECTED) { // 断开连接 gatt.close(); } } @Override public void onServicesDiscovered(BluetoothGatt gatt, int status) { if (status == BluetoothGatt.GATT_SUCCESS) { // 发现所需的服务和特征 final BluetoothGattService service = gatt.getService(serviceUuid); final BluetoothGattCharacteristic characteristic = service.getCharacteristic(charUuid); // 启用通知特征以接收数据 gatt.setCharacteristicNotification(characteristic, true); final BluetoothGattDescriptor descriptor = characteristic.getDescriptor(UUID.fromString("00002902-0000-1000-8000-00805f9b34fb")); descriptor.setValue(BluetoothGattDescriptor.ENABLE_NOTIFICATION_VALUE); gatt.writeDescriptor(descriptor); // 发送数据到写入特征 characteristic.setValue("Hello, Ble!".getBytes()); gatt.writeCharacteristic(characteristic); } } @Override public void onCharacteristicChanged(BluetoothGatt gatt, BluetoothGattCharacteristic characteristic) { // 监听接收到的数据并进行处理 final byte[] data = characteristic.getValue(); final String message = new String(data); System.out.println("Received message: " + message); } }); // 断开连接 gatt.disconnect(); ``` 在上面的示例中,我们使用了Java的Android SDK来实现安卓向Ble蓝牙4.2协议设备传输数据完整用例。我们首先使用BluetoothManager和BluetoothAdapter类初始化Ble蓝牙适配器并检查设备是否支持Ble蓝牙。接下来,我们使用BluetoothAdapter类扫描周围的设备,并使用connectGatt()方法连接到所需的设备。在连接成功后,我们使用discoverServices()方法发现所需的服务和特征,并使用setCharacteristicNotification()和writeDescriptor()方法启用通知特征以接收数据。接下来,我们使用setValue()和writeCharacteristic()方法发送数据到写入特征。最后,我们使用disconnect()方法断开连接。

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值