Keychain wrapper in objective-c

Computer users often have small secrets that they need to store securely. For example, most people manage numerous online accounts. Remembering a complex, unique password for each is impossible, but writing them down is both insecure and tedious. Users typically respond to this situation by recycling simple passwords across many accounts, which is also insecure.

The keychain services API helps you solve this problem by giving your app a mechanism to store small bits of user data in an encrypted database called a keychain. When you securely remember the password for them, you free the user to choose a complicated one.

Most of the implementations available on the web are in Swift right now. I wrote this wrapper to help you in querying/updating and deleting data in objective-c.

#import <Foundation/Foundation.h>

NS_ASSUME_NONNULL_BEGIN

@interface KeyChainWrapper : NSObject
+(NSMutableDictionary*) queryDictionary:(NSString*) identifier;
+(NSString*) searchKeychainCopyMatching:(NSString*) identifier;
+(bool) createKeychainValue:(NSString*) value forIdentifier:(NSString*) identifier;
+(bool) updateKeychainValue:(NSString*) password forIdentifier:(NSString*) identifier;
+(void) deleteKeychainValue:(NSString*) identifier;
@end

NS_ASSUME_NONNULL_END
@implementation KeyChainWrapper
+(NSMutableDictionary*) queryDictionary:(NSString*) identifier
{
	NSMutableDictionary* queryDictionary = [[NSMutableDictionary alloc] init];
	[queryDictionary setObject:(id)kSecClassGenericPassword forKey:(id)kSecClass];
	NSData *encodedIdentifier = [identifier dataUsingEncoding:NSUTF8StringEncoding];
	[queryDictionary setObject:encodedIdentifier forKey:(id)kSecAttrGeneric];
	[queryDictionary setObject:encodedIdentifier forKey:(id)kSecAttrAccount];
	[queryDictionary setObject:[[NSBundle mainBundle] bundleIdentifier] forKey:(id)kSecAttrService];
	return queryDictionary;
}

+(NSString*) searchKeychainCopyMatching:(NSString*) identifier
{
	NSMutableDictionary* queryDictionary = [self queryDictionary:identifier];
	[queryDictionary setObject:(id)kSecMatchLimitOne forKey:(id)kSecMatchLimit];
	[queryDictionary setObject:(id)kCFBooleanTrue forKey:(id)kSecReturnData];
	
	CFDataRef dataResult = nil;
	OSStatus status = SecItemCopyMatching((CFDictionaryRef)queryDictionary, (CFTypeRef*) &dataResult);
	NSString* returnString = @"";
	if (status == noErr)
	{
		NSData* result = (__bridge NSData*) dataResult;
		returnString = [[NSString alloc] initWithData:result encoding:NSUTF8StringEncoding];
	}
	return returnString;
}

+(void) setSendUsageInfoKey:(bool) inEnable
{
	NSString* crashKey = @"";
	if (crashKey.length > 0)
	{
		NSString* returnValue = [KeyChainWrapper searchKeychainCopyMatching:crashKey];
		if (returnValue.length > 0)
		{
			// update the value, keychain item is present
			[KeyChainWrapper updateKeychainValue:inEnable?@"YES":@"NO" forIdentifier:crashKey];
		}
		else
		{
			// this email is not present, lets create a new user with option yes
			[KeyChainWrapper createKeychainValue:inEnable?@"YES":@"NO" forIdentifier:crashKey];
		}
	}
}

+(bool) getSendUsageInfoKey
{
	NSString* crashKey = @"";
	if (crashKey.length > 0)
	{
		NSString* returnValue = [KeyChainWrapper searchKeychainCopyMatching:crashKey];
		if (!(returnValue.length>0))
		{
			// this key is not present, we return true by default
			return true;
		}
		else
		{
			NSLog([returnValue boolValue] ? @"Yes" : @"No");
			return [returnValue boolValue];
		}
		return false;
	}
	// user hasn't logged in, we cannot get the email id
	return false;
}

+(bool) createKeychainValue:(NSString*) value forIdentifier:(NSString*) identifier
{
	NSMutableDictionary* dictionary = [self queryDictionary:identifier];
	NSData* valueData = [value dataUsingEncoding:NSUTF8StringEncoding];
	[dictionary setObject:valueData forKey:(id)kSecValueData];
	OSStatus status = SecItemAdd((CFDictionaryRef)dictionary, NULL);
	if (status == errSecSuccess)
	{
		return true;
	}
	return false;
}

+(bool) updateKeychainValue:(NSString*) password forIdentifier:(NSString*) identifier
{
	NSMutableDictionary* searchDictionary = [self queryDictionary:identifier];
	NSMutableDictionary* updateDictionary = [[NSMutableDictionary alloc] init];
	NSData* passwordData = [password dataUsingEncoding:NSUTF8StringEncoding];
	[updateDictionary setObject:passwordData forKey:(id)kSecValueData];

	OSStatus status = SecItemUpdate((CFDictionaryRef)searchDictionary, (CFDictionaryRef)updateDictionary);
	if (status == errSecSuccess)
	{
		return YES;
	}
	return NO;
}

+(void) deleteKeychainValue:(NSString*) identifier
{
  NSMutableDictionary *searchDictionary = [self queryDictionary:identifier];
  SecItemDelete((CFDictionaryRef)searchDictionary);
}

@end

You can use the below example to call the keychain wrapper API.

NSString* emailOfUserToStore = @"2darshan@yahoo.com+sendUsageCrashData";
	NSString* returnValue = [KeyChainWrapper searchKeychainCopyMatching:emailOfUserToStore];
	if (!(returnValue.length>0))
	{
		// this email is not present, lets create a new user with option yes
		[KeyChainWrapper createKeychainValue:@"yes" forIdentifier:emailOfUserToStore];
		NSString* returnValue = [KeyChainWrapper searchKeychainCopyMatching:emailOfUserToStore];
		if (returnValue.length > 0)
		{
			NSLog(@"data from user %@", returnValue);
		}

	}
	else
	{
		BOOL boolValue = [returnValue boolValue];
		NSLog(@"its a old user, data is present %@", returnValue);
		[KeyChainWrapper updateKeychainValue:boolValue?@"NO":@"YES" forIdentifier:emailOfUserToStore];
	}

Leave a Reply

Your email address will not be published. Required fields are marked *