iOS AES加密(主要使用CFB模式)



前言

首先,希望大家耐心點,這個加密我也是弄很久才出來的,辛辛苦苦整理的博客,介紹大概概念就進入正題!

上面是基本介紹,后面是個人經歷的問題及解決方案,希望你們耐心看,不要踩我進過的坑啦!祝你們都能順利解決問題??

1. 介紹

1.1 AES是什么?

高級加密標準(英語:Advanced Encryption Standard,縮寫:AES),在密碼學中又稱 Rijndael 加密法。AES 是一個對稱分組密碼算法,旨在取代 DES 成為廣泛使用的標準。

1.2 AES詳解

AES 根據使用密碼長度有三種方案以應對不同的場景要求,分別是 AES-128AES-192AES-256。加密模式有四種,分別是 ECB(Elecyronic Code Book,電子密碼本)、CBC(Cipher Block Chaining,加密塊鏈)、CFB(Cipher FeedBack Mode,加密反饋)、OFB(Output FeedBack,輸出反饋)。

需要和后臺統一四個東西:秘鑰長度加密模式填充方式初始向量(也稱偏移量,ECB模式不需要)。

定義中說到 AES 是一個對稱分組密碼算法,加密原理如圖:

AES 加密原理

1.3 實現原理和比較

這個就比較深入啦,有興趣的自行查看~

  1. 實現原理
  1. 比較

1.4 模式和填充選擇

算法/模式/填充 16字節加密后數據長度 不滿16字節加密后長度
AES/CBC/NoPadding 16 不支持
AES/CBC/PKCS5Padding 32 16
AES/CBC/ISO10126Padding 32 16
AES/CFB/NoPadding 16 原始數據長度
AES/CFB/PKCS5Padding 32 16
AES/CFB/ISO10126Padding 32 16
AES/ECB/NoPadding 16 不支持
AES/ECB/PKCS5Padding 32 16
AES/ECB/ISO10126Padding 32 16
AES/OFB/NoPadding 16 原始數據長度
AES/OFB/PKCS5Padding 32 16
AES/OFB/ISO10126Padding 32 16
AES/PCBC/NoPadding 16 不支持
AES/PCBC/PKCS5Padding 32 16
AES/PCBC/ISO10126Padding 32 16

PKCS7Padding VS PKCS5Padding
PKCS5Padding 的 blocksize 為8字節,而 PKCS7Padding 的 blocksize 可以為1到255字節。

需要注意點:
1. iOS只支持 PKCS7Padding 填充方式;Java 支持 PKCS5Padding 但不支持 PKCS7Padding,不過不要擔心,上面說的區別我也不懂,實際中倒是一樣;
2. node.js 在 AES 加密上和其他語言有略不同,它系統自帶方法對 Key 進行過 MD5 處理。

2. 經驗總結

2.1 加密模式和填充方式的確定

首先,一定要確認使用的加密模式和填充方式!!!因為我接到任務的時候郵件里只給了 keyiv,沒有說清楚,而 iOS 默認的是 CBC 模式,AndroidCFB 模式一下就 OK 了,我調了半天不行,看了 Android 的代碼才醒悟過來。。。。。。

我的項目中,后臺使用的是AES/CFB/PKCS7Padding,Android 使用的是AES/CFB/PKCS5Padding

2.2 填充方式的選擇

其次,填充方式的選擇:按照上面來看,我使用 AES/CFB/PKCS7Padding 就可以了哈。
然而,iOS 有的加密方法,只有 CCCryptorCreateWithMode 可以設置除了默認的 CBC 、ECB 之外的其他模式,所以就用它啦,其方法如下:

CCCryptorStatus status = CCCryptorCreateWithMode(operation,
                                                     kCCModeCFB,
                                                     kCCAlgorithmAES,
                                                     ccPKCS7Padding,
                                                     iv,
                                                     key,
                                                     keyStr.length,
                                                     NULL,
                                                     0,
                                                     0,
                                                     0,
                                                     &cryptor); 

這里的 padding 除了 ccPKCS7Padding,還有 ccNoPadding 不填充兩種選擇。我試了兩個的加密結果是一樣的,使用 ccPKCS7Padding 并沒有自動填充。(可能其他模式可以,總不能有這個還不能用吧。但是我沒有查到 AES/CFB/PKCS7Padding 為什么不填充,如果小伙伴知道還請告知哦!)

所以我就先試試 ccNoPadding 不填充模式。舉例如下:

原字符串:@"hello中國"
原數據 Data:<68656c6c 6fe4b8ad e59bbd>

Java 的PKCS5Padding方式加密后的字符串:QReiy/Ddik50cXQ=
iOS 的ccNoPadding加密后的字符串:QReiy/DRrn92WV8=

加密結果當然不一致,下面對 Java 加密字符串解密后進行分析:

iOS 的ccNoPadding對 Java 加密字符串解密后 data:<68656c6c 6fe4b8ad e59bbd05 05050505>
iOS 的ccNoPadding對加密字符串解密后 string:@"hello中國\x05\x05\x05\x05\x05"(這里要注意解密后的字符串在控制臺輸出是沒問題的,但是實際是有多余的,如圖:)

可以看出來,因為一個是PKCS5Padding,一個是ccNoPadding 會有填充模式上導致的數據差異。相信看過上面幾篇文章的應該明白了。

如果 Java 加密的填充方式也是用ccNoPadding,那么解出來的就不會有多余填充了。也就是說應該三方都保持同樣的ccNoPadding填充方式,我和 Android 測試過(注意 Android 那邊應該是 NoPadding書寫方式)

但是后臺那邊說多個地方都已經使用 PKCS7Padding。當然解決辦法還是有的~~~

2.3 選錯填充方式的補救

對此,只能我這邊采取措施和 Android 、后臺保持一致了。
也就是解密后臺給的數據的時,截掉多余的填充;加密傳輸時,加密后,補充需要填充的數據。這里主要是對 NSData 的操作。

注意:加解密的步驟(ase64、URL Encode、有的還有字符串替換)不同公司可能采取方式不同,要對接好。

2.4 代碼思路

在我的項目中,對 NSData 進行填充補位和刪除,我們需要了解 PKCS7Padding 的填充方式:

需要填充的字節長度 = (塊長度 - (數據長度 % 塊長度))

假定塊長度為 8,數據長度為 3,則填充字節數等于 5。
原數據為: FF FF FF 
填充結果: FF FF FF 05 05 05 05 05 
假定塊長度為 8,數據長度為 8,則填充字節數等于 8。
原數據為: FF FF FF FF FF FF FF FF
填充結果: FF FF FF FF FF FF FF FF 08 08 08 08 08 08 08 08 

差多少補多少,不差就補一個塊。

當然我采坑的過程中沒那么順利 ,在此想提醒大家這種不熟悉的任務一定要多批量復雜數據,不能簡簡單單測試簡單少量數據就行了,早發現早解決。

該處理已在項目中使用上線,使用正常,沒有問題。

2.5 加密方法的實現

2.5.1 NSData 擴展實現加密解密

NSData+EHIExtension.h:

#import <Foundation/Foundation.h>
#include <CommonCrypto/CommonCrypto.h>

interface NSData (EHIExtension)

/** AES解密:CFB模式 */
- (NSData *)aes256ByCFBModeWithOperation:(CCOperation)operation key:(NSString *)keyStr iv:(NSString *)ivStr;

end

NSData+EHIExtension.m:

#import "NSData+EHIExtension.h"

/** AES加密位數 */
static NSInteger const kEHIAESMode = 16;

@implementation NSData (EHIExtension)

/** AES解密:CFB模式 */
- (NSData *)aes256ByCFBModeWithOperation:(CCOperation)operation key:(NSString *)keyStr iv:(NSString *)ivStr {
    NSData *originData = self;
    if (operation == kCCEncrypt) {
        // 加密:位數不夠的補全
        originData = [self fullData:originData mode:kEHIAESMode];
    }
    
    const char *iv = [[ivStr dataUsingEncoding:NSUTF8StringEncoding] bytes];
    const char *key = [[keyStr dataUsingEncoding:NSUTF8StringEncoding] bytes];
    
    // 加密/解密
    CCCryptorRef cryptor = NULL;
    CCCryptorStatus status = CCCryptorCreateWithMode(operation,
                                                     kCCModeCFB,
                                                     kCCAlgorithmAES,
                                                     ccNoPadding,
                                                     iv,
                                                     key,
                                                     keyStr.length,
                                                     NULL,
                                                     0,
                                                     0,
                                                     0,
                                                     &cryptor);
    if (status != kCCSuccess) {
        NSLog(@"AES加密/解密失敗 error: %@", @(status));
        return nil;
    }
    
    // 輸出加密/解密數據
    NSUInteger inputLength = originData.length;
    char *outData = malloc(inputLength);
    memset(outData, 0, inputLength);
    
    size_t outLength = 0;
    CCCryptorUpdate(cryptor, originData.bytes, inputLength, outData, inputLength, &outLength);
    NSData *resultData = [NSData dataWithBytes:outData length:outLength];
    
    CCCryptorRelease(cryptor);
    free(outData);
    
    if (operation == kCCDecrypt) {
        // 解密:位數多的刪除
        resultData = [self deleteData:resultData mode:kEHIAESMode];
    }
    return resultData;
}

/** 加密:位數不夠的補全
    補位規則:1.length=13,補5位05
            2.length=16,補16位ff */
- (NSData *)fullData:(NSData *)originData mode:(NSUInteger)mode {
    NSMutableData *tmpData = [[NSMutableData alloc] initWithData:originData];
    // 確定要補全的個數
    NSUInteger shouldLength = mode * ((tmpData.length / mode) + 1);
    NSUInteger diffLength = shouldLength - tmpData.length;
    uint8_t *bytes = malloc(sizeof(*bytes) * diffLength);
    for (NSUInteger i = 0; i < diffLength; i++) {
        // 補全缺失的部分
        bytes[i] = diffLength;
    }
    [tmpData appendBytes:bytes length:diffLength];
    return tmpData;
}

/** 解密:位數多的刪除
    刪位規則:最后一位數字在1-16之間,且連續n位相同n數字 */
- (NSData *)deleteData:(NSData *)originData mode:(NSUInteger)mode {
    NSMutableData *tmpData = [[NSMutableData alloc] initWithData:originData];
    Byte *bytes = (Byte *)tmpData.bytes;
    Byte lastNo = bytes[tmpData.length - 1];
    if (lastNo >= 1 && lastNo <= mode) {
        NSUInteger count = 0;
        // 確定多余的部分正確性
        for (NSUInteger i = tmpData.length - lastNo; i < tmpData.length; i++) {
            if (lastNo == bytes[i]) {
                count ++;
            }
        }
        if (count == lastNo) {
            // 截取正常的部分
            NSRange replaceRange = NSMakeRange(0, tmpData.length - lastNo);
            return [tmpData subdataWithRange:replaceRange];
        }
    }
    return originData;
}

2.5.2 NSString 擴展實現使用過程

NSString+EHIAES.h:

#import <Foundation/Foundation.h>

interface NSString (EHIAES)

/** AES解密 */
- (NSString *)aes256Decrypt;

/** AES加密 */
- (NSString *)aes256Encrypt;

@end

NSString+EHIAES.m:

#import "NSString+EHIAES.h"
#import "NSString+YYAdd.h"
#import "NSData+YYAdd.h"
#import "NSData+EHIExtension.h"
#import "GTMBase64.h"

/** AES加密:key */
static NSString * const kAESKey = @""; // 32位
/** AES加密:iv */
static NSString * const kAESIv = @""; // 16位

@implementation NSString (EHIAES)

/** AES解密 */
- (NSString *)ehi_aes256Decrypt {
    // 1.Base64 Decode
    NSData *base64DecodeData = [NSData dataWithBase64EncodedString:self];
    // 1.Aes256 解密
    NSData *decodeData = [base64DecodeData aes256ByCFBModeWithOperation:kCCDecrypt key:kAESKey iv:kAESIv];
    NSString *decodeStr = [[NSString alloc] initWithData:decodeData encoding:NSUTF8StringEncoding];
    if ([NSString isNilOrEmpty:decodeStr]) {
        // 解密失敗
        return nil;
    }
    return decodeStr;
}

/** AES加密 */
- (NSString *)ehi_aes256Encrypt {
    NSData *originData = [self dataUsingEncoding:NSUTF8StringEncoding];
    // 1.Aes256 加密
    NSData *encodeData = [originData aes256ByCFBModeWithOperation:kCCEncrypt key:kAESKey iv:kAESIv];
    // 2.Base64 Encode
    NSString *base64EncodeStr = [encodeData base64EncodedString];
    return base64EncodeStr;
}

@end
最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。