對稱加密算法原理--OpenSSL演示、iOS代碼運用及CCCrypt安全隱患

對稱加密算法原理

之前介紹了非對稱加密算法,這篇文章介紹一下在非對稱加密算法出現之前的對稱加密算法,常見的對稱加密算法、終端演示OpenSSL和iOS代碼運用以及CCCrypt的安全隱患等。

對稱加密算法:明文通過密鑰加密得到密文,密文再通過這個密鑰解密得到明文。所以在業務邏輯上相對沒有非對稱加密RSA的安全性高。

常見的對稱加密算法

  • DES
    數據加密標準,但由於強度不高,暴力破解難度不是很高,所以用的很少。
  • 3DES
    使用3個密鑰,對數據進行三次加密,強度增強。雖然強度相對DES有所提高,但是對稱加密算法密鑰的保存就很難,3DES的3個密鑰更麻煩,所以3DES也沒有被廣泛使用。
  • AES
    高級密碼標準,加密強度非常高,被廣泛使用,美國安全局和蘋果鑰匙串訪問都是用了AES加密算法。

常用的兩種加密模式

  • ECB(Electronic Code Book):電子密碼本模式(每一塊數據獨立加密)
    最基本的加密模式,也就是通常理解的加密,相同的明文將永遠加密成相同的密文,無初始向量,容易受到密碼本重放攻擊,一般情況下很少用。
  • CBC(Cipher Block Chaining):密碼分組鏈接模式(使用一個密鑰和一個初始化向量[IV]對數據執行加密。每一塊數據加密都依賴上一塊數據,有效的保證數據的完整性)
    明文被加密前要與前面的密文進行異或運算後再加密,因此只要選擇不同的初始向量,相同的密文加密後會形成不同的密文,這是目前應用最廣泛的模式。CBC加密後的密文是上下文相關的,但明文的錯誤不會傳遞到後續分組,但如果一個分組丟失,後面的分組將全部作廢(同步錯誤)。CBC可以有效的保證密文的完整性,如果一個數據塊在傳遞時丟失或改變,後面的數據將無法正常解密。

當然了,除了對稱加密和非對稱加密外,我們肯定還聽說過Hash

Hash概述

Hash:一般翻譯做“散列”,也有直接音譯爲“哈希”的,就是把任意長度的輸入通過散列算法變換成固定長度的輸出,該輸出就是散列值。這種轉換是一種壓縮映射,也就是,散列值的空間通常遠小於輸入的空間,不同的輸入可能會散列成相同的輸出,所以不可能從散列值來確定唯一的輸入值。簡單的說就是一種將任意長度的消息壓縮到某一固定長度的消息摘要的函數

之前介紹了RSA加密算法,在RSA算法之後也衍生出很多加密算法,典型的算法就有HASH函數(也稱之爲散列函數,嚴格意義上不算是加密算法,只不過是和加密一起用),還有在RSA出現之前的對稱加密算法,這些算法都是公開的。

Hash特點

算法是公開的
相同的數據加密結果不變
不同的數據加密結果定長(MD5得到的結果默認是128位二進制,一般用16進制的32個字符來標識)
不可逆
信息摘要,信息“指紋”,用來做數據識別的

HASH用途

密碼加密:服務器不需要知道用戶真實密碼,只需要匹配HASH值
搜索引擎
版權
數字簽名

OpenSSL演示ECB和CBC的區別

  • OpenSSL演示ECB模式加密
// 先創建一個待加密的message.txt文件,編輯內容
$ vi message.txt
$ cat message.txt
  Hello vincent!!!
// enc -des-ecb是對稱加密算法DES的ECB模式,-K是密鑰,616263就是ASCII碼“abc”,-nosalt不加鹽(OpenSSL默認會加鹽),輸出msg.bin
$ openssl enc -des-ecb -K 616263 -nosalt -in message.txt -out msg.bin

這時在文件夾內會多出一個加密過後的二進制文件msg.bin,修改message.txt文件的內容,再次進行加密

// 先修改message.txt的內容,vincent->Vincent
$ vi message.txt
$ cat message.txt
  Hello Vincent!!!
// 再次對message.txt進行同樣的方式加密,輸出msg1.bin
$ openssl enc -des-ecb -K 616263 -nosalt -in message.txt -out msg1.bin
// 查看下msg.bin和msg1.bin有什麼不同
$ xxd msg.bin
00000000: 6d87 4097 d383 0bda a5bc d168 de16 688d  [email protected].
00000010: b8db 0794 f9ed eca9
$ xxd msg1.bin
00000000: 20e2 8361 50a7 16a0 a5bc d168 de16 688d   ..aP......h..h.
00000010: b8db 0794 f9ed eca9

我們會發現,修改message.txt內容後ECB模式加密的結果只是修改部分不同,前後加密結果不變

  • OpenSSL演示CBC模式加密
// 先編輯message.txt的內容
$ vi message.txt
$ cat message.txt
  Hello vincent!!!
// 再次對message.txt進行CBC方式加密,相對ECB模式除了修改-des-cbc,還會多一個iv參數,iv是初始化向量,輸出msg2.bin
$ openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -in message.txt -out msg2.bin
// 再次編輯message.txt的內容,vincent->Vincent
$ vi message.txt
$ cat message.txt
  Hello Vincent!!!
// 對修改後的文件加密,輸出msg3.bin
$ openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -in message.txt -out msg3.bin
// 查看下msg2.bin和msg3.bin有什麼不同
$ xxd msg2.bin
00000000: d647 a33b 0389 dea5 3c81 02c9 ec05 44dd  .G.;....<.....D.
00000010: 467c a581 ab1a 415a
$ xxd msg3.bin
00000000: 9882 c1b6 3186 b465 b3be d08a 5ad5 2fd1  ....1..e....Z./.
00000010: 6032 add7 bdb2 07da                      `2......

經多次測試發現,修改message.txt內容後CBC模式加密的結果是修改部分不同以及後面的加密結果也會變化

  • 終端測試指令

加密過程:先加密,再base64編碼
解密過程:先base64解碼,再解密

//  DES(ECB)加密
$ echo -n hello | openssl enc -des-ecb -K 616263 -nosalt | base64
 
// DES(CBC)加密
$ echo -n hello | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt | base64

//  AES(ECB)加密 128位
$ echo -n hello | openssl enc -aes-128-ecb -K 616263 -nosalt | base64
 
 //  AES(CBC)加密
$ echo -n hello | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt | base64
 
//  DES(ECB)解密  base64 -D進行解碼成二進制 -d解密
$ echo -n HQr0Oij2kbo= | base64 -D | openssl enc -des-ecb -K 616263 -nosalt -d

//  DES(CBC)解密
$ echo -n alvrvb3Gz88= | base64 -D | openssl enc -des-cbc -iv 0102030405060708 -K 616263 -nosalt -d

//  AES(ECB)解密
$ echo -n d1QG4T2tivoi0Kiu3NEmZQ== | base64 -D | openssl enc -aes-128-ecb -K 616263 -nosalt -d

//  AES(CBC)解密
$ echo -n u3W/N816uzFpcg6pZ+kbdg== | base64 -D | openssl enc -aes-128-cbc -iv 0102030405060708 -K 616263 -nosalt -d

對稱加密算法代碼演示

// AES加密、ECB模式對“hello vincent!!!”進行加密
    NSString *ECBEncryptStr = [[EncryptionTools sharedEncryptionTools] encryptString:@"hello vincent!!!" keyString:@"abc" iv:nil];
    NSLog(@"%@", ECBEncryptStr);
    // 解密
    NSString *ECBDecrypt = [[EncryptionTools sharedEncryptionTools] decryptString:ECBEncryptStr keyString:@"abc" iv:nil];
    NSLog(@"%@", ECBDecrypt);
    
    // 一個數組,和前面一樣有8個數據
    uint8_t iv[8] = {1, 2, 3, 4, 5, 6, 7, 8};
    // 把數組包裝才二進制NSdata 把數組的指針和長度傳進去
    NSData *ivData = [NSData dataWithBytes:iv length:sizeof(iv)];
    // AES加密、CBC模式
    NSString *CBCEncryptStr = [[EncryptionTools sharedEncryptionTools] encryptString:@"hello vincent!!!" keyString:@"abc" iv:ivData];
    NSLog(@"%@", CBCEncryptStr);
    // 解密
    NSString *CBCDecrypt = [[EncryptionTools sharedEncryptionTools] decryptString:CBCEncryptStr keyString:@"abc" iv:ivData];
    NSLog(@"%@", CBCDecrypt);

打印結果:

LzWe4b6VMKHECZTg5GEoDvOJyUo3lvcCucS987KliFw=
hello vincent!!!
Vo04z90TAfQX07onyrvCie1SnRpsbHKMkYnaNhcEPP0=
hello vincent!!!

雖然將加密和解密封裝成了兩個方法,但是蘋果內部加密和解密都是用的一個函數。先看下其中封裝的一個方法內部實現

// 加密字符串並返回base64編碼字符串
- (NSString *)encryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
    
    // 設置祕鑰 將keyString轉成二進制
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];
    
    // 設置iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding; // CBC加密
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;  // ECB加密
    }
    
    // 設置輸出緩衝區 將原始數據轉成二進制,並根據所使用的加密方式設置緩衝區
    NSData *data = [string dataUsingEncoding:NSUTF8StringEncoding];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);
    
    // 開始加密
    size_t encryptedSize = 0;
    //加密解密都是它 -- CCCrypt
    CCCryptorStatus cryptStatus = CCCrypt(kCCEncrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &encryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:encryptedSize];
    } else {
        free(buffer);
        NSLog(@"[錯誤] 加密失敗|狀態編碼: %d", cryptStatus);
    }
    
    return [result base64EncodedStringWithOptions:0];
}

// 解密字符串
- (NSString *)decryptString:(NSString *)string keyString:(NSString *)keyString iv:(NSData *)iv {
    
    // 設置祕鑰
    NSData *keyData = [keyString dataUsingEncoding:NSUTF8StringEncoding];
    uint8_t cKey[self.keySize];
    bzero(cKey, sizeof(cKey));
    [keyData getBytes:cKey length:self.keySize];
    
    // 設置iv
    uint8_t cIv[self.blockSize];
    bzero(cIv, self.blockSize);
    int option = 0;
    if (iv) {
        [iv getBytes:cIv length:self.blockSize];
        option = kCCOptionPKCS7Padding;
    } else {
        option = kCCOptionPKCS7Padding | kCCOptionECBMode;
    }
    
    // 設置輸出緩衝區
    NSData *data = [[NSData alloc] initWithBase64EncodedString:string options:0];
    size_t bufferSize = [data length] + self.blockSize;
    void *buffer = malloc(bufferSize);
    
    // 開始解密
    size_t decryptedSize = 0;
    CCCryptorStatus cryptStatus = CCCrypt(kCCDecrypt,
                                          self.algorithm,
                                          option,
                                          cKey,
                                          self.keySize,
                                          cIv,
                                          [data bytes],
                                          [data length],
                                          buffer,
                                          bufferSize,
                                          &decryptedSize);
    
    NSData *result = nil;
    if (cryptStatus == kCCSuccess) {
        result = [NSData dataWithBytesNoCopy:buffer length:decryptedSize];
    } else {
        free(buffer);
        NSLog(@"[錯誤] 解密失敗|狀態編碼: %d", cryptStatus);
    }
    
    return [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
}

代碼已加入相應的註釋,就不解釋代碼了。我們會發現蘋果內部加密和解密都是用CCCryptorStatus CCCrypt( CCOperation op, CCAlgorithm alg, CCOptions options, const void *key, size_t keyLength, const void *iv, const void *dataIn, size_t dataInLength, void *dataOut, size_t dataOutAvailable, size_t *dataOutMoved),這個函數是對稱加密算法的核心函數。
CCCrypt函數

WTF!!!11個參數,這麼多,這都是幹嘛的?

參數意義
op:kCCEncrypt(加密)/ kCCDecrypt(解密)
alg:加密算法 kCCAlgorithmAES、kCCAlgorithmDES、kCCAlgorithmBlowfish等等
options:加密方式 kCCOptionPKCS7Padding(CBC方式)/kCCOptionECBMode(ECB方式)
key:加密密鑰
keyLength:密鑰長度
iv:初始化向量 ECB不需要指定(CBC多了這個參數就相當於加鹽,加密強度更高了)
dataIn:加密的數據
dataInLength:加密數據的長度
dataOut:緩衝區(地址),存放密文
dataOutAvailable:緩衝區的大小
dataOutMoved:加密結果的大小

搞清楚每個參數的意義也就明白了,蘋果這樣設計還是挺人性化的。對稱加密和解密所用的參數密鑰都是一樣的,所以加密和解密都是用同一個函數。蘋果的加密算法也都在CommonCrypto.h這個庫裏面,這個庫並不在macho中,是在系統中,所以我們大多數會認爲這個加密會很安全,但是事實上並不是這樣。

CCCrypt函數安全隱患

現在我們已經使用CCCrypt對數據進行加密和解密了,接下來看下我們用CCCrypt加密的數據是否真的安全。下面的內容涉及到逆向開發,可能有點跑偏,如果感興趣的小夥伴也可以進一步研究一下。
我們加密數據就是爲了防止中間人攻擊,假設如果別人拿到我們的APP,別人肯定不會知道我們的源碼,也不知道在數據核心加密的地方是不是用的CCCrypt,這時別人會進行符號斷點,當然這個只要是沒有去符號,或者系統的都是可以攔截到的。
符號斷點

前方高能!!!

下了斷點後,我們繼續運行剛纔的demo,程序果斷進入斷點,接下來要讀寄存器了!!!

WeChat112460611fd045da8d8d9a181882dc6d.png

剛剛說過,CCCrypt第7個參數是我們加密的數據,所以在寄存器中X6(從X0開始)就是我們的加密數據,我們在lldb中讀取X6的地址值,也就是指針,拿到X6的地址值是0x00000001c403ee00,再p (char *)0x00000001c403ee00查看X6的值,回車!!!我們剛剛加密的數據顯示出來了,我們原以爲很安全的手段就這樣被別人拿到了!所以我們有很多核心的加密算法不能直接用。

那怎麼防禦呢?先想到去符號,前面也說了,這個庫是系統的,所以沒辦法去符號,當然自己實現或者三方庫,比如支付寶就是直接用的OpenSSL,可以去符號來避免被直接破解。最好的方式是加密之前不能直接使用關鍵數據,我們可以自己對關鍵數據處理一下比如異或,方法肯定不止一個,如果各位有什麼好的解決辦法歡迎交流。

後面可能會介紹下怎麼隱藏函數調用,怎樣保護核心數據,當然逆向大神還是很多的,這也僅僅是讓逆向變的更難而已。該文章爲記錄本人的學習路程,希望能夠幫助大家,也歡迎大家點贊留言交流!!!https://www.jianshu.com/p/a6fca79eb89c

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章