Keychain Access From Shell
I have a few scripts which need a password to complete their task. For example I have GeekTool show information extracted from a database, I create new sneakemail addresses from Quicksilver by letting a script simulate the browser session, and I have the TextMate makefile sign updates with a passphrase protected private key.
Mac OS has a keychain which is intended for storing and retrieving passwords in a secure fashion, and this service can fortunately be accessed from shell, so that is what I use for my passwords.
The command to access the keychain is security
and it has a manual page. But let me save you some time and give you the gist of it.
The keychain can store different kinds of entries. Generally we are interested in either generic passwords (i.e. with no predetermined purpose) or internet passwords (those which go together with an internet scheme/protocol such as https
, sftp
, smtp
, or similar.)
You can create a new password by launching Keychain Access (located in the Utilities folder) and click the plus button below the right list (showing all your existing keychain items.)
Keychain will ask you for Keychain Item Name, Account Name, and Password. For a generic password, the Keychain Item Name is a textual description of the password (also labeled Where and referred to as the service.) The Account Name is the name we will use to retrieve the password (we can also retrieve by service, or both,) and the Password should be self-explanatory.
After having created a password, let’s say we set the Account Name to test
, we can run the following from the shell (Terminal):
security find-generic-password -a test
This dumps the record for the test
account, everything except the actual password. To also get the password, we would have to add the -g
option:
security find-generic-password -ga test
When you run this command, you will be asked if security
should be granted access to the keychain item we created. You can either deny, allow once, or always allow. You can later edit which applications are allowed to access the item from Keychain Access. Locate the item and click the I button below the list (or double click) to alter the settings of the item.
The output from security
is however not useable as-is. The output looks something like:
keychain: "/Users/duff/Library/Keychains/login.keychain"
class: "genp"
attributes:
0x00000007 ="Test Item"
0x00000008 =
"acct"="test"
"cdat"=0x32303036303431373035313233365A00 "20060417051236Z\000"
"crtr"=
"cusi"=
"desc"=
"gena"=
"icmt"=
"invi"=
"mdat"=0x32303036303431373036343034375A00 "20060417064047Z\000"
"nega"=
"prot"=
"scrp"=
"svce"="Test Item"
"type"=
password: "the4seasons"
For better or worse, the last line (containing the actual password) is actually written to stderr instead of stdout. This however means, that we can quickly silence all but the last line by redirecting stdout to /dev/null
. We redirect stderr to stdout (which is done using 2>&1
, meaning redirect file descriptor 2 (stderr) to a duplicate of 1 (stdout)). The ordering here matters, since stderr is redirected to a duplicate of stdout, it is important that we do this redirection before we redirect stdout to /dev/null
. So we end up with:
security 2>&1 >/dev/null find-generic-password -ga test
And the result from that is:
password: "the4seasons"
We can pipe the result through a small ruby script to extract the password:
security 2>&1 >/dev/null find-generic-password -ga test \
|ruby -e 'print $1 if STDIN.gets =~ /^password: "(.*)"$/'
This will only output something, if we matched the password line, so we can treat no output as if there either was no password stored, or security
was not allowed to read it.
I suggest wrapping this in a shell function, e.g.:
get_pw () {
security 2>&1 >/dev/null find-generic-password -ga test \
|ruby -e 'print $1 if STDIN.gets =~ /^password: "(.*)"$/'
}
Then whenever we need the password, we can use "$(get_pw)"
as placeholder for the actual password.
I mentioned that there is also something called internet passwords. These work the same, but instead the command to retrieve one is find-internet-password
and in addition to -a
for account (the username) and -s
for service (the domain name to which the password is associated) there are a few other options as well, like -r
for a four letter protocol “name” (ftp
is "ftp "
and https
is "htps"
), -p
for path, -P
for port, etc.
As with the generic passwords, it is possible to search for a password by only providing one search criterion. For example if you have a PayPal account, you can try this line:
security find-internet-password -gs www.paypal.com