iOS

Keychain

Syntax#

  • kSecClassGenericPassword // A value key representing a non-internet password
  • kSecClassInternetPassword // A value key representing an internet password
  • kSecClassCertificate // A value key representing a certificate
  • kSecClassCertificate // A value key representing a key
  • kSecClassIdentity // A value key representing an identity, which is a certificate plus a key

Remarks#

iOS stores private information such as passwords, encryption keys, certificates, and identities in a secure storage area called the Keychain. This storage area is managed completely by a co-processor called the Secure Enclave, which is embedded inside the application processor. Because the Keychain is sandboxed on iOS, keychain items may only be retrieved by the application that put them there in the first place.

In some cases you must turn on Keychain Sharing in Xcode capabilities in order to avoid errors.

In order to interact with the keychain, we use a c framework called Keychain Services. For more information, see Apple’s Keychain Services Programming Guide.

Because Keychain Services is below the Foundation level, it is restricted to using CoreFoundation types. As a result, most objects are internally represented as CFDictionarys holding CFStrings as their keys and a variety of CoreFoundation types as their values.

While Keychain Services is included as a part of the Security framework, importing Foundation is usually a good option since it includes some helper functions in the backend.

Additionally, if you don’t want to deal with Keychain Services directly, Apple provides the Generic Keychain Swift sample project that provides Swift types that use Keychain Services behind the scenes.

Adding a Password to the Keychain

Every Keychain Item is most often represented as a CFDictionary. You can, however, simply use NSDictionary in Objective-C and take advantage of bridging, or in Swift you may use Dictionary and explicitly cast to CFDictionary.

You could construct a password with the following dictionary:

Swift

var dict = [String : AnyObject]()

First, you need a key/value pair that lets the Keychain know this is a password. Note that because our dict key is a String we must cast any CFString to a String explicitly in Swift 3. CFString may not be used as the key to a Swift Dictionary because it is not Hashable.

Swift

dict[kSecClass as String] = kSecClassGenericPassword

Next, our password may have a series of attributes to describe it and help us find it later. Here’s a list of attributes for generic passwords.

Swift

// The password will only be accessible when the device is unlocked
dict[kSecAttrAccessible as String] = kSecAttrAccessibleWhenUnlocked
// Label may help you find it later
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

Finally, we need our actual private data. Be sure not to keep this around in memory for too long. This must be CFData.

Swift

dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData

Finally, the Keychain Services add function wants to know how it should return the newly constructed keychain item. Since you shouldn’t be holding on to the data very long in memory, here’s how you could only return the attributes:

Swift

dict[kSecReturnAttributes as String] = kCFBooleanTrue

Now we have constructed our item. Let’s add it:

Swift

var result: AnyObject?
let status = withUnsafeMutablePointer(to: &result) {
    SecItemAdd(dict as CFDictionary, UnsafeMutablePointer($0))
}
let newAttributes = result as! Dictionary<String, AnyObject>

This places the new attributes dict inside result. SecItemAdd takes in the dictionary we constructed, as well as a pointer to where we would like our result. The function then returns an OSStatus indicating success or an error code. Result codes are described here.

Finding a Password in the Keychain

To construct a query, we need to represent it as a CFDictionary. You may also use NSDictionary in Objective-C or Dictionary in Swift and cast to CFDictionary.

We need a class key:

Swift

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword

Next, we can specify attributes to narrow down our search:

Swift

// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString
// Service name
dict[kSecAttrService as String] = "MyService" as CFString

We can also specify special search modifier keys described here.

Finally, we need to say how we’d like our data returned. Below, we’ll request that just the private password itself be returned as a CFData object:

Swift

dict[kSecReturnData as String] = kCFBooleanTrue

Now, let’s search:

Swift

var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
    SecItemCopyMatching(dict as CFDictionary, UnsafeMutablePointer($0))
}
// Don't keep this in memory for long!!
let password = String(data: queryResult as! Data, encoding: .utf8)!

Here, SecItemCopyMatching takes in a query dictionary and a pointer to where you’d like the result to go. It returns an OSStatus with a result codes. Here are the possibilities.

Updating a Password in the Keychain

As usual, we first need a CFDictionary to represent the item we want to update. This must contain all of the old values for the item, including the old private data. Then it takes a CFDictionary of any attributes or the data itself that you would like to change.

So first, let’s construct a class key and a list of attributes. These attributes can narrow our search but you must include any attributes and there old values if you will be changing them.

Swift

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString

Now we must add the old data:

Swift

dict[kSecValueData as String] = "my_password!!".data(using: .utf8) as! CFData

Now let’s create the same attributes but a different password:

Swift

var newDict = [String : AnyObject]()
newDict[kSecClass as String] = kSecClassGenericPassword
// Label
newDict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
newDict[kSecAttrAccount as String] = "My Name" as CFString
// New password
newDict[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData

Now, we just pass it to Keychain Services:

Swift

let status = SecItemUpdate(dict as CFDictionary, newDict as CFDictionary)

SecItemUpdate returns a status code. Results are described here.

Removing a Password from the Keychain

We need only one thing in order to delete an item from the Keychain: a CFDictionary with attributes describing the items to be deleted. Any items that match the query dictionary will be deleted permanently, so if you are only intending to delete a single item be sure to be specific with your query. As always, we can use an NSDictionary in Objective-C or in Swift we can use a Dictionary and then cast to CFDictionary.

A query dictionary, in this context exclusively includes a class key to describe what the item is and attributes to describe information about the item. Inclusion of search restrictions such as kSecMatchCaseInsensitive is not allowed.

Swift

var dict = [String : AnyObject]()
dict[kSecClass as String] = kSecClassGenericPassword
// Label
dict[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
// Username
dict[kSecAttrAccount as String] = "My Name" as CFString

And now we can simply remove it:

Swift

let status = SecItemDelete(dict as CFDictionary)

SecItemDelete returns an OSStatus. Result codes are described here.

Keychain Add, Update, Remove and Find operations using one file.

Keychain.h

#import <Foundation/Foundation.h>
typedef void (^KeychainOperationBlock)(BOOL successfulOperation, NSData *data, OSStatus status);

@interface Keychain : NSObject

-(id) initWithService:(NSString *) service_ withGroup:(NSString*)group_;

-(void)insertKey:(NSString *)key withData:(NSData *)data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)updateKey:(NSString*)key withData:(NSData*) data withCompletion:(KeychainOperationBlock)completionBlock;
-(void)removeDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;
-(void)findDataForKey:(NSString*)key withCompletionBlock:(KeychainOperationBlock)completionBlock;

@end

Keychain.m

#import "Keychain.h"
#import <Security/Security.h>

@implementation Keychain

{
    NSString * keychainService;
    NSString * keychainGroup;
}

-(id) initWithService:(NSString *)service withGroup:(NSString*)group
{
    self =[super init];
    if(self) {
        keychainService = [NSString stringWithString:service];
        if(group) {
            keychainGroup = [NSString stringWithString:group];
        }
    }
    
    return  self;
}

-(void)insertKey:(NSString *)key
        withData:(NSData *)data
  withCompletion:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary * dict =[self prepareDict:key];
    [dict setObject:data forKey:(__bridge id)kSecValueData];
    [dict setObject:keychainService forKey:(id)kSecAttrService];
    
    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
    if(errSecSuccess != status) {
        DLog(@"Unable add item with key =%@ error:%d",key,(int)status);
        if (completionBlock) {
            completionBlock(errSecSuccess == status, nil, status);
        }
    }
    if (status == errSecDuplicateItem) {
        [self updateKey:key withData:data withCompletion:^(BOOL successfulOperation, NSData *updateData, OSStatus updateStatus) {
            if (completionBlock) {
                completionBlock(successfulOperation, updateData, updateStatus);
            }
            DLog(@"Found duplication item -- updating key with data");
        }];
    }
}

-(void)findDataForKey:(NSString *)key
  withCompletionBlock:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary *dict = [self prepareDict:key];
    [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
    [dict setObject:keychainService forKey:(id)kSecAttrService];
    [dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnData];
    CFTypeRef result = NULL;
    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);
    
    if( status != errSecSuccess) {
        DLog(@"Unable to fetch item for key %@ with error:%d",key,(int)status);
        if (completionBlock) {
            completionBlock(errSecSuccess == status, nil, status);
        }
    } else {
        if (completionBlock) {
            completionBlock(errSecSuccess == status, (__bridge NSData *)result, status);
        }
    }
}

-(void)updateKey:(NSString *)key
        withData:(NSData *)data
  withCompletion:(KeychainOperationBlock)completionBlock
{
    NSMutableDictionary * dictKey =[self prepareDict:key];
    
    NSMutableDictionary * dictUpdate =[[NSMutableDictionary alloc] init];
    [dictUpdate setObject:data forKey:(__bridge id)kSecValueData];
    [dictUpdate setObject:keychainService forKey:(id)kSecAttrService];
    OSStatus status = SecItemUpdate((__bridge CFDictionaryRef)dictKey, (__bridge CFDictionaryRef)dictUpdate);
    if( status != errSecSuccess) {
        DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
    }
    if (completionBlock) {
        completionBlock(errSecSuccess == status, nil, status);
    }
}

-(void)removeDataForKey:(NSString *)key
    withCompletionBlock:(KeychainOperationBlock)completionBlock {
    NSMutableDictionary *dict = [self prepareDict:key];
    OSStatus status = SecItemDelete((__bridge CFDictionaryRef)dict);
    if( status != errSecSuccess) {
        DLog(@"Unable to remove item for key %@ with error:%d",key,(int)status);
    }
    if (completionBlock) {
        completionBlock(errSecSuccess == status, nil, status);
    }
}

#pragma mark Internal methods

-(NSMutableDictionary*) prepareDict:(NSString *) key {
    
    NSMutableDictionary *dict = [[NSMutableDictionary alloc] init];
    [dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
    
    NSData *encodedKey = [key dataUsingEncoding:NSUTF8StringEncoding];
    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
    [dict setObject:keychainService forKey:(__bridge id)kSecAttrService];
    [dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
    
    //This is for sharing data across apps
    if(keychainGroup != nil) {
        [dict setObject:keychainGroup forKey:(__bridge id)kSecAttrAccessGroup];
    }
    
    return  dict;
}

@end

Keychain Access Control (TouchID with password fallback)

Keychain allows to save items with special SecAccessControl attribute which will allow to get item from Keychain only after user will be authenticated with Touch ID (or passcode if such fallback is allowed). App is only notified whether the authentication was successful or not, whole UI is managed by iOS.

First, SecAccessControl object should be created:

Swift

let error: Unmanaged<CFError>?

guard let accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly, .userPresence, &error) else {
    fatalError("Something went wrong")
}

Next, add it to the dictionary with kSecAttrAccessControl key (which is mutually exclusive with kSecAttrAccessible key you’ve been using in other examples):

Swift

var dictionary = [String : Any]()

dictionary[kSecClass as String] = kSecClassGenericPassword
dictionary[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
dictionary[kSecAttrAccount as String] = "My Name" as CFString
dictionary[kSecValueData as String] = "new_password!!".data(using: .utf8) as! CFData
dictionary[kSecAttrAccessControl as String] = accessControl

And save it as you’ve done before:

Swift

let lastResultCode = SecItemAdd(query as CFDictionary, nil)

To access stored data, just query Keychain for a key. Keychain Services will present authentication dialog to the user and return data or nil depending on whether suitable fingerprint was provided or passcode matched.

Optionally, prompt string can be specified:

Swift

var query = [String: Any]()

query[kSecClass as String] = kSecClassGenericPassword
query[kSecReturnData as String] = kCFBooleanTrue
query[kSecAttrAccount as String] = "My Name" as CFString
query[kSecAttrLabel as String] = "com.me.myapp.myaccountpassword" as CFString
query[kSecUseOperationPrompt as String] = "Please put your fingers on that button" as CFString

var queryResult: AnyObject?
let status = withUnsafeMutablePointer(to: &queryResult) {
    SecItemCopyMatching(query as CFDictionary, UnsafeMutablePointer($0))
}

Pay attention that status will be err if user declined, canceled or failed authorization.

Swift

if status == noErr {
    let password = String(data: queryResult as! Data, encoding: .utf8)!
    print("Password: \(password)")
} else {
    print("Authorization not passed")
}

This modified text is an extract of the original Stack Overflow Documentation created by the contributors and released under CC BY-SA 3.0 This website is not affiliated with Stack Overflow