The keychain is a great feature in OS X, allowing you to store passwords securely. In this tutorial I’ll outline how to store passwords in the keychain so your apps don’t have to worry about securely storing the passwords themselves.
Before I start, you can skip straight to the code on github here.
A few pointers
The MonoMac binding of the OS X keychain is somewhat basic at this moment in time, however it should be enough for basic password storage. OS X supports multiple keychains, as well as multiple types of passwords. At the moment the MonoMac bindings limit you to the system (well, user’s login) keychain, and only the ‘Internet Password’ keychain type.
Additionally you’ll want to sign your app to ensure your passwords are uniquely identified, without signing I’ve noticed that other MonoMac apps authored by myself seem to conflict with each other.
Relevant Classes
We store keychain records in a class of SecRecord, while the SecKeyChain static class handles the interaction with the keychain itself. When searching for records, you’ll fill in a SecRecord with the fields you need to match, then get SecKeyChain to return a matching record. You need to be careful not to try and insert duplicate records, if you do it’ll be difficult to pull out the record you want – it’s undefined which one you’ll get. This can lead to odd bugs where one second you get the correct password, and the next you get the wrong one.
The password for Internet Passwords is stored in the ValueData property, this is of NSData type rather than the string you’re probably expecting.
Selecting
To fetch a record, you need to provide a record with the fields you want to filter by set. At a minimum you’ll want to specify the service and account (username):
var searchRecord = new SecRecord(SecKind.InternetPassword) { Service = ServiceName, Account = username }; SecStatusCode code; var data = SecKeyChain.QueryAsRecord(searchRecord, out code); if (code == SecStatusCode.Success) return data; else return null;
This code will return a matching record, or NULL if none are found. If more than one record matches your search criteria, you’ll get a random record. For this reason make sure you never accidentally insert duplicate records, or you’ll get unpredictable behaviour in your app. Remember, the search record you specify will be used as the search criteria – the filled in fields will be used for the search.
To get the password from the record use the following code:
password = NSString.FromData(record.ValueData, NSStringEncoding.UTF8);
Remember, the ValueData property is NSData, not a string. If MonoMac allows you to use a record type of ‘Generic Password’, the password will be stored in the Generic property rather than ValueData. Generic is also an NSData type.
Inserting
Inserting a password involves filling out a SecRecord, and then calling Add() on SecKeyChain:
var record = new SecRecord(SecKind.InternetPassword) { Service = ServiceName, Label = ServiceName, Account = username, ValueData = NSData.FromString(password) }; SecKeyChain.Add(record);
However, if you remember from earlier – you never want to insert a record without checking for an existing one because you’ll risk adding a duplicate record. Once inserted, you’ll see your password in the keychain utlity:
For code on how to perform a check and then update / insert as necessary, check the sample code.
Updating
Updating involves sending in the existing record (or at least one that’ll uniquely identify the record you’re after), and a new record to replace it:
record.ValueData = NSData.FromString(password); SecKeyChain.Update(searchRecord, record);
The first parameter is handled in exactly the same was as when querying for an existing record, the second parameter is your newly updated record.
Deleting
Deleting is remarkably similar to updating, except you only send in your search record:
var searchRecord = new SecRecord(SecKind.InternetPassword) { Service = ServiceName, Account = username }; SecKeyChain.Remove(searchRecord);
Finishing Up
This guide has demonstrated how to interact with the OS X keychain using MonoMac. Make sure to check out the sample project for a simple utility class that wraps up all of the necessary functionality with a friendly interface.