1. 程式人生 > >iOS KeyChain 淺析以及應用(資料AES加密)附demo

iOS KeyChain 淺析以及應用(資料AES加密)附demo

一.iOS鑰匙串KeyChain 解析
     根據蘋果的介紹,iOS備中的Keychain是一個安全的儲存容器,可以用來為不同應用儲存敏感資訊比如使用者名稱,密碼,網路密碼,認證令牌。蘋果自己用keychain來儲存Wi-Fi網路密碼,VPN憑證等等。它是一個sqlite 資料庫,位於/private/var/Keychains/keychain-2.db,其儲存的所有資料都是加密過的。

     Keychain可以實現使用者資訊自動登入和應用之間的資料共享,由於通過Keychain儲存的資訊是存在於每個應用(app)的沙盒之外,並且是以指定group的形勢存在。

keychain的組成:

1.每一個keyChain的組成如圖,整體是一個字典結構.



               

2.每一個keyChain的組成如圖,整體是一個字典結構.
        1.kSecClass key 定義屬於那一種型別的keyChain
        2.不同的型別包含不同的Attributes,這些attributes定義了這個item的具體資訊
        3.每個item可以包含一個密碼項來儲存對應的密碼

3.APP對鑰匙串的訪問許可權:
     1)未對應用APP的entitlement(授權)進行配置時,APP使用鑰匙串儲存時,會預設儲存在自身BundleID的條目下。  

                

(2)對APP的entitlement(授權)進行配置後,說明APP有了對某個條目的訪問許可權。

               

   APP鑰匙串訪問許可權的配置方法:(這裡XXXXX模擬器隨意,但真機必須為自己開發者賬號ID,否則無法通過編譯)
   1.新建一個Plist檔案,在Plist中的陣列中新增可以訪問的條目的名字(如KeychainAccessGroups.plist),結構如下:

               

4.在Build-setting中進行配置,搜尋entitlement,注意路徑別配置錯:

              

安全性:
從Keychain中匯出資料的最流行工具是ptoomey3的Keychain dumper。其github地址位於此。現在到這個地址把它下載下來。然後解壓zip檔案。在解壓的資料夾內,我們感興趣的檔案是keychain_dumper這個二進位制檔案。一個應用能夠訪問的keychain資料是通過其entitlements檔案指定的。keychain_dumper使用一個自簽名檔案,帶有一個*萬用字元的entitlments,因此它能夠訪問keychain中的所有條目。 當然,也有其他方法來使得所有keychain資訊都被授權,比如用一個包含所有訪問組(access group)的entitlements檔案,或者使用一個特定的訪問組(access group)使得能夠訪問所有的keychain資料。 例如,工具Keychain-viewer就使用如下的entitlements.
雖然keychain也容易被破解,不過比NSUserDefaults和plist安全得多,只要我們注意不要在keychain中儲存明文密碼就會在很大程度上提升安全性。
https://my.oschina.net/w11h22j33/blog/206713

鑰匙串中的條目稱為SecItem,SecItem有五類:通用密碼、網際網路密碼、證書、金鑰和身份。在大多數情況下,我們用到的都是通用密碼,KeyChainItemWrapper也只使用通用密碼。iOS應用很少將金鑰和身份儲存起來。只有公鑰的證書通常應該儲存在檔案中,而不是鑰匙串中。
最後,通用密碼條目包含屬性kSecAttrGeneric,可以用它來儲存識別符號。這也是KeyChainItemWrapper的處理方式。
鑰匙串中的條目都有幾個可搜尋的**屬性**和一個加密過的**值**。對於通用密碼條目,比較重要的屬性有賬戶(kSecAttrAccount)、服務(kSecAttrService)和識別符號(kSecAttrGeneric)。而值通常是密碼。

二.Keychain 應用

1.Keychain提供的主要方法


 iOS中Security.framework框架提供了四個主要的方法來操作KeyChain:

// 查詢
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result);

// 新增
OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef *result);

// 更新
KeyChain中的ItemOSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate);

// 刪除
KeyChain中的ItemOSStatus SecItemDelete(CFDictionaryRef query)

keychain item的型別,kSecClass主要鍵值   
kSecClassGenericPassword     (kSecAttrAccount,kSecAttrService)
kSecClassInternetPassword	(kSecAttrAccount, kSecAttrSecurityDomain, kSecAttrServer, kSecAttrProtocol,kSecAttrAuthenticationType, kSecAttrPortkSecAttrPath)
kSecClassCertificate	   (kSecAttrCertificateType, kSecAttrIssuerkSecAttrSerialNumber)
kSecClassKey	(kSecAttrApplicationLabel, kSecAttrApplicationTag, kSecAttrKeyType,kSecAttrKeySizeInBits, kSecAttrEffectiveKeySize)
kSecClassIdentity	(kSecClassKey,kSecClassCertificate)
要把資訊儲存到keychain中,使用 setObject:forKey: 方法。在這裡, (id)kSecAttrAccount 是一個預先定義好的鍵(key),我們可以用它來儲存賬號名稱。 kSecClass指定了我們要儲存的某類資訊,在這裡是一個通用的密碼。kSecValueData可以被用來儲存任意的資料,在這裡是一個密碼。

keychain item的屬性結構是以字典的形勢存在,所以先定義keychain item屬性函式:
區別(標識)一個item要用kSecAttrAccount和kSecAttrService

kechain 組屬性

//建立kechain 屬性 kSecAttrService,kSecAttrAccount 標誌一個item
+ (NSMutableDictionary *)getKeychainQuery:(NSString *)service {
    return [NSMutableDictionary dictionaryWithObjectsAndKeys:
            (id)kSecClassGenericPassword,(id)kSecClass,//型別
            service, (id)kSecAttrService,//服務
            service, (id)kSecAttrAccount,//帳戶
            (id)kSecAttrAccessibleAlwaysThisDeviceOnly,(id)kSecAttrAccessible,//訪問的型別
            nil];
}

新增
//新增
+ (void)saveData:(id)data forIdentifier:(NSString *)service{
    //建立查詢支點
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //建立新的 item 前先刪除舊的 item
    SecItemDelete((CFDictionaryRef)keychainQuery);
    //先把需要儲存的資料序列化,
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    //建立新的 item 到keychain
    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}
//新增(帶AES加密)
+ (void)saveDataWithEncrypt:(id)data forIdentifier:(NSString *)service{
    //建立查詢支點
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //建立新的 item 前先刪除舊的 item
    SecItemDelete((CFDictionaryRef)keychainQuery);
    //先把需要儲存的資料序列化
    NSData*NSKeyData=[NSKeyedArchiver archivedDataWithRootObject:data];
    //使用密碼對nsdata進行加密
    NSData *encryptedData = [NSKeyData AES256EncryptWithKey:APP_PUBLIC_PASSWORD];
    //儲存
    [keychainQuery setObject:encryptedData forKey:(id)kSecValueData];
    
    //建立新的 item 到keychain
    SecItemAdd((CFDictionaryRef)keychainQuery, NULL);
}

查詢

//查詢
+ (id)loadforIdentifier:(NSString *)service {
    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //查詢的返回型別
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    //返回的數目
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    if (keyData)
        CFRelease(keyData);
    return ret;
}
//查詢(帶AES加密)
+ (id)loadforIdentifierWithEncrypt:(NSString *)service {
    id ret = nil;
    NSData*retDecrypt;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    //查詢的返回型別
    [keychainQuery setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
    //返回的數目
    [keychainQuery setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
    CFDataRef keyData = NULL;
    if (SecItemCopyMatching((CFDictionaryRef)keychainQuery, (CFTypeRef *)&keyData) == noErr) {
        @try {
            //解密
            retDecrypt=[(__bridge NSData *)keyData AES256DecryptWithKey:APP_PUBLIC_PASSWORD];
            //反序列化
            ret = [NSKeyedUnarchiver unarchiveObjectWithData:retDecrypt];
        } @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", service, e);
        } @finally {
        }
    }
    if (keyData)
        CFRelease(keyData);
    
    
    return ret;
}

更新
//更新
+(BOOL)updateKeychainValue:(id)data forIdentifier:(NSString *)service {
    //舊的 item
    NSMutableDictionary *searchDictionary = [self getKeychainQuery:service];
    //建立新的item
    NSMutableDictionary *updateDictionary = [self getKeychainQuery:service];
    [updateDictionary setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData];
    //更新新的item
    OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
                                    (CFDictionaryRef)updateDictionary);
    if (status == errSecSuccess) {
        return YES;
    }
    return NO;
}
//更新(帶加密)
+(BOOL)updateKeychainValueWithEncrypt:(id)data forIdentifier:(NSString *)service {
    //舊的 item
    NSMutableDictionary *searchDictionary = [self getKeychainQuery:service];
    //建立新的item
    NSMutableDictionary *updateDictionary = [self getKeychainQuery:service];
    //序列化
    NSData*NSKeyData=[NSKeyedArchiver archivedDataWithRootObject:data];
    //使用密碼對nsdata進行加密
    NSData *encryptedData = [NSKeyData AES256EncryptWithKey:APP_PUBLIC_PASSWORD];
    [updateDictionary setObject:encryptedData forKey:(id)kSecValueData];
    //更新新的item
    OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary,
                                    (CFDictionaryRef)updateDictionary);
    if (status == errSecSuccess) {
        return YES;
    }
    return NO;
}

刪除
+ (void)delete:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
}

三.順便封裝了一下,寫了個demo,以供呼叫

呼叫事例

1.複製  LLKeyChain檔案到工程

2.使用的類檔案匯入標頭檔案

#import "LLKeyChainManager.h"

3.呼叫事例

-(void)viewDidLoad {
    [super viewDidLoad];
   
    //非加密模式
    //儲存
    [KeyChainManager SaveKeyChain:@{@"password":@"12345",@"name":@"王尼瑪",@"id":@"441781197887677873"}];
    //讀取
    NSDictionary*dic=[KeyChainManager LoadKeyChain];
    //列印
    NSMutableString*srting=[NSMutableString string];
    for (NSString*str in dic.allValues) {
        [srting appendString:str];
        [srting appendString:@"\n"];
    }
    NSLog(@"%@",srting);
    
    //加密模式
    //儲存
    [KeyChainManager SaveKeyChainWithEncrypt:@{@"password":@"12345",@"name":@"王尼瑪",@"id":@"441781197887677873"}];
    //讀取
     NSDictionary*dicEnCrypt=[KeyChainManager LoadKeyChainWithEncrypt];
    //列印內容
    NSMutableString*srtingEncrypt=[NSMutableString string];
    for (NSString*str in dicEnCrypt.allValues) {
        [srtingEncrypt appendString:str];
        [srtingEncrypt appendString:@"\n"];
    }
    NSLog(@"%@",dicEnCrypt);
}

此文章圖片有借鑑。

iOS鑰匙串KeyChain相關引數說明

  iOS鑰匙串KeyChain相關引數的說明
    密匙型別:
    鍵:
        CFTypeRef kSecClass
    值:
        CFTypeRef kSecClassGenericPassword   //一般密碼
        CFTypeRef kSecClassInternetPassword   //網路密碼
        CFTypeRef kSecClassCertificate               //證書
        CFTypeRef kSecClassKey                         //金鑰
        CFTypeRef kSecClassIdentity                   //身份證書(帶私鑰的證書)
    
    金鑰串項屬性
    1.kSecClassGenericPassword //一般密碼
    屬性:
        kSecAttrAccessible             //kSecAttrAccessiblein變數用來指定這條資訊的保護程度
        kSecAttrAccessGroup        //金鑰訪問組
        kSecAttrCreationDate        //建立日期(read only)
        kSecAttrModificationDate  //最後一次修改日期
        kSecAttrDescription           //描述
        kSecAttrComment             //註釋
        kSecAttrCreator                //建立者
        kSecAttrType                    //型別
        kSecAttrLabel                   //標籤(給使用者看)
        kSecAttrIsInvisible            //是否隱藏
        kSecAttrIsNegative          //是否具有密碼
        kSecAttrAccount              //賬戶名
        kSecAttrService              //所具有服務
        kSecAttrGeneric             //使用者自定義內容
    
    
    2. kSecClassInternetPassword //網路密碼
    屬性:
        kSecAttrAccessible
        kSecAttrAccessGroup
        kSecAttrCreationDate
        kSecAttrModificationDate
        kSecAttrDescription
        kSecAttrComment
        kSecAttrCreator
        kSecAttrType
        kSecAttrLabel
        kSecAttrIsInvisible
        kSecAttrIsNegative
        kSecAttrAccount
        kSecAttrSecurityDomain
        kSecAttrServer
        kSecAttrProtocol                     //協議型別 CFNumberRef
        kSecAttrAuthenticationType   //認證型別 CFNumberRef
        kSecAttrPort                          //網路埠
        kSecAttrPath                         //訪問路徑
    
    
    3.kSecClassCertificate //證書
    屬性:
        kSecAttrAccessible
        kSecAttrAccessGroup
        kSecAttrLabel
        kSecAttrCertificateType           //證書型別
        kSecAttrCertificateEncoding   //證書編碼型別
        kSecAttrSubject                      //X.500主題名稱
        kSecAttrIssuer                        //X.500發行者名稱
        kSecAttrSerialNumber           //序列號
        kSecAttrSubjectKeyID           //主題ID
        kSecAttrPublicKeyHash        //公鑰Hash值
    
    4.kSecClassKey//金鑰
    屬性:
        kSecAttrAccessible           //變數用來指定這條資訊的保護程度
        kSecAttrAccessGroup       //金鑰存取群
        kSecAttrKeyClass              //加密金鑰類
        kSecAttrLabel                    //標籤
        kSecAttrApplicationLabel  //標籤(給程式使用) CFStringRef(通常是公鑰的Hash值)
        kSecAttrIsPermanent        //是否永久儲存加密金鑰
        kSecAttrApplicationTag     //標籤(私有標籤資料)
        kSecAttrKeyType              //加密金鑰型別(演算法)
        kSecAttrKeySizeInBits      //金鑰總位數     CFNumberRef
        kSecAttrEffectiveKeySize //金鑰有效位數  CFNumberRef
        kSecAttrCanEncrypt         //金鑰是否可用於加密  CFBooleanRef
        kSecAttrCanDecrypt         //金鑰是否可用於解密  CFBooleanRef
        kSecAttrCanDerive          //金鑰是否可用於匯出其他金鑰 CFBooleanRef
        kSecAttrCanSign             //金鑰是否可用於數字簽名  CFBooleanRef
        kSecAttrCanVerify           //金鑰是否可用於驗證數字簽名  CFBooleanRef
        kSecAttrCanWrap           //金鑰是否可用於打包其他金鑰  CFBooleanRef
        kSecAttrCanUnwrap       //金鑰是否可用於解包其他金鑰  CFBooleanRef
    
    5.kSecClassIdentity  //身份證書(帶私鑰的證書)
    屬性:
        1.證書屬性
        2.私鑰屬性
    
    
    金鑰串項屬性的值:
    屬性:
    1.kSecAttrAccessible
        CFTypeRef kSecAttrAccessibleWhenUnlocked;      //解鎖可訪問,備份
        CFTypeRef kSecAttrAccessibleAfterFirstUnlock;     //第一次解鎖後可訪問,備份
        CFTypeRef kSecAttrAccessibleAlways;                   //一直可訪問,備份
        CFTypeRef kSecAttrAccessibleWhenUnlockedThisDeviceOnly;  //解鎖可訪問,不備份
        CFTypeRef kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly; //第一次解鎖後可訪問,不備份
        CFTypeRef kSecAttrAccessibleAlwaysThisDeviceOnly;          //一直可訪問,不備份
    2.kSecAttrProtocol
        略...
    3.kSecAttrKeyClass  //加密金鑰類  CFTypeRef
          CFTypeRef kSecAttrKeyClassPublic;     //公鑰
          CFTypeRef kSecAttrKeyClassPrivate;    //私鑰
          CFTypeRef kSecAttrKeyClassSymmetric;  //對稱金鑰
    
    
#pragma mark- 返回值型別
    可以同時指定多種返回值型別
        CFTypeRef kSecReturnData;           //返回資料(CFDataRef)                  CFBooleanRef
        CFTypeRef kSecReturnAttributes;     //返回屬性字典(CFDictionaryRef)         CFBooleanRef
        CFTypeRef kSecReturnRef;            //返回例項(SecKeychainItemRef, SecKeyRef, SecCertificateRef, SecIdentityRef, or CFDataRef)         CFBooleanRef
        CFTypeRef kSecReturnPersistentRef;  //返回持久型例項(CFDataRef)             CFBooleanRef
    
#pragma mark- 寫入值型別
        CFTypeRef kSecValueData;
        CFTypeRef kSecValueRef;
        CFTypeRef kSecValuePersistentRef;