IOS KeyChain

轉自http://my.oschina.net/w11h22j33/blog/206713


一、Keychain 基礎

根據蘋果的介紹,iOS設備中的Keychain是一個安全的存儲容器,可以用來爲不同應用保存敏感信息比如用戶名,密碼,網絡密碼,認證令牌。蘋果自己用keychain來保存Wi-Fi網絡密碼,VPN憑證等等。它是一個sqlite數據庫,位於/private/var/Keychains/keychain-2.db,其保存的所有數據都是加密過的。

開發者通常會希望能夠利用操作系統提供的功能來保存憑證(credentials)而不是把它們(憑證)保存到NSUserDefaults,plist文件等地方。保存這些數據的原因是開發者不想用戶每次都要登錄,因此會把認證信息保存到設備上的某個地方並且在用戶再次打開應用的時候用這些數據自動登錄。Keychain的信息是存在於每個應用(app)的沙盒之外的。

通過keychain access groups可以在應用之間共享keychain中的數據。要求在保存數據到keychain的時候指定group。把數據保存到keychain的最好方法就是用蘋果提供的KeychainItemWrapper。可以到這下載例子工程。第一步就是創建這個類的實例。

KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@”Password” accessGroup:nil];

標識符(Identifier)在後面我們要從keychain中取數據的時候會用到。如果你想要在應用之間共享信息,那麼你需要指定訪問組(access group)。有同樣的訪問組 的應用能夠訪問同樣的keychain信息。

KeychainItemWrapper *wrapper = [[KeychainItemWrapper alloc] initWithIdentifier:@”Account Number” accessGroup:@”YOUR_APP_ID_HERE.com.yourcompany.GenericKeychainSuite”];

要把信息保存到keychain中,使用 setObject:forKey: 方法。在這裏, (id)kSecAttrAccount 是一個預先定義好的鍵(key),我們可以用它來保存賬號名稱。 kSecClass指定了我們要保存的某類信息,在這裏是一個通用的密碼。kSecValueData可以被用來保存任意的數據,在這裏是一個密碼。

[wrapper setObject:kSecClassGenericPassword forKey:(id)kSecClass];

[wrapper setObject:@”username” forKey:(id)kSecAttrAccount];

[wrapper setObject:@”password”forKey:(id)kSecValueData];

[wrapper setObject:(id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(id)kSecAttrAccessible];

kSecAttrAccessiblein變量用來指定這個應用合適需要訪問這個數據。我們需要對這個選項特別注意,並且使用最嚴格的選項。這個鍵(key)可以設置6種值。

你可以從如下對蘋果的文檔的截圖看到。

當然,我們應該絕對不要使用kSecAttrAccessibleAlways。一個安全點的選項是kSecAttrAccessibleWhenUnlocked。有些選項是以 ThisDeviceOnly 結尾的,如果選中了這個選項,那麼數據就會被以硬件相關的密鑰(key)加密,因此不能被傳輸到或者被其他設備看到。即使它們提供了進一步的安全性,使用它們可能不是一個好主意,除非你有一個更好的理由不允許數據在備份之間遷移。

要從keychain中獲取數據,可以用 NSString *accountName = [wrapper objectForKey:(id)kSecAttrAccount];

鑰匙串中的條目稱爲SecItem,但它是存儲在CFDictionary中的。SecItemRef類型並不存在。SecItem有五類:通用密碼、互聯網密碼、證書、密鑰和身份。在大多數情況下,我們用到的都是通用密碼。許多問題都是開發人員嘗試用互聯網密碼造成的。互聯網密碼要複雜得多,而且相比之下優勢寥寥無幾,除非開發Web瀏覽器,否則沒必要用它。KeyChainItemWrapper只使用通用密碼,這也是我喜歡它的原因之一。iOS應用很少將密鑰和身份存儲起來,所以我們在本書中不會討論這方面的內容。只有公鑰的證書通常應該存儲在文件中,而不是鑰匙串中。

最後,我們需要在鑰匙串中搜索需要的內容。密鑰有很多個部分可用來搜索,但最好的辦法是將自己的標識符賦給它,然後搜索。通用密碼條目都包含屬性kSecAttrGeneric,可以用它來存儲標識符。這也是KeyChainItemWrapper的處理方式。

鑰匙串中的條目都有幾個可搜索的屬性和一個加密過的。對於通用密碼條目,比較重要的屬性有賬戶(kSecAttrAccount)、服務(kSecAttrService)和標識符(kSecAttrGeneric)。而值通常是密碼。

說明:

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

安全性:

從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中保存明文密碼就會在很大程度上提升安全性。

二、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使用

引入Security包,引入文件#import <Security/Security.h>

添加

- (IBAction)add:(id)sender {
     if(nameField.text.length >0  && passwordField.text.length > 0) {
         NSMutableDictionary* dic = [NSMutableDictionary dictionary];
         [dic setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
         [dic setObject:nameField.text forKey:()kSecAttrAccount];
         [dic setObject:[passwordField.text dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];
         OSStatus s = SecItemAdd((CFDictionaryRef)dic, NULL);
         NSLog(,s);
    }
}

查找

- (IBAction)sel:(id)sender {
    NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, 
                           kSecMatchLimitAll,kSecMatchLimit,
                           kCFBooleanTrue,kSecReturnAttributes,nil];
    CFTypeRef result = nil;
    OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result);
    NSLog(@"%@",s);
    NSLog(@"%@",result);
}

- (IBAction)sname:(id)sender {
     if(nameField.text.length >0) {
        NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, 
                               nameField.text,kSecAttrAccount,
                               kCFBooleanTrue,kSecReturnAttributes,nil];
        CFTypeRef result = nil;
        OSStatus s = SecItemCopyMatching((CFDictionaryRef)query, &result);
        NSLog(@"%@",s);          
        NSLog(@"%@",result);        
        if(s == noErr) {
            NSMutableDictionary* dic = [NSMutableDictionary dictionaryWithDictionary:result];
            [dic setObject:(id)kCFBooleanTrue forKey:kSecReturnData];
            [dic setObject:[query objectForKey:kSecClass] forKey:kSecClass];
            NSData* data = nil;
            if(SecItemCopyMatching((CFDictionaryRef)dic, (CFTypeRef*)&data) == noErr) {
                 if(data.length)
                 NSLog(@"%@",[[[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding] autorelease]);
            }
        }
    }
}

修改

- (IBAction)update:(id)sender {
       if(nameField.text.length > 0 && passwordField.text.length >0 ) {
            NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, 
                               nameField.text,kSecAttrAccount,
                               kCFBooleanTrue,kSecReturnAttributes,nil];

       CFTypeRef result = nil;
       if(SecItemCopyMatching((CFDictionaryRef)query, &result) == noErr)
        {    
            NSMutableDictionary* update = [NSMutableDictionary dictionaryWithDictionary:(NSDictionary*)result];
            [update setObject:[query objectForKey:kSecClass] forKey:kSecClass];
            [update setObject:[passwordField.text dataUsingEncoding:NSUTF8StringEncoding] forKey:kSecValueData];
            [update removeObjectForKey:kSecClass];
 TARGET_IPHONE_SIMULATOR
            [update removeObjectForKey:(id)kSecAttrAccessGroup];

            NSMutableDictionary* updateItem = [NSMutableDictionary dictionaryWithDictionary:result];
            [updateItem setObject:[query objectForKey:(id)kSecClass] forKey:(id)kSecClass];
            OSStatus status = SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)update);
            NSLog(,status);
        }
    }
}

刪除

- (IBAction)del:(id)sender {
     if(nameField.text.length >0) {
     NSDictionary* query = [NSDictionary dictionaryWithObjectsAndKeys:kSecClassGenericPassword,kSecClass, 
                               nameField.text,kSecAttrAccount,nil];
     OSStatus status = SecItemDelete((CFDictionaryRef)query);
     NSLog(,status);         
     }
}

注意:

1.區別(標識)一個item要用kSecAttrAccount和kSecAttrService

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