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 Access

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.

Keychain Access From Application

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

15 Comments

  1. 17 Apr 2006 | # Jay Soffian wrote…

    If there are unusual characters in the password, it isn't output as plain text, it's output encoded in hex. Here's a python script I've been using which covers that case:

    #!/usr/bin/python
    import sys
    import os
    import re
    
    def decode_hex(s):
        s = eval('"' + re.sub(r"(..)", r"\x\1", s) + '"')
        if "" in s: s = s[:s.index("")]
        return s
    
    def main(svce, acct):
        cmd = ' '.join([
            "/usr/bin/security",
            " find-generic-password",
            "-g -s '%s' -a '%s'" % (svce, acct),
            "2>&1 >/dev/null"
        ])
        p = os.popen(cmd)
        s = p.read()
        p.close()
        m = re.match(r"password: (?:0x([0-9A-F]+)\s*)?\"(.*)\"$", s)
        if m:
            hexform, stringform = m.groups()
            if hexform: print decode_hex(hexform)
            else: print stringform
    
    if __name__ == "__main__":
        if len(sys.argv) == 3:
            main("SSH", sys.argv[2])
        else:
            main("SSH", os.getenv("USER"))
    

    And in case the whitespace gets swallowed when I submit the comment, you can see the script properly formatted here:

    http://www.macosxhints.com/article.php?story=2003121708324421

    Scroll to the bottom of that hint and look for "ssh-askpass".

  2. 17 Apr 2006 | # eric wrote…

    You mention that you trigger a script via Quicksilver to add (and maybe search for?) an address in your sneakemail account.

    Did you write this script? I can't find such a script or plugin anywhere, and I would love to find a quicksilver solution to searching and creating sneakemail addresses.

  3. 17 Apr 2006 | # jrk wrote…

    Likewise — I'd love to see the script you wrote for creating sneakemail addresses in Quicksilver…

  4. 10 May 2006 | # Aidan wrote…

    The downside of using this method is that you can only add or find. There is no "modify" functionality in the 'security' tool.

  5. 17 May 2006 | # Matthew wrote…

    It's too bad this doesn't work when you're SSH'ed in… I want to be able to retreive these from my home computer when I'm at work, but I get the error "security: SecKeychainFindInternetPassword: User interaction is not allowed." when it tries to do the Allow once/Allow always/Deny dialog

  6. 16 Jan 2007 | # paul wrote…

    I'm writing to verify the security: SecKeychainFindGenericPassword: User interaction is not allowed. bit. Also, this page is the only hit in google for

    security find-generic-password "User interaction is not allowed."

    The app needs a console user, which doesn't make sense for a commandline app, but we work with what we got.

  7. 29 May 2007 | # Shell Account wrote…

    Great scripts! Thanks Allan.

  8. 21 Nov 2007 | # Stefan wrote…

    @Matthew: When using via SSH you have to unlock the keychain first in the script or else with: security unlock-keychain -p ' '

  9. 02 Jan 2008 | # Ian Boardman wrote…

    At least for me, in remote login, even after unlock is performed, still get "User interaction is not allowed." I'm running the latest version of MacOS 10.4.

  10. 22 Jan 2008 | # Christian wrote…

    You can also trim the ruby part and do with pure shell (bash needed):

    pwline=$(security …) # read the pw pwpart=${pwline#*\"} # strip the leading quote echo ${pwpart%\"} # strip the trailing quote

    This won't help with the hex encoding though.

  11. 07 Jul 2008 | # Mark wrote…

    Hi – been a while since a post – but im looking to ADD a new item to the keychain through the security command – to be pricise a wireless password. We have 400+ MBP's and our wireless credentials will change – we want to be able to roll out the wireless password to all these users without having to distribute passwords/get them to do it. ANyone have any idea how we could do this through the 'Security' command?

  12. 07 Jul 2008 | # drew wrote…

    @ Mark, You would be better off using Keychain scripting via Apple Script to create the key. something along the lines of Make new key with properties …

    Then distribute it as an app and run via Remote Desktop, and remove it since it can recreate the password keys on any mac, it's a potential security risk.

  13. 08 Jul 2008 | # Mark wrote…

    @ Drew, Have had limited luck with that. Looks like this: tell application "Keychain Scripting" Make new key with properties {Name:"network name", Kind: "AirPort network password", where: F0413E9D-10F0-4968-8D58-F372AA6054C3, password:"password"} end tell

    It doesn't like the 'Where' item – which i assume is the SSID or something of the network (These properties are pulled out of keychain access). WHen i remove 'where', it compiles ok, but when run it says "Can't make class key'. Where am I going wrong?

  14. 20 Feb 2009 | # cube wrote…

    you want service: instead of where:

    cheers!

  15. 31 Dec 2009 | # Lord Anubis wrote…

    Hi,

    Thanks for the explanation, but I'am looking for a way to get a system key. Do you know how to get that working, I have no clue how to do that.

    Thanks in advance

Comments closed, you can use the mailing list for discussion.