草庐IT

iOS 数据存储(三) -持久化 keychain

搬砖的crystal 2023-09-17 原文

一、简介

keychain 是一个相对独立的空间,保存到 keychain钥匙串中的信息不会因为卸载/重装 app 而丢失, 。相对于 NSUserDefaultsplist 文件保存等一般方式,keychain 保存更为安全。所以我们会用 keyChain 保存一些私密信息,比如密码、证书、设备唯一码(把获取到用户设备的唯一I D 存到 keychain 里面这样卸载或重装之后还可以获取到 id,保证了一个设备一个ID)等等。keychain 是用 SQLite 进行存储的。用苹果的话来说是一个专业的数据库,加密我们保存的数据,可以通过 metadata(attributes) 进行高效的搜索。keychain 适合保存一些比较小的数据量的数据,如果要保存大的数据,可以考虑文件的形式存储在磁盘上,在keychain里面保存解密这个文件的密钥。

二、使用

keychain 的使用类似于数据库,所以也有相应的增删改查操作的语句。
需要导入 Security 库引入头文件 #import <Security/Security.h>

#import "ViewController.h"
#import <Security/Security.h>
@interface ViewController ()


@end

@implementation ViewController
NSString *const accessItem = @"XXXXXXX.com.miongpao.KeyChainDemo";
- (void)viewDidLoad {
    [super viewDidLoad];
    self.view.backgroundColor = [UIColor whiteColor];

}

- (NSMutableDictionary *)getKeychainQuery:(NSString *)service {

    return [NSMutableDictionary dictionaryWithObjectsAndKeys:(id)kSecClassGenericPassword,(id)kSecClass,service, (id)kSecAttrService,service, (id)kSecAttrAccount,(id)kSecAttrAccessibleAfterFirstUnlock,(id)kSecAttrAccessible,nil];
}
/**
 增加
 */
- (void)addKeychainData:(id)data forKey:(NSString *)key{

    NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
    SecItemDelete((__bridge CFDictionaryRef)keychainQuery);
    [keychainQuery setObject:accessItem forKey:(id)kSecAttrAccessGroup];
    [keychainQuery setObject:[NSKeyedArchiver archivedDataWithRootObject:data] forKey:(__bridge id)kSecValueData];
    SecItemAdd((__bridge CFDictionaryRef)keychainQuery, NULL);

}
/**
 删除
 */
- (void)deleteWithService:(NSString *)service {
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:service];
    SecItemDelete((CFDictionaryRef)keychainQuery);
}
/**
 修改
 */
-(void)updateKeychainData:(id)data forKey:(NSString *)key {

    NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
    [keychainQuery setObject:accessItem forKey:(id)kSecAttrAccessGroup];
    NSData * updata = [NSKeyedArchiver archivedDataWithRootObject:data];
    NSDictionary *myDate = @{(__bridge id)kSecValueData : updata};
    SecItemUpdate((__bridge CFDictionaryRef)keychainQuery, (__bridge CFDictionaryRef)myDate);

}
/**
 查询
 */
- (id)readForkey:(NSString *)key {

    id ret = nil;
    NSMutableDictionary *keychainQuery = [self getKeychainQuery:key];
    [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: %@", key, e);
        } @finally {

        }
    }

    if (keyData)CFRelease(keyData);
    return ret;
}
@end

keychain 是一个结构,有很多key-value

1.key - kSecClass
value 说明
kSecClassGenericPassword 一般密码
kSecClassInternetPassword 网络密码
kSecClassCertificate 证书
kSecClassKey 密钥
kSecClassIdentity 身份证书(带私钥的证书)
2. kSecClassGenericPassword包含的 key
key 说明 类型
kSecAttrCreationDate 创建日期 CFDateRef
kSecAttrModificationDate 最后一次修改日期 CFDateRef
kSecAttrDescription 描述 CFStringRef
kSecAttrComment 注释 CFStringRef
kSecAttrCreator 创建者 CFNumberRef(4字符,如'aLXY')
kSecAttrType 类型 CFNumberRef(4字符,如'aTyp')
kSecAttrLabel 标签(给用户看) CFStringRef
kSecAttrIsInvisible 是否隐藏 CFBooleanRef(kCFBooleanTruekCFBooleanFalse)
kSecAttrIsNegative 是否具有密码,此项表示当前的 item 是否只是一个占位项,或者说是只有 key 没有 value CFBooleanRef(kCFBooleanTruekCFBooleanFalse)
kSecAttrAccount 账户名 CFStringRef
kSecAttrService 所具有服务 CFStringRef
kSecAttrGeneric 用户自定义内容 CFDataRef
kSecAttrSecurityDomain 网络安全域 CFStringRef
kSecAttrServer 服务器域名或IP地址 CFStringRef
kSecAttrAccessible 可访问性类型透明 .
3.key - kSecAttrAccessible

这个属性,决定了我们 item 在什么条件下可以获取到里面的内容,我们在添加 item 的时候,可以添加这个属性,来增强数据的安全性,具体的主要有以下几个:

value 说明
kSecAttrAccessibleWhenUnlocked 解锁可访问,备份
kSecAttrAccessibleAfterFirstUnlock 第一次解锁后可访问,备份
kSecAttrAccessibleAlways 一直可访问,备份
kSecAttrAccessibleWhenUnlockedThisDeviceOnly 解锁可访问,不备份
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly 第一次解锁后可访问,不备份
kSecAttrAccessibleAlwaysThisDeviceOnly 一直可访问,不备份

每个意思都很明确,item 默认就是 kSecAttrAccessibleWhenUnlocked,也就是在设备未锁屏的情况下。这个也是苹果推荐的。kSecAttrAccessibleAlways 这个苹果在 WWDC 中也说了,不建议使用,苹果自己已经都弃用了。kSecAttrAccessibleAfterFirstUnlock 这个是在设备第一次解锁后,可以使用。这个最常见的就是后台唤醒功能里面,如果需要访问某个 item,那么需要使用这个属性,不然是访问不了 item 的数据的。最后几个 DeviceOnly 相关的设置,如果设置了,那么在手机备份恢复到其他设备时,是不能被恢复的。同样 iCloud 也不会同步到其他设备,因为在其他设备上是解密不出来的。

三、数据共享

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

    //添加
    NSDictionary *query = @{(__bridge id)kSecAttrAccessible : (__bridge id)kSecAttrAccessibleWhenUnlocked,
                                (__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecValueData : [@"1234562" dataUsingEncoding:NSUTF8StringEncoding],
                                (__bridge id)kSecAttrAccount : @"account name",
                                (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test",
                                (__bridge id)kSecAttrService : @"noraml1",
                                (__bridge id)kSecAttrSynchronizable : @YES,
                                };
        
    CFErrorRef error = NULL;
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)query, nil);
    
    //读取
    NSDictionary *query1 = @{(__bridge id)kSecClass : (__bridge id)kSecClassGenericPassword,
                                (__bridge id)kSecReturnRef : @YES,
                                (__bridge id)kSecReturnData : @YES,
                                (__bridge id)kSecMatchLimit : (__bridge id)kSecMatchLimitAll,
                                (__bridge id)kSecAttrAccount : @"account name",
                                (__bridge id)kSecAttrAccessGroup : @"XEGH3759AB.com.developer.test",
                                (__bridge id)kSecAttrService : @"noraml1",
                                };
        
    CFTypeRef dataTypeRef = NULL;
    OSStatus status1 = SecItemCopyMatching((__bridge CFDictionaryRef)query1, &dataTypeRef)
    //只需要添加一个kSecAttrAccessGroup属性即可。

有关iOS 数据存储(三) -持久化 keychain的更多相关文章

  1. ruby - 解析 RDFa、微数据等的最佳方式是什么,使用统一的模式/词汇(例如 schema.org)存储和显示信息 - 2

    我主要使用Ruby来执行此操作,但到目前为止我的攻击计划如下:使用gemsrdf、rdf-rdfa和rdf-microdata或mida来解析给定任何URI的数据。我认为最好映射到像schema.org这样的统一模式,例如使用这个yaml文件,它试图描述数据词汇表和opengraph到schema.org之间的转换:#SchemaXtoschema.orgconversion#data-vocabularyDV:name:namestreet-address:streetAddressregion:addressRegionlocality:addressLocalityphoto:i

  2. ruby-on-rails - Rails 模型——非持久类成员或属性? - 2

    对于Rails模型,是否可以/建议让一个类的成员不持久保存到数据库中?我想将用户最后选择的类型存储在session变量中。由于我无法从我的模型中设置session变量,我想将值存储在一个“虚拟”类成员中,该成员只是将值传递回Controller。你能有这样的类(class)成员吗? 最佳答案 将非持久属性添加到Rails模型就像任何其他Ruby类一样:classUser扩展解释:在Ruby中,所有实例变量都是私有(private)的,不需要在赋值前定义。attr_accessor创建一个setter和getter方法:classUs

  3. ruby - 如何验证 IO.copy_stream 是否成功 - 2

    这里有一个很好的答案解释了如何在Ruby中下载文件而不将其加载到内存中:https://stackoverflow.com/a/29743394/4852737require'open-uri'download=open('http://example.com/image.png')IO.copy_stream(download,'~/image.png')我如何验证下载文件的IO.copy_stream调用是否真的成功——这意味着下载的文件与我打算下载的文件完全相同,而不是下载一半的损坏文件?documentation说IO.copy_stream返回它复制的字节数,但是当我还没有下

  4. ruby - Ruby 有 `Pair` 数据类型吗? - 2

    有时我需要处理键/值数据。我不喜欢使用数组,因为它们在大小上没有限制(很容易不小心添加超过2个项目,而且您最终需要稍后验证大小)。此外,0和1的索引变成了魔数(MagicNumber),并且在传达含义方面做得很差(“当我说0时,我的意思是head...”)。散列也不合适,因为可能会不小心添加额外的条目。我写了下面的类来解决这个问题:classPairattr_accessor:head,:taildefinitialize(h,t)@head,@tail=h,tendend它工作得很好并且解决了问题,但我很想知道:Ruby标准库是否已经带有这样一个类? 最佳

  5. Ruby 文件 IO 定界符? - 2

    我正在尝试解析一个文本文件,该文件每行包含可变数量的单词和数字,如下所示:foo4.500bar3.001.33foobar如何读取由空格而不是换行符分隔的文件?有什么方法可以设置File("file.txt").foreach方法以使用空格而不是换行符作为分隔符? 最佳答案 接受的答案将slurp文件,这可能是大文本文件的问题。更好的解决方案是IO.foreach.它是惯用的,将按字符流式传输文件:File.foreach(filename,""){|string|putsstring}包含“thisisanexample”结果的

  6. ruby - 我如何添加二进制数据来遏制 POST - 2

    我正在尝试使用Curbgem执行以下POST以解析云curl-XPOST\-H"X-Parse-Application-Id:PARSE_APP_ID"\-H"X-Parse-REST-API-Key:PARSE_API_KEY"\-H"Content-Type:image/jpeg"\--data-binary'@myPicture.jpg'\https://api.parse.com/1/files/pic.jpg用这个:curl=Curl::Easy.new("https://api.parse.com/1/files/lion.jpg")curl.multipart_form_

  7. 世界前沿3D开发引擎HOOPS全面讲解——集3D数据读取、3D图形渲染、3D数据发布于一体的全新3D应用开发工具 - 2

    无论您是想搭建桌面端、WEB端或者移动端APP应用,HOOPSPlatform组件都可以为您提供弹性的3D集成架构,同时,由工业领域3D技术专家组成的HOOPS技术团队也能为您提供技术支持服务。如果您的客户期望有一种在多个平台(桌面/WEB/APP,而且某些客户端是“瘦”客户端)快速、方便地将数据接入到3D应用系统的解决方案,并且当访问数据时,在各个平台上的性能和用户体验保持一致,HOOPSPlatform将帮助您完成。利用HOOPSPlatform,您可以开发在任何环境下的3D基础应用架构。HOOPSPlatform可以帮您打造3D创新型产品,HOOPSSDK包含的技术有:快速且准确的CAD

  8. ruby - Rack:如何将 URL 存储为变量? - 2

    我正在编写一个简单的静态Rack应用程序。查看下面的config.ru代码:useRack::Static,:urls=>["/elements","/img","/pages","/users","/css","/js"],:root=>"archive"map'/'dorunProc.new{|env|[200,{'Content-Type'=>'text/html','Cache-Control'=>'public,max-age=6400'},File.open('archive/splash.html',File::RDONLY)]}endmap'/pages/search.

  9. FOHEART H1数据手套驱动Optitrack光学动捕双手运动(Unity3D) - 2

    本教程将在Unity3D中混合Optitrack与数据手套的数据流,在人体运动的基础上,添加双手手指部分的运动。双手手背的角度仍由Optitrack提供,数据手套提供双手手指的角度。 01  客户端软件分别安装MotiveBody与MotionVenus并校准人体与数据手套。MotiveBodyMotionVenus数据手套使用、校准流程参照:https://gitee.com/foheart_1/foheart-h1-data-summary.git02  数据转发打开MotiveBody软件的Streaming,开始向Unity3D广播数据;MotionVenus中设置->选项选择Unit

  10. 使用canal同步MySQL数据到ES - 2

    文章目录一、概述简介原理模块二、配置Mysql使用版本环境要求1.操作系统2.mysql要求三、配置canal-server离线下载在线下载上传解压修改配置单机配置集群配置分库分表配置1.修改全局配置2.实例配置垂直分库水平分库3.修改group-instance.xml4.启动监听四、配置canal-adapter1修改启动配置2配置映射文件3启动ES数据同步查询所有订阅同步数据同步开关启动4.验证五、配置canal-admin一、概述简介canal是Alibaba旗下的一款开源项目,Java开发。基于数据库增量日志解析,提供增量数据订阅&消费。Git地址:https://github.co

随机推荐