iOS KeyChain 淺析以及應用(資料AES加密)附demo
根據蘋果的介紹,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;