0

I am wondering when preparing for a Keychain item, when would you convert NSString to NSData?

For instance: In the code provided by this tutorial http://hayageek.com/ios-keychain-tutorial/

It states the following:

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

However, in the book "iOS Application Security" by David Thiel used the following:

[dict setObject:@"dthiel" forKey:(__bridge id)kSecAttrAccount];

So, I am quite confused, when do I need to convert NSString to NSData and how can I tell?

Thank you.

nynohu
  • 1,628
  • 12
  • 12
Kwok Ping Lau
  • 13
  • 1
  • 1

1 Answers1

0

You MUST encode the value as NSData.

For example:

#define KeychainIdentifier @"keychain.access.identifier"

- (void)setKeyValue:(NSString *)key value:(NSString *)value {
    //The keychain identifier must be encoded as `NSData`.
    NSData *keychainItemID = [KeychainIdentifier dataUsingEncoding:NSUTF8StringEncoding];


    //Build the query. We need to QUERY the keychain and check if the item exists.
    //If it does, we will NOT be adding the item in the keychain. Note: You can "overwrite" the data if you want but for this example, I'm going to keep it simple and NOT do that.
    //For this example, the item stored is a "GenericPassword".
    //We will query for the existence of "one" item.
    //This query will only return attributes because we are not FETCHING from the keychain. Just "checking/querying".
    //Finally, the item is accessible when the device is unlocked.
    NSMutableDictionary *query = [@{
                                    (id)kSecClass             : (id)kSecClassGenericPassword,
                                    (id)kSecAttrGeneric       : keychainItemID,
                                    (id)kSecMatchLimit        : (id)kSecMatchLimitOne,
                                    (id)kSecReturnAttributes  : (id)kCFBooleanTrue,
                                    (id)kSecAttrAccessible    : (id)kSecAttrAccessibleWhenUnlocked,
                                    (id)kSecAttrAccount       : key
                                    } mutableCopy];


    //Query the keychain and get all the item's attributes.
    CFMutableDictionaryRef result = nil;
    OSStatus error = SecItemCopyMatching((__bridge CFMutableDictionaryRef)query, (CFTypeRef *)&result);

    if (error == errSecItemNotFound) {
        //Item does not exist, add it to the keychain.
        //To do that, we turn our query into an "INSERT".
        //That means we need to remove the "return" key because we are no longer fetching/querying and returning attributes. We also have to remove the match limit.
        //We also remove the match limit.
        [query removeObjectForKey:(id)kSecMatchLimit];
        [query removeObjectForKey:(id)kSecReturnAttributes];

        //Now we encode the data to be stored in the keychain and then we submit our "INSERT" to the keychain. This will add the item in the keychain.
        [query setObject:[value dataUsingEncoding:NSUTF8StringEncoding] forKey:(id)kSecValueData];

        error = SecItemAdd((__bridge CFMutableDictionaryRef)query, nil);

        if (error == noErr) {
            //Success.
        }
        else {
            //Something went wrong.
        }
    }
    else {
        //Item already exists.
    }
}
Brandon
  • 22,723
  • 11
  • 93
  • 186
  • Thank you for your sample. Just wondering, is it necessary to have **kSecAttrGeneric**? Because I recall that **kSecClassGenericPassword** only need **kSecClass**, **kSecAttrAccount** and **kSecAttrServer** for identification, is this correct? – Kwok Ping Lau Jan 17 '17 at 06:29
  • @KwokPingLau; It is not necessary. It is optional. As you can see here: http://stackoverflow.com/questions/11614047/what-makes-a-keychain-item-unique-in-ios The primary key for ``kSecClassGenericPassword`` is `kSecAttrAccount and kSecAttrService` – Brandon Jan 17 '17 at 14:34