Anybody who’s tried to use Apple’s Keychain Scripting has probably learned that its performance and functionality are both fatally flawed.
The quintessential flaw in Keychain Scripting’s performance can be observed by simply asking it to find a key by some attribute test. If it works, then it will take a long, long time. All of its “whose clause” functionality is extremely slow, but this simple case forces it to go through each item in the keychain, failing to find a key:
tell application "Keychain Scripting"
set myKey to first key of current keychain ¬
whose name is "zzzzz"
end tell
Just how bad is it? I knew I had seen times of at least a minute, but I decided to test with Shark just to be sure. I let it rip and to my surprise AppleScript eventually gave up with a “event timed out” error. I stopped shark and observed that Keychain Scripting had used up 2.3 minutes of CPU time (!).
I was anxious to learn how bad it could get so I put a “with timeout of 200000 seconds” around the AppleScript block and ran it again. The pipes must have been warm this time, because Keychain Access came back in a “zippy” 1.3 minutes, finally having failed to locate the item as expected.
Obviously, Keychain Scripting is freaking useless.
I decided to take a stab at writing my own, because I’ve been waiting too long for this to get fixed and I just don’t have any faith left. I figured it was a good opportunity to learn about the Security Framework, with which I have had no prior experience. I used the Keychain Access dictionary as scaffolding for everything I needed to implement, and slowly filled in the gaps, testing each attribute and command before and after implementation, learning the API as I went along. I abided by my policy of ignoring performance considerations until I got something working. Before long, I had completed set of simple Cocoa wrappers around the Security framework, which simply map through to live-fetches of the data from the underlying database.
The results? The script that takes (at best) 1.3 minutes to run in Keychain Access takes 300ms in “Usable Keychain Scripting.” Holy cow manure! That’s a 260x performance improvement, and I haven’t optimized anything. This fruit is so low-hanging it’s practically buried.
OK, you might be thinking this is just an arbitrary example of a bad edge case. How does Keychain Scripting really perform? You know … when you’re looking for something that actually exists. Here’s another arbitrary example, this time collecting the keychain items that have my name in their name:
tell application "Keychain Scripting"
set myKey to first key of current keychain ¬
whose name contains "daniel"
end tell
This time it’s 360ms in my Usable version, and uh oh, bad example, simply doesn’t work in Keychain Scripting.
Keychain Scripting got an error: Can’t get key 1 of current keychain whose name contains “daniel”.
Unusable. The same experience applies to most other reasonable attempts to ask it for information.
Usable Keychain Scripting is at least 260x better than Keychain Scripting in the speed of critical lookups, but there are other improvements, too. I fixed up the dictionary to not suck as much, and my code goes directly to the most modern security API, while the Apple version still goes through an outdated “KC” API. Who knows, one of these days maybe I’ll even optimize it. NOTE: the terminology is slightly different from Apple’s Keychain Scripting, but I think my terminology is clearer.
So why would you even want to script Keychain? Well, for one thing if you script Keychain instead of hardcoding passwords in your scripts, things are a lot more secure. Also, there are certain keys for which I find myself constantly going into Keychain Access and looking up the values for. Either Safari forgets how to auto-enter it, or NetNewsWire loses its wits about something. For instance, every once in a while my Daring Fireball subscriber feeds lose knowledge of their password. With the scripting mechanism working as it should, I can keep a short script attached to NNW for refetching it:
tell application "Usable Keychain Scripting" to tell current keychain
set myPass to password of first internet password ¬
whose protocol is HTTP and name is ¬
"daringfireball.net (<my email address>)"
end tell
set the clipboard to myPass
Now the password is in my clipboard and I just paste it into the field as NNW requests.
Note: I have a mechanism in place for setting values of keychain items, but I am wary of making it public yet because of the risk of data loss if I did something wrong. For now just enjoy the speedy access to your existing keychain data.