Scripting Keychain Access in Lion

Apple’s “Keychain Scripting” application disappeared in the transition form Snow Leopard to Lion.
That left me without a viable method of fetching and setting passwords in my scripts.

Turns out Daniel Jalkut, of Red Sweater software has updated his keychain scripting App for Lion:
Usable Keychain Scripting For Lion
It’s pretty nice stuff!

However, the shell-accessible ‘security’ command offers keychain access without relying on the installation of 3rd party software.
Security just needs a little Applescript interface, and my password requiring scripts will be securely back in action.

After playing around in Terminal and Keychain Access for a while, it became clear that Keychain Access and the security command use different vocabularies.
Specifically:
What Keychain Access calls a ‘Name’, the security utility calls a ‘label’ (-l is match label string)
What Keychain Access calls an ‘Account’, the security utility calls a ‘name’ (-a is “match account name string”)
What Keychain Access calls ‘Where’, the security utility calls a ‘service name’ (-s is “match service name string”)

Once that is sorted out, implementation of utility shell scripts is reasonably straightforward.
Here’s a heavily commented Demo which can add a password item to the login keychain, find an item specified by its label, read its password etc., and delete an item specified by its label.
Additional functions list keychains, and allow for locking and unlocking of the default keychain.

The Demo is meant to be run repeatedly, while watching the contents of the login keychain in “Keychain Access”.
The shell scripting functions may easily be pulled out of the script, modified and used as needed.
Consider them a starting point, not a finished product.

Included Functions:
AddPwrdItemToKeychain
GetPassword
GetAcctnameServicenameAndPwrdFromPwrdItem
DeletePwrdItemFromKeychain
LockDefaultKeychain
UnlockDefaultKeychain
ListKeychains

-- ShellScripting Keychains
-- BP Aug, 2011

-- Permits AppleScript access to keychain items through BSD's 'security' command.
-- Run this script to demonstrate its main functions.

-- If you'd rather use a separate App for scripting the keychain, try
-- http://www.red-sweater.com/blog/2035/usable-keychain-scripting-for-lion

-----------------------------------------------------------------------------------------------------
(*
A little terminological clarification:
What Keychain Access calls a 'Name', the security utility calls a 'label' (-l is match label string)
What Keychain Access calls an 'Account', the security utility calls a 'name' (-a is "match account name string")
What Keychain Access calls 'Where', the security utility calls a 'service name' (-s is "match service name string")
*)
-----------------------------------------------------------------------------------------------------
set t to "" -- all purpose variable holds various types of data

tell application "Keychain Access" to activate -- Bring this up so as to see what's happening in the keychain.
tell application "AppleScript Editor" to activate

set label to "My New Keyitem" --e		-- if omitted, service name is used as default label
set KeyKind to "application password" --e unless new type is "Internet password", then --n
-- set KeyKind to "Internet password"  --e  -- here for testing internet password items
set AcctName to "Vladimir's Secret Cache" -- n
set ServiceName to "Boise Oblast dot com" --n
set Acctpassword to "tootyfruity232" --e
set AcctComments to "Chatty stuff of no import, except to the chatterer. Or someone could fill this space with a pointer to another keychain entry." --e
(*
Trying to add an item to the keychain twice usually results in an error (indicated by --e above).
Changing AcctName,  ServiceName or KeyKind to "Internet password" produces a 2nd keychain item  (indicated by --n above).. 
The security add-XX-password command's -U (for update) option alters this behavior.
*)


set t to AddPwrdItemToKeychain(label, KeyKind, AcctName, ServiceName, Acctpassword, AcctComments) -- Add a new password item to the default keychain 

-- set t to GetPassword(label, KeyKind)  -- ********** If you just want a password, this line will do nicely **********

set t to GetAcctnameServicenameAndPwrdFromPwrdItem(label, KeyKind) -- Read and display data from that new password item

-- Show the results in a Dialog, and let user DELETE the keychain entry if desired.
set t2 to "Label: \t\t\t" & label & "\n" & "Account name: \t" & text item 1 of t & "\n" & "Password: \t\t" & text item 2 of t & "\n" & "Service name: \t" & text item 3 of t & "\n"
set dr to display dialog t2 buttons {"Delete Item", "OK"} default button 2 with title "Recovered Keychain Item Data"
if button returned of dr is equal to "Delete Item" then
	set t to DeletePwrdItemFromKeychain(label, KeyKind) -- Delete the newly added password item from the keychain.
end if

return t -- look to the result pane

-----
-----
on AddPwrdItemToKeychain(label, KeyKind, AcctName, ServiceName, Acctpassword, AcctComments)
	-- Make UNIX happy by quoting everything:
	set Qlabel to quoted form of label
	set QKeyKind to quoted form of KeyKind
	set QAcctName to quoted form of AcctName
	set QServiceName to quoted form of ServiceName -- or server if it's an internet password being created
	set QAcctpassword to quoted form of Acctpassword
	set QAcctComments to quoted form of AcctComments
	
	set retval to true
	
	-- Unless specified, new items are added to the default, usually login, keychain.
	-- add-generic-password [-h] [-a account] [-s service] [-w password] [options...] [keychain]
	-- add-internet-password [-h] [-a account] [-s server] [-w password] [options...] [keychain]
	try
		if KeyKind is equal to "Internet password" then
			set t to do shell script "security add-internet-password -a " & QAcctName & " -s " & QServiceName & " -w " & QAcctpassword & " -l " & Qlabel & " -j " & QAcctComments
		else
			set t to do shell script "security add-generic-password -a " & QAcctName & " -s " & QServiceName & " -w " & QAcctpassword & " -l " & Qlabel & " -j " & QAcctComments
		end if
	on error n --number n
		display dialog n buttons {"OK"} default button 1 with title "Key Creation Error" with icon caution -- error number 45 is keychain entry already exists.
		set retval to false
	end try
	return retval
end AddPwrdItemToKeychain

-----
-----
on GetAcctnameServicenameAndPwrdFromPwrdItem(label, KeyKind)
	set retarray to {"", "", ""}
	set Qlabel to quoted form of label
	set oldelim to text item delimiters
	
	try
		if KeyKind is equal to "Internet password" then
			set t to do shell script "security 2>&1 find-internet-password -gl " & Qlabel
		else
			set t to do shell script "security 2>&1 find-generic-password -gl " & Qlabel
		end if
		(* 
		 That odd 2>&1 redirects password output from stderr to stdout so we can get to it along with everything else.
		 See Allan Odgaard's posting here:
		 http://blog.macromates.com/2006/keychain-access-from-shell/
		 *)
		
		--display dialog t			-- Display raw data
		(* 
			Output is messy, and needs to be parsed.
			grep and friends could work here.
			See http://www.maclovin.de/2010/02/access-os-x-keychain-from-terminal/
			I'd rather use Applescript's text item delimiters here:
		*)
		set text item delimiters to "acct" -- Get Account name
		set tlst to every text item of t
		set acct to item 2 of tlst
		set text item delimiters to "\""
		set tlst to every text item of acct
		set acct to item 3 of tlst
		
		if KeyKind is equal to "Internet password" then -- Get Service name or Server
			set text item delimiters to "srvr" -- server
		else
			set text item delimiters to "svce" -- service
		end if
		set tlst to every text item of t
		set svcnam to item 2 of tlst
		set text item delimiters to "\""
		set tlst to every text item of svcnam
		set svcnam to item 3 of tlst
		
		set text item delimiters to "\"" -- Get Password
		set tlst to every text item of t
		set pw to item 2 of tlst
		--display dialog pw
		
		set retarray to {acct, pw, svcnam}
	on error
		display dialog "Sorry, can't find your keychain item." buttons "OK" default button 1 with icon caution
		set retarray to {"", "", ""}
	end try
	
	set text item delimiters to oldelim
	return retarray
end GetAcctnameServicenameAndPwrdFromPwrdItem
-----
-----
on DeletePwrdItemFromKeychain(label, KeyKind)
	set Qlabel to quoted form of label
	set retval to true
	
	try
		if KeyKind is equal to "Internet password" then
			set t to do shell script "security delete-internet-password -l " & Qlabel
		else
			set t to do shell script "security delete-generic-password -l " & Qlabel
		end if
	on error
		set retval to false
	end try
	
	return retval
end DeletePwrdItemFromKeychain
-----
-----
on LockDefaultKeychain()
	do shell script "security lock-keychain"
	
	-- This'll work too:
	-- do shell script "security lock-keychain login.keychain"	
end LockDefaultKeychain
-----
-----
on UnlockDefaultKeychain()
	-- KeychainAccess'll ask for password as needed, or I could hardcode it here.
	do shell script "security unlock-keychain"
end UnlockDefaultKeychain
-----
-----
on listKeychains()
	set t to do shell script "security list-keychains"
	display dialog t as text buttons "OK" default button 1 with title "Keychains List" -- Ugly but this is just a utility function
end listKeychains
-----
-----
on GetPassword(label, KeyKind)
	set Qlabel to quoted form of label
	(*
	I search keychains based on the desired item's label (ie what Keychain Access calls a name) here.
	An item's label appears to be its most unique identifier in a keychain. This is important because the find-XX-password
	command returns only the first matching instance.
	I could as easily search by other attributes, or combinations thereof: 

            -a account      Match account string
            -c creator      Match creator (four-character code)
            -C type         Match type (four-character code)
            -D kind         Match kind string
            -G value        Match value string (generic attribute)
            -j comment      Match comment string
            -l label        Match label string
            -s service      Match service string
            -g              Display the password for the item found		-- from the man page
	
			For example:  set pwrd to do shell script "security 2>&1 >/dev/null find-generic-password -gs " & 'Boise Oblast dot com'
	*)
	
	try
		if KeyKind is equal to "Internet password" then
			set pwrd to do shell script "security 2>&1 >/dev/null find-internet-password -gl " & Qlabel
		else
			set pwrd to do shell script "security 2>&1 >/dev/null find-generic-password -gl " & Qlabel
		end if
		(* 
		 That odd 2>&1 redirects password output from stderr to stdout so we can get to it.
		 The >/dev/null  redirects stdout, (everything except password) to nowhere.
		 See Allan Odgaard's posting here:
		 http://blog.macromates.com/2006/keychain-access-from-shell/
		 *)
	on error
		set pwrd to ""
	end try
	
	--display dialog pwrd -- Display raw data  This'll be something like "password: tuttyfruity"	
	
	set oldelim to text item delimiters -- clean up the extra text.
	set text item delimiters to "\""
	set tlst to every text item of pwrd
	set pwrd to item 2 of tlst
	set text item delimiters to oldelim
	
	--display dialog pwrd -- Display cleaned up data  This'll be something like "tuttyfruity"
	
	return pwrd
end GetPassword
-----
-----

Thanks for this. I posted a question about this recently here. I found that you could copy keychain scripting from 10.6 to 10.7 (also URL Access Scripting because that’s missing too). However I like your solution better so I’ll use this.

I would like to add a few clarifications based on my experience (please correct me if something is wrong). I find that the security tool is not as well documented as it should be, so maybe my remarks can be helpful to others, too.

First of all, any password item in a keychain belongs essentially to one of two (completely distinct) types: a generic password or an internet password (there are other item types, of course, like certificates and keys, but here I am only interested in those items that allow you to store some sensitive information). In particular, a secure note is nothing more than a generic password (treated specially by Keychain.app, but not by the command-line tool).

Any generic password inside a keychain is uniquely determined by two values: the “account” and the “service” (this holds for secure notes, too, although typically”but not mandatorily”the account is the null value).

Any internet password inside a keychain is uniquely determined by three values: the “account”, the “server” and the “protocol”.

Note that the “label” needs not be unique inside a keychain. As far as I know, there is no direct way to let the security tool output all the items matching given criteria: security always reports at most one item. Hence, when there is more than one generic password or more than one internet password with the same label, security outputs only one of them.

I’ll make an example. The following session assumes that an empty keychain called test.keychain has been created in the current directory.

First, let us create a generic password whose label coincides with the service (“My credit card number”):

security add-generic-password -a john -s 'My credit card number' -p abc123def456 ./test.keychain

This is the same as choosing “New Password Item.” in Keychain.app, filling “Keychain Item Name” with the value of the -s option, “Account Name” with the value of the -a option and the password with the value of the -p option. Similarly, we may create a generic password for the same service and a different account:

security add-generic-password -a bryan -s 'My credit card number' -p xyz789uvw345 ./test.keychain

Note that we have now two password items with the same label (“My credit card number”).

We may also assign a label which is different from the service:

security add-generic-password -a john -s 'Shopping card' -l 'My credit card number' -p qwerty456 ./test.keychain

(Now we have three items with the same label). This cannot be done directly in Keychain.app: you must edit the item after creating it to change its name (as pointed out by the OP, -l corresponds to “Name”, -s to “Where” and -a to “Account”).

For internet passwords, things are a bit different, because you may have items that coincide on the account and the service, but differ on the protocol. For example,

security add-internet-password -a john -s www.google.com ./test.keychain security add-internet-password -a john -s www.google.com -r http ./test.keychain security add-internet-password -a bryan -s www.google.com -r htps ./test.keychain security add-internet-password -a bryan -s www.google.com -r 'ftp ' ./test.keychain
may all coexist. If John wants to retrieve his Google password, he may try one of:

security find-internet-password -s www.google.com ./test.keychain security find-internet-password -l www.google.com ./test.keychain
but he will get one (and only one) of the four items above. The correct way to retrieve a specific Internet password is by specifying all the three parameters:

security find-internet-password -a john -s www.google.com -r https ./test.keychain

(note that this cannot be shortened by "-s https://www.google.com”).

I’ have said that secure notes are just generic passwords. A secure note can be created as follows:

security add-generic-password -C note -a "" -s 'My note' -p 'This is my secret. Ssst...' ./test.keychain

What makes this a secure note is the ‘-C note’ option. The note can be retrieved as follows:

security find-generic-password -a "" -s 'My note' ./test.keychain

Since secure notes created in Keychain.app have a null account, also this should work:

security find-generic-password -s 'My note' ./test.keychain

Note, however, that the command line offers you the possibility of having notes with a non-empty account (which won’t be visible in Keychain.app) and/or with a label which differs from the service. The latter is especially confusing, as Keychain.app will show the label, but the note will still be identified by the service. Try this:

security add-generic-password -C note -a john -s 'My second note' -l 'My note' -p 'The secret goes here' ./test.keychain security add-generic-password -C note -a bryan -s 'Yet another note' -l 'My note' -p 'This is top-secret' ./test.keychain
The above will all look as exactly the same note (with a different content) in Keychain.app. And

security find-generic-password -l 'My note' ./test.keychain

will retrieve only one of them.

All in all, Keychain.app and security are pretty good tools, although there is some mismatch in their use. Given the considerations above, I would add to your code handlers to retrieve items by account/service and by account/server/protocol. Also,

security unlock-keychain

should be

security unlock-keychain -u

for the tool to prompt a password dialog.

Thanks for sharing this!

Thanks, partron22 and druido, for your posts. I have a Keychain Scripting script on my Tiger machine which reads a particular secure note like this:

on main()
	set pw to text returned of (display dialog "Enter your password" default answer "" with icon note)
	tell application "Keychain Scripting"
		tell keychain "My Keychain.keychain"
			unlock with password pw
			set stuff to password of first generic key whose name is "My note"
			lock
		end tell
	end tell
	display dialog stuff with icon note giving up after 5 * minutes
end main

main()

But it doesn’t work on my Snow Leopard machine. (Not with my note, anyway. It does with the one-line note created by druido’s script.) Instead, it throws an error saying there isn’t enough memory to handle the generic key’s ‘password’ property. If I change it to return the note’s creation and modification dates instead, it returns totally unrelated dates from the next century!

Experimenting with the “security” code in your posts, I find the following:

¢ The ‘2>&1 >/dev/null’ hack is definitely necessary for accessing the contents of the note.
¢ With a one-line note, the result from the shell script is in the form: “password: "The line”"".
¢ With a two-line note, the shell-script result format is: “password: 0x "First line\012Second line"”. (Note the octal representation of a linefeed character.)
¢ With my note, the format is “password: 0x "<plist data containing a key whose value is the note text. The tabs and linefeeds are represented in octal>"”.
¢ Using a four-command shell script to unlock the keychain, get and store the note password, lock the keychain, and return the stored password has the effect of replacing the octal strings with the characters they represent, so AppleScript code isn’t needed for this.

The Snow Leopard version of my script is thus:

on main()
	set pw to text returned of (display dialog "Enter your password" default answer "" with icon note)
	
	set keychain to quoted form of POSIX path of ((path to keychain folder as text) & "My Keychain.keychain")
	
	-- Unlock the keychain; store the password data from the note; lock the keychain; return the password data.
	set stuff to (do shell script "security unlock-keychain -p " & pw & " " & keychain & " ; a=$(security 2>&1 >/dev/null find-generic-password -s 'My note' -g " & keychain & ") ; security lock-keychain " & keychain & " ; echo $a")
	
	-- Extract the "quoted" part of the result.
	set astid to AppleScript's text item delimiters
	set text item delimiters to quote
	set stuff to text from text item 2 to text item -2 of stuff
	set text item delimiters to astid
	
	-- If the result's plist data, extract the note text from it.
	if (stuff begins with "<?xml version=") then
		tell application "System Events" to get value of (make new property list item with data stuff)
		set stuff to |note| of result
	end if
	
	display dialog stuff with icon note giving up after 5 * minutes
end main

main()

Now we know why they’ve removed Keychain Scripting from Lion :slight_smile:

Yes, the password (=the note’s content) is sent to standard error by default.


Any password (not only secure notes) that contains “unusual” or non-printable characters is returned as a hexadecimal string followed by a mixed ascii/octal representation.

That’s good :slight_smile: I had not tried that.

I have to amend my previous post already: internet passwords are in fact fully identified by seven parameters: account (-a option), server (-s option), path (-p option), protocol (-r option), port (-P option), authentication type (-t option) and security domain (-d option), which some protocol requires for authentication.

I tried partron22’s script as-is in the first post, and when run it creates the sample Keychain entry correctly and displays the contents, but when I click the dialog’s “Delete” button, it doesn’t delete the entry. There were no error messages. For the life of me I can’t figure out why not from inspecting the script. Any ideas why? Thanks!

Edit: Tue, Jul 10, 2012, 12:42PM
I just had a friend try the post #1 script as-is, and he says that the Delete function works on his 10.7.4 system. So is this just a known Leopard thing? Is the security command set different for different versions of Mac OS X? I thought this was going to let me write scripts involving Keychain that would work with any version of Mac OS X, to expressly overcome the absence of the Keychain Scripting addition in Lion. Aaarrgghhh!

Edit #2: Tue, Jul 10, 2012, 6:03PM
Oh, I see why I didn’t get an error message; it’s trapped in the on DeletePwrdItemFromKeychain(label, KeyKind) handler. A little troubleshooting reveals that it’s error 1: "security: unknown command “delete-generic-password” ” so it looks like different versions of Mac OS X may use different syntax for the [i]security [/i]commands.

Edit #3: Wed, Jul 18, 2012, 5:07PM
OK, I have a definitive answer now. After comparing the Mac OS X 10.5 vs. the 10.6/10.7 man pages for security, it turns out that commands exist only for deleting the whole keychain in 10.5, while the:

delete-generic-password
delete-internet-password

…commands don’t exist in 10.5, but only in 10.6 and 10.7.

I guess with a little more trouble the same functionality could be attained in Leopard by deleting the whole keychain then recreating a new one of the same name containing all the other entries from temporarily stored values in AppleScript variables.

Model: MacBook Pro (5,2) “Mid-2009”
AppleScript: 2.0.1
Browser: Firefox 4.0.1
Operating System: Mac OS X (10.5.8)

I just ran the code from post1 of this thread (lost my original) under Mavericks, and it created and set new keychain items nicely. It ran without changes under OS 10.9.1, 10.8.5, 10.7.5, and 10.6.8. Only trouble I noted was a tendency to leave Keychain access open when creating a new item under Snow Leopard and Lion.

Put together a bit of code to make those new keychain entries easily avalable from within Applescripts.
This code also runs without change under OS 10.9.1, 10.8.5, 10.7.5, and 10.6.8. as part of a Script app:

-- KeychainPasswordGetter.scpt
-- BP Feb 2014

-- Recovers info about a keychain item, including password
-- Easy to slip into new scripts.
-- You'll want to create your keychain iten before using this script

-- user settable variables
set keychainlabel to "ReStartupChooser-key" -- The name of the keychain item, in this case the Script App I've put this code in
set KeyKind to "application password" --e unless new type is "Internet password", then... --
-- end of user settable variables


set tem to GetAcctnameServicenameAndPwrdFromPwrdItem(keychainlabel, KeyKind) -- Read and display data from that new password item
set pw to item 2 of tem -- Glorious new code!
return pw

-----
-----
on GetAcctnameServicenameAndPwrdFromPwrdItem(label, KeyKind) -- This is straight from the Aug 2011 post
	set retarray to {"", "", ""}
	set Qlabel to quoted form of label
	set oldelim to text item delimiters
	
	try
		if KeyKind is equal to "Internet password" then
			set t to do shell script "security 2>&1 find-internet-password -gl " & Qlabel
		else
			set t to do shell script "security 2>&1 find-generic-password -gl " & Qlabel
		end if
		(* 
		 That odd 2>&1 redirects password output from stderr to stdout so we can get to it along with everything else.
		 See Allan Odgaard's posting here:
		 http://blog.macromates.com/2006/keychain-access-from-shell/
		 *)
		
		--display dialog t			-- Display raw data
		(* 
			Output is messy, and needs to be parsed.
			grep and friends could work here.
			See http://www.maclovin.de/2010/02/access-os-x-keychain-from-terminal/
			I'd rather use Applescript's text item delimiters here:
		*)
		set text item delimiters to "acct" -- Get Account name
		set tlst to every text item of t
		set acct to item 2 of tlst
		set text item delimiters to "\""
		set tlst to every text item of acct
		set acct to item 3 of tlst
		
		if KeyKind is equal to "Internet password" then -- Get Service name or Server
			set text item delimiters to "srvr" -- server
		else
			set text item delimiters to "svce" -- service
		end if
		set tlst to every text item of t
		set svcnam to item 2 of tlst
		set text item delimiters to "\""
		set tlst to every text item of svcnam
		set svcnam to item 3 of tlst
		
		set text item delimiters to "\"" -- Get Password
		set tlst to every text item of t
		set pw to item 2 of tlst
		--display dialog pw
		
		set retarray to {acct, pw, svcnam}
	on error
		display dialog "Sorry, can't find your keychain item." buttons "OK" default button 1 with icon caution
		set retarray to {"", "", ""}
	end try
	
	set text item delimiters to oldelim
	return retarray
end GetAcctnameServicenameAndPwrdFromPwrdItem
-----
-----