keychain services on iPhone
2010-12-29 17:41 Tracy E 阅读(1706) 评论(3) 编辑 收藏 举报keychain services on iPhone:
# When a user backs up iPhone data,the keychain data is backed up but the secrets in the keychain remain encrypted in the backup.The keychain password is not included in the backup.Therefore,passwords and other secrets stored in the keychain on the iPhone cannot be used by someone who gains access to an iPhone backup. For this reason, it is important to use the keychain on iPhone to store passwords and other data(such as cookies)that can be used to log into secure web sites.
Adding keychain services to your application:
* SecItemAdd -- to add an item to a keychain
* SecItemUpdate -- to modify an existing keychain item
* SecItemCopyMatching -- to find a keychain item and extract information from it
// //KeychainWrapper.h //keychainTest // //Created by Tracy E on 10-12-29. //Copyright 2010 tracy.cpp@gmail.com. All rights reserved. // #import <Foundation/Foundation.h> #import <Security/Security.h> //Define an Objective-C wrapper class to hold Keychain Services code. @interface KeychainWrapper : NSObject { NSMutableDictionary *keychainData; NSMutableDictionary *genericPasswordQuery; } @property (nonatomic, retain) NSMutableDictionary *keychainData; @property (nonatomic, retain) NSMutableDictionary *genericPasswordQuery; - (void)mySetObject:(id)inObject forKey:(id)key; - (id)myObjectForKey:(id)key; - (void)resetKeychainItem; @end // //KeychainWrapper.m //keychainTest // //Created by Tracy E on 10-12-29. //Copyright 2010 tracy.cpp@gmail.com. All rights reserved. // #import "KeychainWrapper.h" //Unique string used to identify the keychain item: static const UInt8 kKeychainItemIdentifier[]= "com.apple.dts.KeychainUI\0"; @interface KeychainWrapper(PrivateMethods) - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert; - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToVonvert; - (void)writeToKeychain; @end @implementation KeychainWrapper @synthesize keychainData,genericPasswordQuery; - (id)init{ self = [superinit]; if (self) { OSStatus keychainErr = noErr; //set up the keychain search dictionary: genericPasswordQuery = [[NSMutableDictionaryalloc] init]; //this keychain item is a generic passwprd. [genericPasswordQuerysetObject:(id)kSecClassGenericPassword forKey:(id)kSecClass]; /* the kSecAttrGeneric attribute is used to store a unique string that is used to easily identify and find this keychain item.the string is first converted to an NSData object: */ NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier length:strlen((constchar*)kKeychainItemIdentifier)]; [genericPasswordQuery setObject:keychainItemID forKey:(id)kSecMatchLimit]; /* return the attributes of the keychain item (the password is acquired in the secItemFormatToDictionary: method): */ [genericPasswordQuerysetObject:(id)kCFBooleanTrueforKey:(id)kSecReturnAttributes]; //initialize the dictionary used to hold return data from the keychain: NSMutableDictionary *outDictionary = nil; //if the keychain item exists, return the attributes of the item: keychainErr = SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&outDictionary); if (keychainErr == noErr) { //convert the data dictionary into the format used by the view controller: self.keychainData = [self secItemFormatToDictionary:outDictionary]; }else if (keychainErr == errSecItemNotFound) { //put default values into the keychain if no matching //keychain item is found: [selfresetKeychainItem]; }else { //any other error is unexpected. NSAssert(NO,@"Serious error.\n"); } [outDictionary release]; } returnself; } - (void)dealloc{ [keychainDatarelease]; [genericPasswordQueryrelease]; [superdealloc]; } //implement the mySetObject:forKey method.which writes attributes to keychain: - (void)mySetObject:(id)inObject forKey:(id)key{ if (inObject == nil) { return; } id currentObject = [keychainData objectForKey:key]; if (![currentObject isEqual:inObject]) { [keychainData setObject:inObject forKey:key]; [selfwriteToKeychain]; } } //implement the myObjectForKey: method, which reads an attribute value form a dictionary: - (id)myObjectForKey:(id)key{ return [keychainDataobjectForKey:key]; } //reset the values in the keychain item, or create a new item if it doesn't already exist: - (void)resetKeychainItem{ if (!keychainData) { self.keychainData = [[NSMutableDictionaryalloc] init]; } elseif(keychainData){ /* Format the data in the keychainData dictionary into the format needed for a query and put it into temDictionary: */ NSMutableDictionary *temDictionary = [self dictionaryToSecItemFormat:keychainData]; //delete the keychain item in preparation for resetting the values: NSAssert(SecItemDelete((CFDictionaryRef) temDictionary) == noErr, @"Problem deleting current keychain item."); } //default generic data for keychain item: [keychainDatasetObject:@"Item label"forKey:(id)kSecAttrLabel]; [keychainDatasetObject:@"Item description"forKey:(id)kSecAttrDescription]; [keychainDatasetObject:@"Account"forKey:(id)kSecAttrAccount]; [keychainDatasetObject:@"Service"forKey:(id)kSecAttrService]; [keychainDatasetObject:@"Your comment here."forKey:(id)kSecAttrComment]; [keychainDatasetObject:@"password"forKey:(id)kSecValueData]; } /* implement the dictionaryToSecItemFormat: method, which takes the attributes that you want to add to the keychain item and sets up a dictionary in the format needed by Keychain Services:*/ - (NSMutableDictionary *)dictionaryToSecItemFormat:(NSDictionary *)dictionaryToVonvert{ // this method must be called with a properly populated dictionary // containing all the right key/value pairs for a keychain item search. //create the return dictionary: NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToVonvert]; //add the keychain item class and the generic attribute: NSData *keychainItemID = [NSData dataWithBytes:kKeychainItemIdentifier length:strlen((constchar *)kKeychainItemIdentifier)]; [returnDictionary setObject:keychainItemID forKey:(id)kSecAttrGeneric]; [returnDictionary setObject:(id)kSecClassGenericPasswordforKey:(id)kSecClass]; //convert the password NSString to NSData to fit the API paradigm: NSString *passwordString = [dictionaryToVonvert objectForKey:(id)kSecValueData]; [returnDictionary setObject:[passwordString dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData]; return returnDictionary; } /* implement the secItemFormatToDictionary: method, which takes the attribute dictionary obtained from the keychain item, acquires the password from the keychain, and adds it to the attribute dictionary: */ - (NSMutableDictionary *)secItemFormatToDictionary:(NSDictionary *)dictionaryToConvert{ //this method must be called with a properly populated dictionary //containing all the right key/value pairs for keychain item. //create a return dictionary populated with the attributes: NSMutableDictionary *returnDictionary = [NSMutableDictionary dictionaryWithDictionary:dictionaryToConvert]; //to acquire the password data from the keychain item. //first add the search key and class attribute required to obtain the password: [returnDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData]; [returnDictionary setObject:(id)kSecClassGenericPasswordforKey:(id)kSecClass]; //then call Keychain Services to get the password: NSData *passwordData = NULL; OSStatus keychainError = noErr; keychainError = SecItemCopyMatching((CFDictionaryRef) returnDictionary, (CFTypeRef *)&passwordData); if (keychainError == noErr) { //remove the kSecReturnData key; we don't need it anymore: [returnDictionary removeObjectForKey:(id)kSecValueData]; //convert the password to an NSString and add it to return dictionary: NSString *password = [[[NSString alloc] initWithBytes:[passwordData bytes] length:[passwordData length] encoding:NSUTF8StringEncoding] autorelease]; [returnDictionary setObject:password forKey:(id)kSecValueData]; } //don't do anything if nothing is found. else if(keychainError == errSecItemNotFound){ NSAssert(NO,@"Serious error.\n"); } [passwordData release]; return returnDictionary; } /* implement the writeToKeychain method, which is called the mySetObject routine, which in turn is called by the UI when there is new data for the keychain. This method modified an existing keychain item, or if the item does not already exist, creates a new keychain item with the new attribute value plus default values the other attributes. */ - (void)writeToKeychain{ NSDictionary *attributes = NULL; NSMutableDictionary *updateItem = NULL; //if the keychain item already exist, modify it: if (SecItemCopyMatching((CFDictionaryRef)genericPasswordQuery, (CFTypeRef *)&attributes) == noErr) { //First, get the attributes returned from the keychain and add them to the //dictionary that controls the update: updateItem = [NSMutableDictionary dictionaryWithDictionary:attributes]; //Second, get the class value from the generic password query dictionary and //add it to the updateItem dictionary: [updateItem setObject:[genericPasswordQuery objectForKey:(id)kSecClass] forKey:(id)kSecClass]; //Finally, set up the dictionary that contains new values for the attributes: NSMutableDictionary *tempCheck = [selfdictionaryToSecItemFormat:keychainData]; //remove the class -- it's not a keychain attribute: [tempCheck removeObjectForKey:(id)kSecClass]; //you can update only a single keychain item a time . NSAssert(SecItemUpdate((CFDictionaryRef)updateItem, (CFDictionaryRef)tempCheck) == noErr, @"Couldn't update the keychain item."); } else { // No previous item found ; add the new item . // The new value was added to keychainData dictionary in the mySetObject routine, // and the other values were added to the keychainData dictionary perviously. //No pointer to newly-added items is needed, so pass NULL for the second parameter: NSAssert(SecItemAdd((CFDictionaryRef)[selfdictionaryToSecItemFormat:keychainData], NULL) == noErr, @"Couldn't add the keychain item."); } } @end