前言 本文将向大家介绍如何使用 iOS Keychain(钥匙串)
来 保护用户敏感数据
。iOS Keychain
是一个安全的存储解决方案,可以将 用户名、密码、证书、私钥等
敏感数据存储在设备的安全存储区域中,并且只有经过身份验证的应用程序才能访问这些数据。
为什么不使用 NSUserDefaults 存储敏感数据? 不安全:因为 NSUserDefaults
将数据以 plist文件
形式存储到 沙盒
中的 Library/Preferences
中,如果 沙盒被破解或手机被越狱
,那么就能轻松拿到这个文件,就能轻松读取都存储的信息,不安全
。
如何解决把 密码等敏感数据存储在 NSUserDefaults 不安全的问题?
通过 加密算法加密,然后存储,例如 不可逆的MD5加密
1 #import <CommonCrypto/CommonDigest.h>
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 // 简单的MD5加密 + ( NSString *)md5String:( NSString *)str{ const char *myPasswd = [str UTF8String ]; unsigned char mdc[ 16 ]; CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc); NSMutableString *md5String = [ NSMutableString string ]; for ( int i = 0 ; i< 16 ; i++) { [md5String appendFormat : @"%02x" ,mdc[i]]; } return md5String; }
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 // 复杂一些的MD5加密 + ( NSString *)md5String:( NSString *)str{ const char *myPasswd = [str UTF8String ]; unsigned char mdc[ 16 ]; CC_MD5 (myPasswd, ( CC_LONG ) strlen (myPasswd), mdc); NSMutableString *md5String = [ NSMutableString string ]; [md5String appendFormat : @"%02x" ,mdc[ 0 ]]; for ( int i = 1 ; i< 16 ; i++) { [md5String appendFormat : @"%02x" ,mdc[i]^mdc[ 0 ]]; } return md5String; }
使用 keyChain
来保存密码
更加保险的方法是把密码保存在iOS提供的 keychain
中,并且删除应用后,密码不会删除,下载安装还能使用
什么是 iOS Keychain?
Keychain
是一个存储在文件下的简单数据库。Keychain
有任意数量的 钥匙链(item)
,该钥匙链中包含一组属性。该属性和钥匙链的 类型
相关。创建日期和label对所有的钥匙链是通用的。其他的都是根据钥匙链的类型不同而不同,比如,generic password类型
包含 service
和 account
属性。
钥匙链可以使用 kSecAttrSynchronizable
同步属性,被标记为该属性的值都可以被放置在iCloud
的钥匙链中,它会被自动同步到相同账号的设备上。
有些钥匙链需要保护起来,比如 密码和私人key
,都会被加密;对于那些不需要被保护的钥匙链,比如证书,就不会被加密。
在iOS设备上(手机),当屏幕被解锁时,钥匙链的访问权限就会被打开。
iOS Keychain
是一个安全的存储解决方案,可以将敏感数据存储在设备的安全存储区域中。这个安全存储区域被称为 Keychain
,它是一种加密的数据库,可以存储 用户名、密码、证书、私钥等
敏感数据。Keychain可以防止未经授权的应用程序访问用户的敏感数据。
除了做 数据存储
,它还可以做 App间的数据共享
。
如何在iOS应用程序中使用Keychain?
使用 Keychain API
在iOS应用程序中 存储和访问
敏感数据是很容易的。
在 Objective-C
中,可以使用 Security
框架中的 SecItem API
来执行 Keychain操作。
在 Swift
中,可以使用 Security
框架中的 Security.framework
。这个框架提供了一些函数和数据结构,可以帮助我们执行Keychain操作。
访问 keychain
保存的 类型
,有5中:
kSecClassGenericPassword:存储一般密码,比较常用的item
kSecClassInternetPasswork:存储网络密码的item
kSecClassCertificate:存储证书的item
kSecClassKey:存储私有秘钥的item
kSecClassIdentity:存储一个包含证书和私有秘钥的item
使用Keychain API来存储和访问敏感数据的基本步骤
创建一个 字典
,用于 描述要存储的敏感数据
。
调用 SecItemAdd
函数来将敏感数据添加到Keychain中。
1 2 OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result) API_AVAILABLE(macos(10.6), ios(2.0));
attributes由四部分组成:
item的类型:必选,key = kSecClass,value = 上面说的5种item
item 的存储数据
属性,可以用来做一些标记,用于查找或者程序之间的数据共享,但是不同的kSecClass有不同的属性
返回的数据类型,它的设置影响参数 result:对添加的item的引用,如果没有什么需要,可以设置为 nil
注: 1、要保证每次添加都是不同账户 2、注意变量内存释放 3、要注意数据类型要对
调用 SecItemCopyMatching
函数来从Keychain中检索敏感数据。
1 2 OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result) API_AVAILABLE(macos(10.6), ios(2.0));
query的组成:
item的类型:必选,key = kSecClass,value = 上面说的5种item
要查找的属性及值,这个可以用来筛选掉多余的选项,这个值越详细,最后获取的结果越精确
做进一步的限制,比如大小写是否敏感等等
返回匹配项的个数,但是注意 kSecReturnData
和 kSecMatchLimit
不能共存,更多的kSecMatchLimit
可以搭配 kSecReturnAttributes
使用
注: 注意一下返回类型
调用 SecItemUpdate
函数来更新Keychain中的敏感数据。
调用 SecItemDelete
函数来删除Keychain中的敏感数据。
1 2 OSStatus SecItemDelete(CFDictionaryRef query) API_AVAILABLE(macos(10.6), ios(2.0));
在 iOS
中,我们可以使用 Keychain API
来存储和访问敏感数据,下面以 OC
语言为例,实现一个存储 企业ID、账号、密码
的例子:
使用
创建一个 ZJKeyChainManager 管理类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 @interface ZJKeyChainManager : NSObject + (NSMutableDictionary *)getKeychainQuery:(NSString *)service; // save enterprise account password to keychain + (void)save:(NSString *)service data:(id)data; // load enterprise account password from keychain + (id)load:(NSString *)service; // delete enterprise account password from keychain + (void)delete:(NSString *)serviece; #pragma mark - public // 保存登录信息到钥匙串 + (void)saveLoginInfoToKeyChain; // 读取钥匙串登录信息 + (NSMutableArray *)readLoginInfoFromKeyChain; // 删除钥匙串缓存登录信息 + (void)deleteLoginInfoFromKeyChain; @end
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 #import "ZJKeyChainManager.h" #import <Security/Security.h> @implementation ZJKeyChainManager #pragma mark - 创建一个字典,描述要存储的敏感数据 + (NSMutableDictionary *)getKeychainQuery:(NSString *)service { return [NSMutableDictionary dictionaryWithObjectsAndKeys: (id)kSecClassGenericPassword,(id)kSecClass, service, (id)kSecAttrService, service, (id)kSecAttrAccount, (id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible, nil]; } #pragma mark -写入 + (void)save:(NSString *)service data:(id)data { // Get search dictionary NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; // Delete old item before add new item SecItemDelete((CFDictionaryRef)keychainQuery); // Add new object to search dictionary(Attention:the data format) [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(id)kSecValueData]; // Add item to keychain with the search dictionary SecItemAdd((CFDictionaryRef)keychainQuery, NULL); } #pragma mark -读取 + (id)load:(NSString *)service { id ret = nil; NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; // Configure the search setting // Since in our simple case we are expecting only a single attribute to be returned (the password) we can set the attribute kSecReturnData to kCFBooleanTrue [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; } #pragma mark -删除 + (void)delete:(NSString *)service { NSMutableDictionary *keychainQuery = [self getKeychainQuery:service]; SecItemDelete((CFDictionaryRef)keychainQuery); } #pragma mark - public // 保存登录信息到钥匙串 + (void)saveLoginInfoToKeyChain{ // 企业ID NSString *enterpriseId = [NSObject getSaveDataWithKey:JITENTERPRISEID]; // 账号 NSString *account = [NSObject getSaveDataWithKey:JITACCOUNT]; // 密码 NSString *password = [NSObject getSaveDataWithKey:JITPASSWORD]; // 需要存储的键值对 NSMutableDictionary * kvPair = [NSMutableDictionary dictionary]; [kvPair setObject:enterpriseId forKey:KEY_ENTERPRISEID]; [kvPair setObject:account forKey:KEY_ACCOUNT]; [kvPair setObject:password forKey:KEY_PASSWORD]; NSLog(@"%@", kvPair); // 将存储内容写入keychain [ZJKeyChainManager save:KEY_ENTERPRISEID_ACCOUNT_PASSWORD data:kvPair]; } // 读取钥匙串登录信息 + (NSMutableArray *)readLoginInfoFromKeyChain{ NSMutableArray * mArr = @[].mutableCopy; // 从keychain中读取存储内容 NSMutableDictionary * readKvPair = (NSMutableDictionary *)[ZJKeyChainManager load:KEY_ENTERPRISEID_ACCOUNT_PASSWORD]; NSString *enterpriseId = [readKvPair objectForKey:KEY_ENTERPRISEID]; NSString *account = [readKvPair objectForKey:KEY_ACCOUNT]; NSString *password = [readKvPair objectForKey:KEY_PASSWORD]; NSLog(@"enterpriseId = %@ account = %@ password = %@", enterpriseId,account,password); [mArr addObjectsFromArray:@[enterpriseId,account,password]]; return mArr; } // 删除钥匙串缓存登录信息 + (void)deleteLoginInfoFromKeyChain{ // 将存储内容从keychain中删除 [ZJKeyChainManager delete:KEY_ENTERPRISEID_ACCOUNT_PASSWORD]; } @end
外部使用
1 2 3 4 5 6 7 8 // 服务 #define KEY_ENTERPRISEID_ACCOUNT_PASSWORD @"cn.com.jit.DigitalGuard.enterpriseaccountpassword" // 企业ID #define KEY_ENTERPRISEID @"cn.com.jit.DigitalGuard.enterpriseid" // 账号 #define KEY_ACCOUNT @"cn.com.jit.DigitalGuard.account" // 密码 #define KEY_PASSWORD @"cn.com.jit.DigitalGuard.password"
1 2 3 4 5 6 // 需要存储的键值对 NSMutableDictionary * kvPair = [NSMutableDictionary dictionary]; [kvPair setObject:enterpriseId forKey:KEY_ENTERPRISEID]; [kvPair setObject:account forKey:KEY_ACCOUNT]; [kvPair setObject:password forKey:KEY_PASSWORD]; DLogInfo(@"%@", kvPair);
1 2 // 将存储内容写入keychain [ZJKeyChainManager save:KEY_ENTERPRISEID_ACCOUNT_PASSWORD data:kvPair];
1 2 3 4 5 NSMutableDictionary * readKvPair = (NSMutableDictionary *)[ZJKeyChainManager load:KEY_ENTERPRISEID_ACCOUNT_PASSWORD]; NSString *enterpriseId = [readKvPair objectForKey:KEY_ENTERPRISEID]; NSString *account = [readKvPair objectForKey:KEY_ACCOUNT]; NSString *password = [readKvPair objectForKey:KEY_PASSWORD]; NSLog(@"enterpriseId = %@ account = %@ password = %@", enterpriseId,account,password);
1 [ZJKeyChainManager delete:KEY_ENTERPRISEID_ACCOUNT_PASSWORD];
其他 保护用户数据安全,我们可以考虑以下几种方案:
Keychain
是iOS中一种 安全的存储方式
,可以用于存储 用户名、密码、证书、密钥等
敏感数据。Keychain中存储的 数据是加密的
,只能被当前应用程序访问
。因此,使用Keychain来存储敏感数据可以增强应用程序的安全性。
如果您需要在应用程序中存储敏感数据,您可以 使用加密算法对数据进行加密
。这样可以确保即使数据被盗,攻击者也无法访问敏感信息。
当您的应用程序需要与服务器进行通信时,建议使用HTTPS协议。HTTPS使用TLS/SSL协议来加密数据,从而确保通信安全。
总结 本文主要介绍了如何使用 keyChain
来存储和访问iOS应用程序中的敏感数据,同时也介绍了一些保护数据安全的一些方案,通过这些措施,我们可以保护用户的数据安全,增强应用程序的安全性。