Monotouch by example: Working with the keychain

One of the things you will be doing a lot in your apps is storing and retrieving passwords. Most apps today use some form of cloud service, that requires usernames and passwords at one moment or another. Of course you can store usernames and passwords inside the storage folder of the app, but that’s easy to hack by people that really want to break into the app.

The best way to store passwords on your iPhone or iPad is to use the keychain. In this post I will show you some of the basics needed to get the keychain to work in your app and what you need to look out for when working with this API.

Basic principles of the keychain

The idea behind the keychain in iOS is to provide developers to securely store certificates, passwords and other data that needs to be encrypted before storage. The same API is used on Mac OS X, with a difference. You can manage your certificates, passwords, etc on there. On iOS you cannot manage the stored data, unless you are a developer and manage the data through the provided API.

So with these basic principles out of the way, let’s get started on using the API to store simple usernames and passwords for your app.

Retrieving entries from the keychain

To retrieve a stored password from the keychain, you need to query it. There’s basically two ways to do this. One of which I will demonstrate in this article.

public class SecureAccountStorage
{
    private const string ServiceName = "NerdCooking";

    public string GetAccountPassword(string username)
    {
        var existingRecord = new SecRecord(SecKind.GenericPassword)
        {
            Account = username,
            Label = username,
            Service = ServiceName
        };

        // Locate the entry in the keychain, using the label, service and account information.
        // The result code will tell us the outcome of the operation.
        SecStatusCode resultCode;
        var data = SecKeyChain.QueryAsRecord(existingRecord, out resultCode);

        if (resultCode != SecStatusCode.Success)
        {
            throw new Exception("No password found for provided account");
        }

        return NSString.FromData(existingRecord.ValueData, NSStringEncoding.UTF8);
    }
}

The QueryAsRecord method is an easy way to locate a single user account in the keychain. All you need to do is to create a new SecRecord instance and provide the service, account and label for it. These values are used by the keychain API to locate the account information in the keychain.

The result code used in the sample tells you whether or not the entry was succesfully located. Normally you only need to care about either Success or ItemNotFound in the resultcode. Anything else means that there’s something horribly wrong.

Adding new entries into the keychain

To store a new password securely, you need to create a new record to store in the keychain. This is done by creating a new record, which you fill with the name of the service, the username and the data you want encrypted.

public class SecureAccountStorage
{
    private const string ServiceName = "NerdCooking";

    public void StoreUserAccount(string username, string password)
    {
        SecKeyChain.Add(new SecRecord(SecKind.GenericPassword)
        {
            Service = ServiceName,
            Account = username,
            ValueData = NSData.FromString(password, NSStringEncoding.UTF8)
        });
    }
}

Important to know is that the username or the service name is not encrypted by the keychain API. The data in the ValueData property is stored encrypted on disk.

The SecKeyChain.Add method returns a result code that you can use to discover the outcome of the add operation. Normally, when you add a new item you will get a Success result code back from the keychain API.

Replacing an entry in the keychain

However, if you’re trying to add an entry to the keychain that already exists, you will get the resultcode DuplicateItem back from the API. The only way to replace an item in the keychain is to remove the old item. The following example code demonstrates this:

public class SecureAccountStorage
{
    private const string ServiceName = "NerdCooking";

    public void StoreUserAccount(string username, string password)
    {
        var existingRecord = new SecRecord(SecKind.GenericPassword)
        {
            Account = username,
            Label = username,
            Service = ServiceName
        };

        // Locate the entry in the keychain, using the label, service and account information.
        // The result code will tell us the outcome of the operation.
        SecStatusCode resultCode;
        var data = SecKeyChain.QueryAsRecord(existingRecord, out resultCode);

        if (resultCode == SecStatusCode.Success)
        {
            // Remove the existing record from the keychain if it was succesfully found.
            resultCode = SecKeyChain.Remove(existingRecord);

            if (resultCode == SecStatusCode.Success)
            {
                // Store the new value in the keychain after the old value was removed.
                SecKeyChain.Add(new SecRecord(SecKind.GenericPassword)
                {
                    Label = username,
                    Account = username,
                    Service = ServiceName,
                    ValueData = NSData.FromString(password, NSStringEncoding.UTF8)
                });
            }
        }
    }
}

Querying the keychain is done using the method I showed earlier in the post. If this is succesfull, the existing entry is removed from the keychain. After that a new entry is inserted into the keychain, to store the new value.

Conclusion

Given that the API is so easy to use, I suggest you start storing those passwords securely on the device of the user.

Happy coding!