I have a few shellscripts that need username and passwords to accomplish some tasks. For example, I wrote a shellscript to backup some data in an encrypted sparsebundle file that is stored on an AFP share. So I have a username and a password to mount the share, and some others credentials to mount the sparsebundle.
When it comes to automating the backup process, I had to face a simple issue : where do I store these usernames and passwords ?
Hard-code them in the script ? Naaah.
Create some kind of read-only file with proper permissions ? Sounds rather risky.
So, what would be the right way to do this with OSX ?
The answer is kind of obvious : OSX ships with a nice tool called Keychain Access.app. This service is intended to store and retrieve credentials, keys, passwords, certificates,... in a secure fashion. But how can I access it from the shell ?
This is where
security comes to the rescue !
security is a Command Line Interface that allows you to administer and manipulate keychains, keys and certificates. In other words,
security allows you to access Keychain (the app) from the shell (local shell ! SSH ! shellscript !). Wonderful, isn't it ?
Although it comes with a manpage, let's see some examples.
Adding items to a keychain
First, let's add a generic password to the default keychain. Here is what I'm using to store the credentials for my sparsebundle file :
security add-generic-password -a "username" -D "Image Disk Password" -s "MyFile.sparsebundle" -w "THis_IS_so_s3crEt_!"
-a option allows you to specify the account name.
-D is for the description.
-s allows you to specify the service name (here I chose to set it to the sparsebundle filename but you can put whatever you want). And
-w allows you to specify the password.
Adding an Internet password is a little bit different :
security add-internet-password -a "username" -D "AFP Share Password" -s "myserver.example.com" -p "share" -r "afp " -l "My AFP Share" -w "THis_IS_4lso_very_s3crEt_!" /Library/Keychains/System.keychain
This time we had to specify the server with
-s, a path (here, the name of the shared point) with
-p, a label with
-l and the protocol (could have been http, ftp, or whatever) with
WARNING : the argument for the
-r option MUST be exactly 4 characters long. So you have to write "afp ", not "afp".
Please also notice that we decided to add this entry to the System keychain which is located in /Library/Keychains/System.keychain. We could have chosen to add it to another existing keychain by providing the path to this keychain, or we could have created a brand new one with :
security create-keychain /path/to/my/new/Personnal.keychain
Retrieving an item in a keychain is pretty easy. Here I choose to retrieve the item by service (remember the
-s option we used when we created the entry ?) :
security find-generic-password -g -s "MyFile.sparsebundle"
Which outputs the following :
keychain: "/Users/francoiskubler/Library/Keychains/login.keychain" class: "genp" attributes: 0x00000007 <blob>="MyFile.sparsebundle" 0x00000008 <blob>=<NULL> "acct"<blob>="username" "cdat"<timedate>=0x32303134313232393130323735365A00 "20141229102756Z\000" "crtr"<uint32>=<NULL> "cusi"<sint32>=<NULL> "desc"<blob>="Image Disk Password" "gena"<blob>=<NULL> "icmt"<blob>=<NULL> "invi"<sint32>=<NULL> "mdat"<timedate>=0x32303134313232393130323735365A00 "20141229102756Z\000" "nega"<sint32>=<NULL> "prot"<blob>=<NULL> "scrp"<sint32>=<NULL> "svce"<blob>="MyFile.sparsebundle" "type"<uint32>=<NULL> password: "THis_IS_so_s3crEt_!"
But I could have chosen to retrieve it by account name (remember the
-a option ?) :
security find-generic-password -g -a "username"
Or by account name AND service AND description in a specific keychain file :
security find-generic-password -g -a "username" -s "MyFile.sparsebundle" -D "Image Disk Password" /path/to/my/new/Personnal.keychain
If you have more than one entry that matches your criteria,
security will only output the first one that matches. So, be careful when you add a new entry in a keychain, and make sure you'll be able to retrieve it easily later by specifying a different label (
-l option) or a comment (
-j option) or whatever you prefer.
Parsing the output
Obviously, the output of the command is kind of useless and needs to be parsed. On OSX prior to 10.9, when retrieving a password with the -g option (which asks
security to print the password), the password was printed on
stderr, so you had to play with shell redirections and write something like the following :
my_precious=$(security 2>&1 > /dev/null find-generic-password -g -s "MyFile.sparsebundle" | cut -d "\"" -f 2)
Starting with OSX 10.9, you can drop the
-g flag and use
-w instead :
my_precious=$(security find-generic-password -w -s "MyFile.sparsebundle")
The cool part here is that you can retrieve more than just the password. You can also retrieve the username (tested on OSX >= 10.6) :
output=$(security 2>&1 find-internet-password -s "myserver.example.com" -r "afp " -g) username=$(head -n 7 <<< "$output" | tail -n 1 | cut -d "\"" -f 4) my_precious=$(head -n 1 <<< "$output" | cut -d "\"" -f 2)
Of course, depending on your needs, you'll have to modify the commands, but you should now get the idea. The most important part here is to remember that when called with the
security will print the password to
stderr instead of
More : Certificates, Keys, ...
There are a bunch of option that allows you to manage certificates, keys and identities (certificate + private key). I've never had to deal with that stuff so I can't provide any decent example. But they do exist and a quick look at the manpages should be enough to help you.