根据苹果的介绍,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);
}
demo 在此,可以下载导入调用 (后续更新补充)
此文章图片有借鉴。
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;