iOS BLE藍牙開發數據傳輸協議詳解 經常使用算法(AES加密 HMAC_hash PRF)

前言

這段時間參與了一款與藍牙外設交互的項目, 之前沒有涉及過數據傳輸方面的開發, 踩了很多坑, 同時也學到了不少東西. 此時, 項目也即將進入尾聲, 有時間把這些記錄一二. 本人才疏學淺, 若有錯誤,大佬輕噴.前端

BLE4.0開發

這方面網上的Demo一大堆, 暫時不作太多的贅述, 只對坑點作一個摘要.git

  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.github

// 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算法的實現原理:bash

PRF算法

實現以下:app

+ (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;
    }
}
複製代碼

附上 源碼Demoui

相關文章
相關標籤/搜索