Recently, the project needs to store the unique identifier of the user, but if the user reinstalls the APP, it will get a new UDID. After querying a series of information, the UDID can be stored using Keychain. Then, even if the APP is reinstalled, the UDID stored before can be read from the Keychain.
The Keychain is stored in the internal database of the iOS system and is managed and encrypted by the system. Even if an APP is deleted, the data stored in the Keychain is not deleted.
You must first open Keychain in Capabilities, as shown in figure 4.
Well, doing Keychain operations without it will return an error code of 34018.
Don’t forget to introduce imports, of course
The Keychain storage process is as follows:
In simple terms, if you want to store a password, you need to first traverse the Keychain to see if its password already exists in the Keychain. If so, update its value. If not, store it. So this uses the method provided by Apple:
OSStatus SecItemCopyMatching(CFDictionaryRef query, CFTypeRef *result); // add OSStatus SecItemAdd(CFDictionaryRef Attributes, CFTypeRef *result); OSStatus SecItemUpdate(CFDictionaryRef query, CFDictionaryRef attributesToUpdate); // delete OSStatus SecItemDelete(CFDictionaryRef query)Copy the code
Next, look at the key-value commonly used to operate a Keychain.
KSecClass: has five values, Respectively kSecClassGenericPassword (general password - next to use), kSecClassInternetPassword (Internet password), kSecClassCertificate (certificate), KSecClassKey and kSecClassIdentity kSecAttrService: service kSecAttrServer: domain name or IP address of the server kSecAttrAccount: account KSecMatchLimit: returns the search result. KSecMatchLimitOne, kSecMatchLimitAll //Copy the code
This is based on the CoreFundation framework and is done through a dictionary with the above key-value values, so it’s a bit cumbersome to write. Fortunately, Apple provides a source code Demo. In Demo, service, Account, and accessGroup are used to store passwords, which is a Keychain table of type kSecClassGenericPassword. Each account corresponds to one password. It is very convenient to store key-value type in APP.
However, since it is written in Swift, it is not suitable to introduce mixed editing in my small project, so I copied an OC version according to the logic of demo. In my opinion, OC is easier to understand. After removing all kinds of try catches, my thinking will be much clearer.
Initialization method:
- (instancetype)initWithSevice:(NSString *)service account :(NSString *)account accessGroup:(NSString *)accessGroup { if (self = [super init]) { _service = service; _account = account; _accessGroup = accessGroup; } return self; }Copy the code
The main methods are:
- (void)savePassword:(NSString *)password; - (BOOL)deleteItem; - (NSString *)readPassword; // return all Keychain Item + (NSArray *)passwordItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup;Copy the code
(The specific code is posted in the back, so as not to affect the length, interested friends can also see the demo)
To use: Storage:
KeychainWrapper *wrapper = [[KeychainWrapper alloc] initWithSevice:kKeychainService account:self.accountField.text accessGroup:kKeychainAccessGroup];
[wrapper savePassword:self.passwordField.text];Copy the code
Read the current account password:
KeychainWrapper *item = [[KeychainWrapper alloc] initWithSevice:service account:acount accessGroup:accessGroup];
[item readPassword];Copy the code
Return all keychains of services under the current accessGroup:
NSArray *keychains = [KeychainWrapper passwordItemsForService:kKeychainService accessGroup:kKeychainAccessGroup];Copy the code
OSStatus Status error code:
- 50: Request parameters are incorrect. I used to write kSecAttrService as kSecAttrServer and crash until I cried…
- 34018: Capabilities…
Code:
- (void)savePassword:(NSString *)password { NSData *encodePasswordData = [password dataUsingEncoding:NSUTF8StringEncoding]; // if password was existed, update NSString *originPassword = [self readPassword]; if (originPassword.length > 0) { NSMutableDictionary *updateAttributes = [NSMutableDictionary dictionary]; updateAttributes[(__bridge id)kSecValueData] = encodePasswordData; NSMutableDictionary *query = [self keychainQueryWithService:_service account:_account accessGroup:_accessGroup]; OSStatus statusCode = SecItemUpdate( (__bridge CFDictionaryRef)query, (__bridge CFDictionaryRef)updateAttributes); NSAssert(statusCode == noErr, @"Couldn't update the Keychain Item." ); }else{ // else , add NSMutableDictionary *attributes = [self keychainQueryWithService:_service account:_account accessGroup:_accessGroup]; attributes[(__bridge id)kSecValueData] = encodePasswordData; OSStatus status = SecItemAdd((__bridge CFDictionaryRef)attributes, nil); NSAssert(status == noErr, @"Couldn't add the Keychain Item."); } } - (NSString *)readPassword { NSMutableDictionary *attributes = [self keychainQueryWithService:_service account:_account accessGroup:_accessGroup]; attributes[(__bridge id)kSecMatchLimit] = (__bridge id)(kSecMatchLimitOne); attributes[(__bridge id)kSecReturnAttributes] = (__bridge id _Nullable)(kCFBooleanTrue); attributes[(__bridge id)kSecReturnData] = (__bridge id _Nullable)(kCFBooleanTrue); CFMutableDictionaryRef queryResult = nil; OSStatus keychainError = noErr; keychainError = SecItemCopyMatching((__bridge CFDictionaryRef)attributes,(CFTypeRef *)&queryResult); if (keychainError == errSecItemNotFound) { if (queryResult) CFRelease(queryResult); return nil; }else if (keychainError == noErr) { if (queryResult == nil){return nil; } NSMutableDictionary *resultDict = (__bridge NSMutableDictionary *)queryResult; NSData *passwordData = resultDict[(__bridge id)kSecValueData]; NSString *password = [[NSString alloc] initWithData:passwordData encoding:NSUTF8StringEncoding]; return password; }else { NSAssert(NO, @"Serious error.\n"); if (queryResult) CFRelease(queryResult); } return nil; } - (NSMutableDictionary *)keychainQueryWithService:(NSString *)service account:(NSString *)account accessGroup:(NSString *)accessGroup { NSMutableDictionary *query = [NSMutableDictionary dictionary]; query[(__bridge id)kSecClass] = (__bridge id)kSecClassGenericPassword; query[(__bridge id)kSecAttrService] = service; query[(__bridge id)kSecAttrAccount] = account; query[(__bridge id)kSecAttrAccessGroup] = accessGroup; return query; } + (NSArray *)passwordItemsForService:(NSString *)service accessGroup:(NSString *)accessGroup { NSMutableDictionary *query = [[[self alloc]init] keychainQueryWithService:service account:nil accessGroup:accessGroup]; query[(__bridge id)kSecMatchLimit] = (__bridge id)kSecMatchLimitAll; query[(__bridge id)kSecReturnAttributes] = (__bridge id _Nullable)(kCFBooleanTrue); query[(__bridge id)kSecReturnData] = (__bridge id _Nullable)(kCFBooleanFalse); CFMutableArrayRef queryResult = nil; OSStatus status = noErr; status = SecItemCopyMatching((__bridge CFDictionaryRef)query,(CFTypeRef *)&queryResult); if (status == errSecItemNotFound) { return @[]; }else if (status == noErr) { NSArray *resultArray = (__bridge NSArray *)queryResult; if (resultArray.count == 0){return @[]; } NSMutableArray *passwordItems = [NSMutableArray array]; for (NSDictionary *result in resultArray) { NSString *acount = result[(__bridge id)kSecAttrAccount]; if (acount.length > 0) { KeychainWrapper *item = [[KeychainWrapper alloc] initWithSevice:service account:acount accessGroup:accessGroup]; [passwordItems addObject:item]; } } return passwordItems; }else { NSAssert(NO, @"Serious error.\n"); if (queryResult) CFRelease(queryResult); return @[]; } } - (BOOL)deleteItem { // Delete the existing item from the keychain. NSMutableDictionary *query = [self keychainQueryWithService:self.service account:self.account accessGroup:self.accessGroup]; OSStatus status = SecItemDelete((__bridge CFDictionaryRef)query); if (status ! = noErr && status ! = errSecItemNotFound) { return NO; } return true; }Copy the code
Reference: demo developer.apple.com/library/pre… www.360doc.com/content/15/… My.oschina.net/w11h22j33/b…