Home

04 Nov 2015

Keychain Entropy on iOS 9

This article assumes you’re familiar with the keychain and it’s query style. If that is new to you, check out Keychain Services in C or the Keychain in Swift tutorial.

iOS 9 added the ability to add entropy while encrypting items in the keychain. This involves using the Application Password option as part of an access control list. This opens up opportunities to provide entropy in different ways. For example, you could use PBKDF2 from user’s provided passwords that let them save and load items from the keychain. You could authenticate with a server which securely returns a token for the keychain. While not very secure, you could hardcode an obfuscated password in the binary to prevent offline keychain dumps.

Access Control

You can provide an access control object and context to the keychain query dictionary. The keys are kSecUseAuthenticationContext and kSecAttrAccessControl.

kSecUseAuthenticationContext lets you pass in an authentication context. You’ll create an LAContext that allows you to set an app-specific password:

LAContext *localAuthenticationContext = [[LAContext alloc] init];
NSData *theApplicationPassword = [@"1234" dataUsingEncoding:NSUTF8StringEncoding];
            [localAuthenticationContext setCredential:theApplicationPassword type:LACredentialTypeApplicationPassword];

Next, use a SecAccessControlRef object that will contain access control conditions. Use the SecAccessControlCreateWithFlags function (available in iOS 8 +) to create this object:

SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlock, kSecAccessControlApplicationPassword, &error);

The parameters you passed are a default allocator, kSecAttrAccessibleAfterFirstUnlock as the kSecAttrAccessible protection class and kSecAccessControlApplicationPassword as the bitmask option - this denotes that an application provided password for the keychain item. Then you passed in an error object.

Adding a Keychain Item

Now that you have a context and an access control object, add them to a keychain query dictionary:

NSDictionary *saveDictionary = @{
                            (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                            (__bridge id)kSecAttrService: @"testService2",
                            (__bridge id)kSecAttrAccount: @"testAccount2",
                            (__bridge id)kSecValueData: [@"qqqq" dataUsingEncoding:NSUTF8StringEncoding],
                            (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
                            (__bridge id)kSecUseAuthenticationContext:localAuthenticationContext
                            };

Putting It All Together

Here’s an example that stores and retrieves a password from the keychain using the new application password option. If the user is not on iOS 9 then there should be a fallback at the last else statement to save and load without using the new feature.

Remember to add the LocalAuthentication framework to your project and import it in your code:

#import <LocalAuthentication/LocalAuthentication.h>
#import <Security/SecAccessControl.h>

Here’s the full example:

- (void)applicationPasswordExample
{
    if ([LAContext class]) // iOS 8 or later
    {
        LAContext *localAuthenticationContext = [[LAContext alloc] init];
        if ([localAuthenticationContext respondsToSelector:@selector(setCredential:type:)]) //iOS 9 + , could use [[NSProcessInfo processInfo] operatingSystemVersion] instead
        {
            OSStatus status = noErr;
            CFErrorRef error;
            SecAccessControlRef accessControl = SecAccessControlCreateWithFlags(kCFAllocatorDefault, kSecAttrAccessibleAfterFirstUnlock, kSecAccessControlApplicationPassword, &error);
            if (accessControl)
            {
                NSData *theApplicationPassword = [@"1234" dataUsingEncoding:NSUTF8StringEncoding];
                [localAuthenticationContext setCredential:theApplicationPassword type:LACredentialTypeApplicationPassword];
                
                NSDictionary *saveDictionary = @{
                                                 (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                                                 (__bridge id)kSecAttrService: @"testService",
                                                 (__bridge id)kSecAttrAccount: @"testAccount",
                                                 (__bridge id)kSecValueData: [@"qqqq" dataUsingEncoding:NSUTF8StringEncoding],
                                                 (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
                                                 (__bridge id)kSecUseAuthenticationContext:localAuthenticationContext
                                                 };
                
                status = SecItemAdd((__bridge CFDictionaryRef)saveDictionary, nil);
                if (status == noErr)
                {
                    NSLog(@"Item stored to keychain");
                }
                
                NSDictionary *loadDictionary = @{
                                                 (__bridge id)kSecClass: (__bridge id)kSecClassGenericPassword,
                                                 (__bridge id)kSecAttrService: @"testService",
                                                 (__bridge id)kSecAttrAccount: @"testAccount",
                                                 (__bridge id)kSecReturnData: @(YES),
                                                 (__bridge id)kSecMatchLimit: (NSString *)kSecMatchLimitOne,
                                                 (__bridge id)kSecAttrAccessControl: (__bridge id)accessControl,
                                                 (__bridge id)kSecUseAuthenticationContext:localAuthenticationContext
                                                 };
                
                CFDataRef passwordData = NULL;
                status = SecItemCopyMatching((CFDictionaryRef)loadDictionary, (CFTypeRef *)&passwordData);
                
                if ( (status == noErr) && (0 < CFDataGetLength(passwordData)) )
                {
                    CFStringRef string = CFStringCreateWithBytes(kCFAllocatorDefault, CFDataGetBytePtr(passwordData), CFDataGetLength(passwordData), kCFStringEncodingUTF8, FALSE);
                    if (string)
                    {
                        CFShow(string);
                        CFRelease(string);
                    }
                }
                
                if (passwordData != NULL)
                {
                    CFRelease(passwordData);
                }
                
                CFRelease(accessControl);
            }
            else
            {
                NSLog(@"Error creating access control object");
            }
        }
        else
        {
            //Perform save without application password (without kSecAttrAccessControl and kSecUseAuthenticationContext) fields
        }
    }
    else
    {
        //Perform save without application password (without kSecAttrAccessControl and kSecUseAuthenticationContext) fields
    }
}

To learn move about keychain capabilities, check out the Keychain in Swift and Keys and Credentials on Android tutorial.