in iOS

How to get persistent reference to a keychain item in iOS

Keychain is a very good idea from Apple. It lets app developers to store and retrieve sensitive data securely.

Keychain Services provides secure storage of passwords, keys, certificates, and notes for one or more users. A user can unlock a keychain with a single password, and any Keychain Services–aware application can then use that keychain to store and retrieve passwords. Keychain Services Programming Guide contains an overview of Keychain Services, discusses the functions and data structures that are most commonly used by developers, and provides examples of how to use Keychain Services in your own applications.

The following shows how keychain works:

unlocking_keychain. Photo taken from: developer.apple.com

During the process of creating VPN profiles programmatically in iOS 8, the NEVPNProtocol.passwordReference property requires a persistence reference to a keychain item with the kSecClassGenericPassword class. This post covers saving and getting persistence references to a keychain item in iOS.

Getting started with Keychain

The process of saving data in keychain is very simple once you understand it. To start working with Keychain, Security.framework needs to added to your project. Security framework has 4 major methods which enables you to access iOS keychain. These methods are:

  • SecItemAdd
  • SecItemCopyMatching
  • SecItemDelete
  • SecItemUpdate

As it can be guessed from each method’s name SecItemAdd, SecItemDelete and SecItemUpdate adds, deletes and updates data. SecItemCopyMatching method searches for a specific keychain item and copies item data to a reference variable if available. For more information about these four methods checkout Apple’s Keychain Services Reference page.

Saving data

Note: There’s actually no difference in saving normal and persistent references to the keychain!
The following adds a NSData object to the iOS keychain:

NSData *data = [Your data]; // Data to save. It can be a string too.
NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [@"[Your key name]" dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
[dict setObject:service forKey:(__bridge id)kSecAttrService];
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
[dict setObject:data forKey:(__bridge id)kSecValueData];

OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);
if(errSecSuccess != status) {
    NSLog(@"Unable add item with key =%@ error:%ld",key,status);
}

After the data is saved it can be get anytime from keychain.

Getting Data

Data can be get from keychain using the following code:

NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];
[dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];
NSData *encodedKey = [@"[Your key name]" dataUsingEncoding:NSUTF8StringEncoding];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];
[dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];
[dict setObject:service forKey:(__bridge id)kSecAttrService];
[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];
[dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];
[dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnPersistentRef]; // The most important part
    
CFTypeRef result = NULL;
OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);
    
if( status != errSecSuccess) {
    NSLog(@"Unable to fetch item for key %@ with error:%ld",key,status);
    return nil;
}
    
NSData *resultData = (__bridge NSData *)result; //Your data is ready

The most important part of the above code is in line 9 where kSecReturnPersistentRef attribute is set. This attribute tells the keychain to return a persistent reference to the keychain item.

Hope it helps 🙂

  • Hi, awesome article. Thanks.

  • Noman

    It still prompt for password.
    Error message while fetch password reference: “Unable to fetch item for key VTI with error:-25300”

    • ramezanpour

      Pay attention to the key you specify for your keychain item.

      • Timur Suleimanov

        mb more info? What do you mean by “pay attention”? what key should be like? Because i’ve faced the same problem.

        • 朋 李

          -25300 says the key you want to add had in the keychain,you must remove it from keychain.so you can add it successful

  • It’s very strange that I copied and pasted your code into my project and I failed to get the data.
    The solution is simple. Just delete the line “[dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];”.

  • Jonathan Aguele

    Hi, nice article, been vey helpful. I have one problem, im not able to add item to key chain and as a result, i cannot fetch a persistent reference to the keychain item.

    This is the code i used

    – (void)savePassword{

    NSData *data = [@”data to save” dataUsingEncoding:NSUTF8StringEncoding]; // Data to save. It can be a string too.

    NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];

    [dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

    NSData *encodedKey = [@”passwordKey” dataUsingEncoding:NSUTF8StringEncoding];

    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];

    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];

    [dict setObject:@”service name” forKey:(__bridge id)kSecAttrService];

    [dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];

    [dict setObject:data forKey:(__bridge id)kSecValueData];

    OSStatus status = SecItemAdd((__bridge CFDictionaryRef)dict, NULL);

    if(errSecSuccess != status) {

    NSLog(@”Unable add item with key =%@ error:%d”,encodedKey,(int)status);

    }

    }

    And then i use this method to return the reference

    -(NSData*)returnData:(NSString*)identifier{
    //identifier is the password key
    NSMutableDictionary * dict = [[NSMutableDictionary alloc] init];

    [dict setObject:(__bridge id)kSecClassGenericPassword forKey:(__bridge id)kSecClass];

    NSData *encodedKey = [identifier dataUsingEncoding:NSUTF8StringEncoding];

    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrGeneric];

    [dict setObject:encodedKey forKey:(__bridge id)kSecAttrAccount];

    [dict setObject:@”work” forKey:(__bridge id)kSecAttrService];

    [dict setObject:(__bridge id)kSecAttrAccessibleAlwaysThisDeviceOnly forKey:(__bridge id)kSecAttrAccessible];

    [dict setObject:(__bridge id)kSecMatchLimitOne forKey:(__bridge id)kSecMatchLimit];

    [dict setObject:(id)kCFBooleanTrue forKey:(__bridge id)kSecReturnPersistentRef]; // The most important part

    CFTypeRef result = NULL;

    OSStatus status = SecItemCopyMatching((__bridge CFDictionaryRef)dict,&result);

    if( status != errSecSuccess) {

    NSLog(@”Unable to fetch item for key %@ with error:%d”,encodedKey,(int)status);

    return nil;

    }
    NSData *resultData = (__bridge NSData *)result; //Your data is ready

    return resultData;

    }

    then i just call:
    [self savePassword];
    p.sharedSecretReference = [self returnData:@”passwordKey”];

    but this doesnt work the console says “unable to add item with key”, any help will be appreciated. Thnaks

  • 郑晓铭

    cool.Thanks.