More Secure Passwords In AppleScript

Sometimes an Applescript might require user authentication. For example, this will happen if using do shell script to run a UNIX command that needs administrator access:

do shell script "chown -R Vickash " & quoted form of "/Users/Vickash/Desktop/My Files" with administrator privileges

A dialog box appears prompting the user for their password before the shell script executes. This is fine if your script is always run manually by the user. However, for Folder Actions, or any type of script that runs on a trigger, this can be a problem. What if the user isn’t at the machine when the script is triggered? And if the script runs frequently, do you really want to ask the user for a password each and every time?

If you’ve come across this problem, you know that one possible solution is to put the login credentials inline. However, the security problem here is obvious; anyone looking at the source code has the user’s login credentials:

do shell script "chown -R Vickash " & quoted form of "/Users/Vickash/Desktop/My Files" user name "Vickash" password "this is a test" with administrator privileges

Ideally, the password would be stored elsewhere, in an encrypted form, and only accessed by the script when necessary. Luckily, OS X provides an app called Keychain Access (check your Utilities folder) for storing credentials in encrypted form.

Using Keychains

Your default keychain is at ~/Library/Keychains/login.keychain, and holds things like Safari’s saved passwords. It’s protected with your login password by default. To keep things clean, I recommend creating a new keychain specifically for storing generic password items that you want Applescript to access. Here’s how:

1) Open Keychain Access.app and go to File > New Keychain… You can name the keychain whatever you want and store it wherever you want, but take note of it’s full path. You’ll need that for the script to access it later.

Step 1 - Create A New Keychain

2) When creating a new keychain, you’ll need to set a password. This is separate from the credentials you want to save within the keychain; it’s a password that restricts access to the keychain itself. Think of it like a LastPass or 1Password master password. This is the password your user will need to type the first time the script runs to allow the script access to the keychain and the credentials within.

Step 2 - Set the Keychain's Password

3) Once the keychain is created, create a generic password item by clicking on the “add” button below the empty list. Put in the credentials that you would have typed inline before, and give the item a name. I’ve called it “Admin” here. It doesn’t matter much what you call it, but you’ll need to know the name for the script to find the right item.

Step 3 - Create the Keychain Item

By doing this, you’ve now used the keychain’s encryption to protect the username and password that would have been typed as plain text in the script before. Now we need to set the script up to access the keychain.

4) To make things even more convenient I recommend changing the keychain’s settings so that it doesn’t automatically lock on sleep or after a period of inactivity. You can adjust this to suit your needs, but the idea is that the user allows the script access to the keychain once, the first time the script runs, and then doesn’t need to again until the user logs out.

Step 4 - Edit the Keychain's Settings


Step 5 - Keychain Settings

Accessing the Keychain from AppleScript

We’ve solved half the problem. The password is stored in an encrypted form on disk rather than in plaintext. But now we need some way to pull those credentials out of the keychain and use them in our script.

Apple used to provide a standard extension for this. You’d call tell application "Keychain Scripting" and be able to access the keychains, but I’m not sure that’s still exists in Lion, and it had major performance issues before that.

Daniel Jalkut at Red Sweater Software has written a tiny scriptable app called “Usable Keychain Scripting” for Lion and Pre-Lion systems. It’s free, I’ve used it, and it works great. If you don’t have a problem with installing something extra to get your scripts to run, head over to his blog and check it out.

If you’d like your scripts to be dependent only on the keychain, like I needed on a couple occasions, I’ve come up with a pure AppleScript workaround. On OS X there’s a shell command called security that can be used from the terminal to access keychains.

You can read more about the security command here, but the main thing we’re interested in is its find-generic-password option. When using this option and passing in the keychain path, and keychain item name we’re searching for, it returns the full details for the password item as text.

Running

security 2>&1 find-generic-password -gs "Admin" "/Users/Vickash/Library/Keychains/ScriptingDemo.keychain"

Returns

keychain: "/Users/Vickash/Library/Keychains/ScriptingDemo.keychain"
class: "genp"
attributes:
    0x00000007 <blob>="Admin"
    0x00000008 <blob>=<NULL>
    "acct"<blob>="Vickash"
    "cdat"<timedate>=0x32303132303131353136343231355A00  "20120115164215Z00"
    "crtr"<uint32>=<NULL>
    "cusi"<sint32>=<NULL>
    "desc"<blob>=<NULL>
    "gena"<blob>=<NULL>
    "icmt"<blob>=<NULL>
    "invi"<sint32>=<NULL>
    "mdat"<timedate>=0x32303132303131353136343231355A00  "20120115164215Z00"
    "nega"<sint32>=<NULL>
    "prot"<blob>=<NULL>
    "scrp"<sint32>=<NULL>
    "svce"<blob>="Admin"
    "type"<uint32>=<NULL>
password: "this is a test"

Now, it’s obvious that the only two lines we care about are "acct"<blob>="Vickash" and password: "this is a test". By executing the shell command via AppleScript, and writing a separate subroutine to extract the data we need, I’ve come up with the following:

on extractData(theText, theFieldName, theEndDelimiter, spaces)
    set theDataStart to the offset of theFieldName in theText
    if theDataStart = 0 then
        return ""
    else
        set theDataStart to theDataStart + (length of theFieldName) + spaces
        set theData to text theDataStart through end of theText
        set theDataEnd to ((offset of theEndDelimiter in theData) - 1)
        set theData to text 1 through theDataEnd of theData
    end if
end extractData

on getCredentials of theKeychainItem from theKeychain
    set theKeychainPath to (POSIX path of theKeychain) as text
    try
        set theKeychainAlias to (POSIX file theKeychainPath) as alias
        set theKeychainPath to (POSIX path of theKeychainAlias)
    on error
        return "The keychain file was not found at the specified location: " & theKeychain as text
    end try
    try
        set theResult to do shell script "security 2>&1 find-generic-password -gs " & quoted form of theKeychainItem & " " & quoted form of theKeychainPath
        set theAccount to extractData(theResult, "\"acct\"<blob>=\"", "\"", 0)
        set thePassword to extractData(theResult, "password: \"", "\"", 0)
        return {account:theAccount, password:thePassword}
    on error
        return "The generic password item was not found in the keychain. Please verify its name: " & theKeychainItem
    end try
end getCredentials

Practical Use

Copy these subroutines into your script. Now, whenever your script script needs to get credentials from a keychain, do something like:

set theCredentials to getCredentials of "Admin" from "/Users/Vickash/Library/Keychains/ScriptingDemo.keychain"

This will return an AppleScript record, in my case:

theCredentials {account: "Vickash", password: "this is a test"}

Now that the script has the login credentials stored in a record, I can execute the command that would have required manual authentication like:

do shell script "chown -R Vickash " & quoted form of "/Users/Vickash/Desktop/My Files" user name (account of theCredentials) password (password of theCredentials) with administrator privileges

Which is much more secure than:

do shell script "chown -R Vickash " & quoted form of "/Users/Vickash/Desktop/My Files" user name "Vickash" password "this is a test" with administrator privileges

Notes

Keep in mind that the first time the script runs it will prompt you for the keychain password to unlock it. Put the password in and click “Always Allow” to give the script continuous access to the keychain. If you’ve used the settings I recommended, the keychain won’t relock until it’s manually relocked via the Keychain Access app, or until the user logs out.

The getCredentials handler returns text, rather than a record, on error, so you can write your script to catch this and display a dialog box.

Advertisement