iOS KeyChain 浅析以及应用(数据AES加密)附demo

一.iOS钥匙串KeyChain 解析
     根据苹果的介绍,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;

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值