TextMate News

Anything vaguely related to TextMate and macOS.

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

categories OS X Tips

15 Comments

17 April 2006

by Jay Soffian

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”.

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.

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

The downside of using this method is that you can only add or find. There is no “modify” functionality in the ‘security’ tool.

17 May 2006

by Matthew

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

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.

Great scripts! Thanks Allan.

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

02 January 2008

by Ian Boardman

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.

21 January 2008

by Christian

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.

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?

@ 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.

@ 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?

you want service: instead of where:

cheers!

31 December 2009

by Lord Anubis

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