iOS 鑰匙串keychain的應用

問題

目前需要保存一些用戶配置,在app刪掉後依然能正常讀取,那麼就用到【鑰匙串】

鑰匙串簡介

項目中有時會需要存儲敏感信息(如密碼、密鑰等),蘋果官方提供了一種存儲機制--鑰匙串(keychain)。
keychain是一種存儲在硬盤上的加密的數據庫。這個可能是卸載App後,keychain信息還在的原因。
keychain適合存儲 較小的數據量(不超過上千字節或上兆字節)的內容。

解決方案

JJKeychain.h

@interface JJKeychain : NSObject

//保存是先刪掉之前的key,沒有使用update,感覺這樣簡單;然後保存的value轉換爲NSData,如果value爲自定義object,則需遵循NSSecureCoding協議
+ (BOOL)setValue:(id)value forKey:(NSString *)key;

+ (BOOL)setValue:(id)value forKey:(NSString *)key forAccessGroup:(nullable NSString *)group;


+ (id)valueForKey:(NSString *)key;

+ (id)valueForKey:(NSString *)key forAccessGroup:(nullable NSString *)group;

+ (BOOL)deleteValueForKey:(NSString *)key;

+ (BOOL)deleteValueForKey:(NSString *)key forAccessGroup:(nullable NSString *)group;

+ (NSString *)getBundleSeedIdentifier;

@end

JJKeychain.m

@implementation JJKeychain

+ (NSMutableDictionary *)getKeychainQuery:(NSString *)key forAccessGroup:(NSString *)group{
    NSMutableDictionary *query = @{(__bridge id)kSecClass                   : (__bridge id)kSecClassGenericPassword,
                                          (__bridge id)kSecAttrService      : key,
                                          (__bridge id)kSecAttrAccount      : key,
                                          (__bridge id)kSecAttrAccessible   : (__bridge id)kSecAttrAccessibleAfterFirstUnlock
                                          }.mutableCopy;
    if (group != nil) {
        [query setObject:[self getFullAccessGroup:group] forKey:(__bridge id)kSecAttrAccessGroup];
    }
    
    return query;
}

+ (NSString *)getFullAccessGroup:(NSString *)group
{
    NSString *accessGroup = nil;
    NSString *bundleSeedIdentifier = [self getBundleSeedIdentifier];
    if (bundleSeedIdentifier != nil && [group rangeOfString:bundleSeedIdentifier].location == NSNotFound) {
        accessGroup = [NSString stringWithFormat:@"%@.%@", bundleSeedIdentifier, group];
    }
    return accessGroup;
}

+ (NSString *)getBundleSeedIdentifier
{
    static __strong NSString *bundleSeedIdentifier = nil;
    
    if (bundleSeedIdentifier == nil) {
        @synchronized(self) {
            if (bundleSeedIdentifier == nil) {
                NSString *_bundleSeedIdentifier = nil;
                NSDictionary *query = @{
                                        (__bridge id)kSecClass: (__bridge NSString *)kSecClassGenericPassword,
                                        (__bridge id)kSecAttrAccount: @"bundleSeedID",
                                        (__bridge id)kSecAttrService: @"",
                                        (__bridge id)kSecReturnAttributes: (__bridge id)kCFBooleanTrue
                                        };
                CFDictionaryRef result = nil;
                OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
                if (status == errSecItemNotFound) {
                    status = SecItemAdd((__bridge CFDictionaryRef)query, (CFTypeRef *)&result);
                }
                if (status == errSecSuccess) {
                    NSString *accessGroup = [(__bridge NSDictionary *)result objectForKey:(__bridge NSString *)kSecAttrAccessGroup];
                    NSArray *components = [accessGroup componentsSeparatedByString:@"."];
//                    NSLog(@"components %@",components);
                    _bundleSeedIdentifier = [[components objectEnumerator] nextObject];
                    CFRelease(result);
                }
                if (_bundleSeedIdentifier != nil) {
                    bundleSeedIdentifier = [_bundleSeedIdentifier copy];
                }
            }
        }
    }
    
    return bundleSeedIdentifier;
}

+ (BOOL)setValue:(id)value forKey:(NSString *)key{
    return [self setValue:value forKey:key forAccessGroup:nil];
}

+ (BOOL)setValue:(id)value forKey:(NSString *)key forAccessGroup:(NSString *)group{
    NSMutableDictionary *query = [self getKeychainQuery:key forAccessGroup:group];
    [self deleteValueForKey:key forAccessGroup:group];
    NSData *data = nil;
    @try {
        data = [NSKeyedArchiver archivedDataWithRootObject:value];
    } @catch (NSException *exception) {
        NSLog(@"archived failure value %@  %@",value,exception);
        return NO;
    }
    
    [query setObject:data forKey:(__bridge id)kSecValueData];
    OSStatus result = SecItemAdd((__bridge CFDictionaryRef)query, NULL);
    return result == errSecSuccess;
}

+ (BOOL)deleteValueForKey:(NSString *)key{
    return [self deleteValueForKey:key forAccessGroup:nil];
}

+ (BOOL)deleteValueForKey:(NSString *)key forAccessGroup:(NSString *)group{
    NSMutableDictionary *query = [self getKeychainQuery:key forAccessGroup:group];
    OSStatus result = SecItemDelete((__bridge CFDictionaryRef)query);
    return result == errSecSuccess;
}

+ (id)valueForKey:(NSString *)key{
    return [self valueForKey:key forAccessGroup:nil];
}

+ (id)valueForKey:(NSString *)key forAccessGroup:(NSString *)group{
    id value = nil;
    NSMutableDictionary *query = [self getKeychainQuery:key forAccessGroup:group];
    CFDataRef keyData = NULL;
    [query setObject:(__bridge id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    [query setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    if (SecItemCopyMatching((__bridge CFDictionaryRef)query, (CFTypeRef *)&keyData) == errSecSuccess) {
        @try {
            value = [NSKeyedUnarchiver unarchiveObjectWithData:(__bridge NSData *)keyData];
        }
        @catch (NSException *e) {
            NSLog(@"Unarchive of %@ failed: %@", key, e);
            value = nil;
        }
        
    }
    
    if (keyData) {
        CFRelease(keyData);
    }
    return value;
}

@end

參考:https://www.jianshu.com/p/354ea1279e68

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章