keychain介绍

news/2024/7/20 20:38:04 标签: ios

1. keychain概述

1.1 keychain是什么

苹果官网对钥匙串的描述

iOS keychain 是一个相对独立的空间,是用SQLite进行存储的,可以加密我们保存的数据,并且使用keychain service API增删改查。

keychain的是以item为单位存储的。data是数据本身,attributes就是数据库中的键。

1.2 keychain的优点

相对于NSUserDefaults、plist文件保存等一般方式,keychain有以下优点

  • keychain钥匙串中的信息不会因为卸载/重装app而丢失

  • keychain保存更为安全

1.3 keychain的用途

  • 保存密码、证书、设备唯一码

  • 历史数据

例子:keychain保存did和登录历史数据。

keychain适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。

2. keychain的基本使用

2.1 keychain item 的类型和主键

kSecClass可以有以下取值

  • kSecClassGenericPassword //普通密码

  • kSecClassInternetPassword  //互联网密码

  • kSecClassCertificate//证书

  • kSecClassKey//加密的内容

  • kSecClassIdentity //身份相关的

每种类型的Keychain item都有不同的键作为主要的Key也就是唯一标示符用于搜索,更新和删除

类型为GenericPassword的item必须使用kSecAttrAccount和kSecAttrService作为主要的key

item类型可指定的attributes
kSecClassInternetPassword

kSecAttrAccess (OS X only)

       kSecAttrAccessControl

       kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable and/or kSecUseDataProtectionKeychain set)

       kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable and/or kSecUseDataProtectionKeychain set)

       kSecAttrCreationDate

       kSecAttrModificationDate

       kSecAttrDescription

       kSecAttrComment

       kSecAttrCreator

       kSecAttrType

       kSecAttrLabel

       kSecAttrIsInvisible

       kSecAttrIsNegative

       kSecAttrAccount

       kSecAttrSecurityDomain

       kSecAttrServer

       kSecAttrProtocol

       kSecAttrAuthenticationType

       kSecAttrPort

       kSecAttrPath

       kSecAttrSynchronizable

kSecClassGenericPassword

kSecAttrAccess (OS X only)

       kSecAttrAccessControl

       kSecAttrAccessGroup (iOS; also OS X if kSecAttrSynchronizable and/or kSecUseDataProtectionKeychain set)

       kSecAttrAccessible (iOS; also OS X if kSecAttrSynchronizable and/or kSecUseDataProtectionKeychain set)

       kSecAttrCreationDate

       kSecAttrModificationDate

       kSecAttrDescription

       kSecAttrComment

       kSecAttrCreator

       kSecAttrType

       kSecAttrLabel

       kSecAttrIsInvisible

       kSecAttrIsNegative

       kSecAttrAccount

       kSecAttrService

       kSecAttrGeneric

       kSecAttrSynchronizable

kSecClassCertificate

kSecAttrAccessible    (iOS only)

       kSecAttrAccessControl (iOS only)

       kSecAttrAccessGroup   (iOS only)

       kSecAttrCertificateType

       kSecAttrCertificateEncoding

       kSecAttrLabel

       kSecAttrSubject

       kSecAttrIssuer

       kSecAttrSerialNumber

       kSecAttrSubjectKeyID

       kSecAttrPublicKeyHash

       kSecAttrSynchronizable

attributes取值及意义
kSecAttrAccessible(keychain item保护等级)

1. kSecAttrAccessibleWhenUnlocked                          //keychain项受到保护,只有在设备未被锁定时才可以访问  

2. kSecAttrAccessibleAfterFirstUnlock                      //keychain项受到保护,直到设备启动并且用户第一次输入密码  

3. kSecAttrAccessibleAlways                                //keychain未受保护,任何时候都可以访问 (Default)  

4.kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly

//keychain项受到保护,要求在使用时用户设定了密码,而且不可以转移到其他设备  

5. kSecAttrAccessibleWhenUnlockedThisDeviceOnly            //keychain项受到保护,只有在设备未被锁定时才可以访问,而且不可以转移到其他设备  

6. kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly        //keychain项受到保护,直到设备启动并且用户第一次输入密码,而且不可以转移到其他设备  

7.kSecAttrAccessibleAlwaysThisDeviceOnly                  //keychain未受保护,任何时候都可以访问,但是不能转移到其他设备

2.2 keychain的增删改查

keychain的增删改查API

增删改查
SecItemAdd
SecItemDelete
SecItemUpdate
SecItemCopyMatching

OSStatus SecItemAdd(CFDictionaryRef attributes, CFTypeRef * __nullable CF_RETURNS_RETAINED result)
OSStatus SecItemDelete(CFDictionaryRef query)
OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate)
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef * __nullable CF_RETURNS_RETAINED result)

要增加一个keychainItem,要指定其kSecClass(item类型),attributes(属性)和data(存储的数据)

Keychain内部不允许添加重复的Item。如果已存在同样的item,调用SecItemAdd会返回错误码

增加一个keychain item的例子

NSMutableDictionary *keychainQuery = [NSMutableDictionary dictionaryWithDictionary:@{
(__bridge id)kSecClass    : (__bridge id)kSecClassGenericPassword,
                                            (__bridge id)kSecAttrService      : @"kuaishou.com",
                                            (__bridge id)kSecAttrAccount      : @"your user name",
                                            (__bridge id)kSecAttrAccessible   : (__bridge id)kSecAttrAccessibleWhenUnlocked
                                            (__bridge 
id)kSecValueData        : @"your password"
                                            }];
                                            
OSStatus status = SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);

要删除一个keychainItem,要指定其kSecClass(item类型),attributes(属性)。

要注意的是尽量用多个字段确定这个item,防止删除了其他item。

NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : @"kuaishou.com",
(__bridge id)kSecAttrAccount : @"your user name"
                            };
OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query);

改query这个item的某些属性,要改的属性定义在update中

NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrAccount : @"kuaishou.com",
(__bridge id)kSecAttrService : @"your user name",
                            };
NSDictionary *update = @{
(__bridge id)kSecValueData : [@"654321" dataUsingEncoding:NSUTF8StringEncoding],
                             };
    
OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)update);

查询除了要指定kSecClass,kSecAttrAccount,kSecAttrService,还要指定查询结果的条数限制和查询返回的数据

指定查询结果的条数限制

kSecMatchLimit

1.kSecMatchLimitOne  //返回搜索到的第一个item(default)

2.kSecMatchLimitAll //返回搜索到的所有item

指定查询返回的数据

kSecReturnData

kSecReturnAttributes

kSecReturnRef

kSecReturnPersistentRef

如果指定为kCFBooleanTrue,返回分别是

1.对于key或者password item,返回它们的加密的data

2.返回item不加密的attributes

3.返回item的引用

4.返回item的持久引用

NSDictionary *query = @{
(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
(__bridge id)kSecAttrService : @"kuaishou.com",
(__bridge id)kSecAttrAccount : @"your user name",

(__bridge id)kSecReturnData : @YES,
(__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitOne,
                            };
    
CFTypeRef dataTypeRef = NULL;//存返回的结果
    
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, &dataTypeRef);

3.钥匙串共享

同一个开发者账号下(teamID),各个应用之间可以共享item。keychain通过keychain-access-groups来进行访问权限的控制。

在Xcode的Capabilities选项中打开Keychain Sharing即可。

每个group命名开头必须是开发者账号的teamId。

如果有多个sharedGroup,在添加的时候如果不指定,默认是第一个group。

(__bridge id)kSecAttrAccessGroup : @"your_teamId..xxx.xxx"

group也可以置为nil,这样默认也会以自己的bundleID作为Group。

.新建一个Plist文件,在Plist中的数组中添加可以访问的条目的名字(如KeychainAccessGroups.plist),并在Build-setting中进行entitlemment配置

4.keychain三方库介绍

直接调钥匙串API比较麻烦,大多数场景的需求都是键值对形式的调用。

4.1.KeychainItemWrapper,苹果原生封装的库

接口

- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *)accessGroup;
- (id)initWithIdentifier: (NSString *)identifier accessGroup:(NSString *)accessGroup accessible:(CFStringRef)accessible;
- (void)setObject:(id)inObject forKey:(id)key onError:(void(^)(SInt32 result))onError;
- (void)resetKeychainItem;

KeychainItemWrapper创建对象时会创建一个字典,用于记录钥匙串的信息,首先会指定查询的query

(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,

(__bridge id)kSecAttrGeneric : identifier,

(__bridge id)kSecReturnAttributes:(__bridge id)kCFBooleanTrue

调用SecItemCopyMatching查找是否存在相关字段,

不存在,这个字典的attributes就赋值为空字符串,

存在就再调用SecItemCopyMatching查找data 的值存在字典中

再查询的时候就直接从这个字典取值

setObject存之前,会调用SecItemCopyMatching查找是否存过,已经存在会比较新的data和旧的data是否相同,不同则调用SecItemUpdate更新,这也是苹果推荐的做法

缺点:初始化可能会调用两次SecItemCopyMatching查询,初始化后再setObject,就有4步操作了,可能会比较耗时

使用默认kSecAttrAccessible, KeychainItemWrapper会出现很多-25308的错误//User interaction is not allowed

KeychainItemWrapper写成一个单例,能得到优化

4.2.JNKeychain

接口

+ (BOOL)saveValue:(id)value forKey:(NSString*)key;
+ (BOOL)saveValue:(id)value forKey:(NSString*)key forAccessGroup:(NSString *)group;
+ (BOOL)saveValue:(id)value forKey:(NSString*)key forAccessGroup:(NSString *)group accessible:(CFStringRef)accessible;
+ (BOOL)deleteValueForKey:(NSString *)key;

内部指定item如下

(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,

(__bridge id)kSecAttrService : key,

(__bridge id)kSecAttrAccount : key,

(__bridge id)kSecValueData   : value的data形式

为求简单先调一遍SecItemDelete再SecItemAdd

缺点:JNKeychain如果存储的kSecAttrAccessible变了,要先deleteValueForKey之前的kSecAttrAccessible

4.3..SFHFKeychainUtils

接口

+ (NSString *) getPasswordForUsernameOld: (NSString *) username
					   andServiceName: (NSString *) serviceName
								error: (NSError **) error;


+ (NSString *) getPasswordForUsernameV2: (NSString *) username
					   andServiceName: (NSString *) serviceName
								error: (NSError **) error;


+ (BOOL) storeUsername: (NSString *) username
		   andPassword: (NSString *) password
		forServiceName: (NSString *) serviceName
		updateExisting: (BOOL) updateExisting
				 error: (NSError **) error;


+ (BOOL) deleteItemForUsername: (NSString *) username
                andServiceName: (NSString *) serviceName
                         error: (NSError **) error
                withAccessible: (BOOL) hasAccessible;

SFHFKeychainUtils和KeychainItemWrapper类似,存密码之前,会调用SecItemCopyMatching查找是否存过,已经存在会比较新的data和旧的data是否相同,不同则调用SecItemUpdate更新。

但不需要初始化,因此存密码效率更高。

缺点:不能指定kSecAttrAccessible属性,默认kSecAttrAccessibleAlways

5. 注意的点

5.1.同样的secClass、account、service,但是存储的kSecAttrAccessible变了,需要deleteValueForKey之前的kSecAttrAccessible对应的item,否则不能存储新的item

5.2.KeychainItemWrapper主线程调用,低端机上长卡顿

5.3 Kechain item字典内添加自定义key时会出现参数不合法的错误。

5.4设备越狱后相当于对苹果做签名检查的地方打了个补丁,伪造一个证书的app也能正常使用,并且加上Keychain Dumper这些工具获取Keychain内的信息会非常容易。


 


http://www.niftyadmin.cn/n/905305.html

相关文章

@codeforces - 786E@ ALT

目录 description - translationsolutionpart - 1part - 2part - 3accepted codedetailsdescription - translation 给定一棵含 n 个点的树和 m 个人,第 i 个人会从结点 xi 走到 结点 yi。 每个人有一个需求:要么他开局自带一条狗,要么他走的…

gitLab创建自己的私有库

1.在gitlab上创建私有库 code repository,代码仓库,克隆code repository到本地并添加工程CXTool,上传所有文件到远端的库中并打tag git add . git commit -m 你的修改记录 git remote add origin 代码仓库地址 // 在push之前, 查看spec是否配…

mDNS故障排查(译)

WLC上mDNS网关的理解及排查 第一部分:介绍 这篇文档描述了Bonjour协议在WLC上的操作,该文档旨在协助工程师理解该工作流量的原理以及提供故障排查的指导。 第二部分:需求和前提 知识需求: Cisco建议你对Bonjour协议、在WLC配置mDN…

Masonry

Masonry框架的优点 Masonry是iOS在控件布局中经常使用的一个轻量级框架,Masonry让NSLayoutConstraint使用起来更为简洁。 基于NSLayoutConstraint的布局实现subView.top superView.top * 1 10 subView.translatesAutoresizingMaskIntoConstraints NO;//关闭自动…

前端面试手册

分别从HTML、CSS、JavaScript、综合四个方面总结,后续持续更新 HTML部分 Doctype的作用?文档声明,不存在或格式不正确会导致文档以兼容模式呈现标准模式的排版和JS运作模式都是以该浏览器支持的最高标准运行兼容模式页面以宽松的向后兼容的方…

MJExtension用法及源码解析

MJExtension介绍 1.MJExtension是一套字典和模型之间互相转换的超轻量级框架 2.MJExtension能完成的功能 字典(JSON) --> 模型(Model) 模型(Model) --> 字典(JSON) 字典数…

一个快速检测系统CPU负载的小程序

原理说明 在对服务器进行维护时,有时也遇到由于系统 CPU(利用率)负载过高导致业务中断的情况。服务器上可能运行多个进程,查看单个进程的 CPU 都是正常的,但是整个系统的 CPU 负载可能是异常的。通过脚本对系统 CPU 负…

用MyEclipse开发REST Web Service

MyEclipse 在线订购年终抄底促销!火爆开抢>> MyEclipse最新版下载 使用MyEclipse开发RESTWeb服务来放大您的Web应用程序。在本教程示例中,您将创建一个简单的Web服务来维护客户列表。你将学会: 用于开发REST Web服务的过程部署到MyEcli…