iOS密码存储

本文主要整理下iOS密码存储的相关知识。

毫无疑问,密码存储是一个比较老生常谈的问题,原先项目中使用的是SFHFKeychainUtils,不知道什么原因使用的它。不过还算好用,没出现什么问题。

本着猎奇心理,在github上搜索ios keychain关键字,找出了星星最多的前两个开源包:

SSKeychain

UICKeyChainStore

当然使用就使用星星最多的SSKeychain,pod install下,就应用到了项目里。但是使用了一段时间经常会发生errSecDefault问题

出于无奈,我开始看UICKeyChainStore,但它貌似不如SSKeychain,没有提供account。又想起原先使用的SFHFKeychainUtils

基于以上原因,也或许原先仅是拿过来使用,而没有理解里面的实现细节,故整理下:

苹果官方相关信息

苹果官方:Keychain Services Programming Guide

苹果官方: Keychain Services Reference

苹果官方代码

由于上方文档,即有Mac OS X的,又有iOS的,仅摘抄下关于iOS的重要部分:

A keychain is an encrypted container that holds passwords for multiple applications and secure services. Keychains are secure storage containers, which means that when the keychain is locked, no one can access its protected contents. In OS X, users can unlock a keychain—thus providing trusted applications access to the contents—by entering a single master password. In iOS, each application always has access to its own keychain items; the user is never asked to unlock the keychain. Whereas in OS X any application can access any keychain item provided the user gives permission, in iOS an application can access only its own keychain items.

钥匙链是一个加密的容器,它保存了多个程序和安全服务的密码。钥匙链是一个安全的存储容器,这意味着没有应用程序或服务在它锁住的情况下,可以访问它的内容。在Mac OS X下,用户可以解锁钥匙链,因此可以通过输入主密码的形式提供给信任的应用程序访问里面的内容。在iOS中,每个应用程序只能访问自己的钥匙链项;用户不能解锁钥匙链。相反在Mac OS X里,用户给予了许可,每个应用程序都能访问任何一个钥匙链项。在iOS里,一个应用程序只能访问自己的钥匙链项。

Note: On iPhone, Keychain rights depend on the provisioning profile used to sign your application. Be sure to consistently use the same provisioning profile across different versions of your application.

注意:在iPhone中,钥匙链依赖于 provisioning profile 文件来标识你的应用程序。确保应用程序在不同版本中一贯的使用同一个 provisioning profile 文件

In iOS,there is a single keychain accessible to applications. Although it stores the keychain items of all the applications on the system, an application can access only its own keychain items (with the possible exception of a keychain item for which the application that created it obtained a persistent reference).

在iOS里,有一个单独的钥匙链供应用程序访问。尽管他存储了系统里全部应用程序的钥匙链项,一个应用程序只能并且仅能访问它自己的钥匙链项(包括因为应用程序创建包含了一个持久的引用二引发的可能的钥匙链项异常)

Structure of a Keychain
Each keychain can contain any number of keychain items. Each keychain item contains data plus a set of attributes. For a keychain item that needs protection, such as a password or private key (a string of bytes used to encrypt or decrypt data), the data is encrypted and protected by the keychain. For keychain items that do not need protection, such as certificates, the data is not encrypted.

钥匙链结构

每一个钥匙链可以包含任意数量的钥匙链项。每个钥匙链项包含数据加上一些属性集。对一个钥匙链项来说需要进行保护,比如像一个密码或这个私有键(加密或者揭秘的二进制数据字符串),数据被钥匙链加密和保护。对多个钥匙链项来说不需要保护,比如像证书,它的数据是不加密的。

The iOS gives an application access to only its own keychain items.

在iOS中,仅给一个用程序访问它自己的钥匙项权限。

The iOS Keychain Services API uses a different paradigm. This API has a single function (SecItemAdd) for adding an item to a keychain.

在iOS钥匙链服务API使用了一个不同的范式(相对于Mac OS X)。这个API有一个单独的函数(SecItemAdd)来添加一个钥匙链项。

In iOS, you call the SecItemCopyMatching function to find a keychain item owned by your application. In this case there’s only one keychain and the user is never prompted to unlock it.

在iOS中,你调用SecItemCopyMatching函数来查找你应用程序自己的钥匙链项目。在这种情况下,仅有一个钥匙链并且用户不需要被提示来解锁它(相对于Mac OS X)。

iOS_mi_ma_cun_chu.jpg

iOS钥匙链服务搜索字典

在iOS,钥匙链服务使用键值对字典方式来制定钥匙链项的属性,这样你就能查找或者创建。

经典的搜索字典组成:

class key:用来指定要搜索的钥匙链项的类型(比如,互联网密码 或者密码密钥)。

一个或多个键值对来指定匹配属性数据(比如标签或者创建日期)

一个或多个搜索键值对,用来指定值来精确搜索,比如发行证书或者邮件地址来匹配。

一个返回值键值对,来指定你想要的数据(比如。一个字典或者是一个偏好引用)。

什么样的属性值被指定,取决于你要搜索的钥匙链项的类型。举例,如果你指定键位kSecClass的值为kSecClassGenericPassword,然后你可以指定创建时间的值或这个修改时间的值,但是不能指定主题或发行者(这个被用于证书)。

举例,如果你想执行一个对AppStore账户名为'ImaUser'密码不敏感情况的搜索,你可以在SecItemCopyMatching函数,使用上面表格中的键值对。

kSecReturnData键是函数返回钥匙链项的数据,如果你想得到键值对的值,你需要使用kSecReturnAttributes返回类型键并且值为kCFBooleanTrue.

制作一个类似SSKeychain的翻版

0.设置密码(添加、更新密码)

/**
*  @author iYiming, 15-04-19 22:20:51
*
*  设置密码(添加密码、修改密码)
*
*  @param password    密码
*  @param serviceName 服务名称
*  @param account     账户名称
*
*  @return 是否设置成功
*/
+ (BOOL) setPassword:(NSString *)password forService:(NSString *)serviceName account:(NSString *)account{
    NSMutableDictionary *keyChainDic = [[NSMutableDictionary alloc] init];
    [keyChainDic setObject:account forKey:(__bridge id)kSecAttrAccount];//账户
    [keyChainDic setObject:serviceName forKey:(__bridge id)kSecAttrService];//服务名
    [keyChainDic setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];//类型
    [keyChainDic setObject:[password dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];//密码数据
    [keyChainDic setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];//返回数据键值对
    [keyChainDic setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];//返回Data

    CFMutableDictionaryRef outDictionary = nil;
    OSStatus keychainErr = SecItemCopyMatching((__bridge CFDictionaryRef)keyChainDic,(CFTypeRef *)&outDictionary);

    BOOL resultState = NO;
    if (keychainErr == noErr) {
        NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:(__bridge_transfer NSMutableDictionary *)outDictionary];
        [returnDictionary setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

        [keyChainDic removeObjectForKey:(__bridge id)kSecAttrAccount];
        [keyChainDic removeObjectForKey:(__bridge id)kSecAttrService];
        [keyChainDic removeObjectForKey:(__bridge id)kSecClass];
        [keyChainDic removeObjectForKey:(__bridge id)kSecReturnAttributes];
        [keyChainDic removeObjectForKey:(__bridge id)kSecReturnData];
        OSStatus errorCode = SecItemUpdate((__bridge CFDictionaryRef)returnDictionary, (__bridge CFDictionaryRef)keyChainDic);

        if (errorCode == noErr) {
            resultState = YES;
        }else{
            resultState = NO;
        }
    }else if (keychainErr == errSecItemNotFound) {
        //添加
        OSStatus errorCode = SecItemAdd((__bridge CFDictionaryRef)keyChainDic,NULL);

        if (errorCode == noErr) {
            resultState = YES;
        }else{
            resultState = NO;
        }
    }

    return resultState;
}

1.获取密码

/**
*  @author iYiming, 15-04-19 22:21:38
*
*  获取密码
*
*  @param serviceName 服务名称
*  @param account     账户名称
*
*  @return 密码
*/
+ (NSString *) passwordForService:(NSString *)serviceName account:(NSString *)account
{
    NSMutableDictionary *keyChainDic = [[NSMutableDictionary alloc] init];
    [keyChainDic setObject:account forKey:(__bridge id)kSecAttrAccount];
    [keyChainDic setObject:serviceName forKey:(__bridge id)kSecAttrService];
    [keyChainDic setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    [keyChainDic setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];

    CFDataRef passwordData = NULL;
    OSStatus keychainError = noErr; 
    keychainError = SecItemCopyMatching((__bridge CFDictionaryRef)keyChainDic,(CFTypeRef *)&passwordData);

    NSString *pwdStr = nil;
    if (keychainError == noErr){
        NSString *passwordString = [[NSString alloc] initWithBytes:[(__bridge_transfer NSData *)passwordData bytes]
        length:[(__bridge NSData *)passwordData length] encoding:NSUTF8StringEncoding];
        NSLog(@"%@",passwordString);
        pwdStr = passwordString;
    }else if (keychainError == errSecItemNotFound) {
        pwdStr = nil;
    }

    return pwdStr;
}

2.删除密码

/**
*  @author iYiming, 15-04-19 22:22:10
*
*  删除密码
*
*  @param serviceName 服务名称
*  @param account     账户名称
*
*  @return 是否删除成功状态
*/
+ (BOOL) deletePasswordForService:(NSString *)serviceName account:(NSString *)account {
    NSMutableDictionary *keyChainDic = [[NSMutableDictionary alloc] init];
    [keyChainDic setObject:account forKey:(__bridge id)kSecAttrAccount];
    [keyChainDic setObject:serviceName forKey:(__bridge id)kSecAttrService];
    [keyChainDic setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    [keyChainDic setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnAttributes];

    CFMutableDictionaryRef outDictionary = nil;

    OSStatus keychainError = SecItemCopyMatching((__bridge CFDictionaryRef)keyChainDic,(CFTypeRef *)&outDictionary);
    BOOL resultState = NO;

    if (keychainError == noErr){
        NSString *pwd = [YMKeyChain passwordForService:serviceName account:account];//YMKeyChain 自己写的是一个类,调用的上面的方法

        [keyChainDic setObject:[pwd dataUsingEncoding:NSUTF8StringEncoding] forKey:(__bridge id)kSecValueData];

        OSStatus keychainError =  SecItemDelete((__bridge CFDictionaryRef)keyChainDic);//密码正确才能删除成功
        if (keychainError == noErr){
            resultState = YES;
        }else{
            resultState = NO;
        }
    }

    return resultState;
}

已经放到 GitHub 上。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 195,360评论 5 461
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 82,195评论 2 373
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 142,543评论 0 323
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,420评论 1 266
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,242评论 4 357
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,224评论 1 273
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,626评论 3 384
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,301评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,589评论 1 292
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,660评论 2 312
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,423评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,286评论 3 313
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,662评论 3 299
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,951评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,237评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,613评论 2 342
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,804评论 2 335

推荐阅读更多精彩内容