Storing Script Passwords In Macs Keychain

October - 2020

Forward

  • I built my own functions to use Mac's Keychain Access for password storage, but you should probably checkout keyring to do the same thing and more.



I've been using GPG to encrypt my various credentials so I don't accidentally flash them while streaming or taking zoom calls. The process also ensures I never store passwords directly inside a script. A sin that's all to easy to commit.

I drop passwords into individual plain-text files and encrypt them with:

gpg --armor --encrypt --yes -r self@localhost.localdomain PASSWORD_FILE

When I need one, I use this Python function to grab it:

import subprocess

def get_credentials(file_path):    
    return subprocess.check_output([
        '/usr/local/bin/gpg', '--decrypt', 
        '-q', file_path
    ]).decode('utf-8').split("\n")[0]

credential = get_credentials('PASSWORD_FILE.asc')

A bit tedious, but it works great.

I recently started working with AWS Secrets Manager. It got me thinking about the Keychain Access app on Macs. I wondered if it was possible to store credentials for my scripts there. I found this great post from Kristian Köhntopp that shows how to use it from the command line. Working from the article, I built these simple Python functions.


# set_security_credential.py

#!/usr/bin/env python3

import subprocess
import getpass

def set_security_credential(*, credential_for, credential_value):
    subprocess.run([
        'security', 'add-generic-password', '-U',
        '-a', getpass.getuser(), '-s',
        credential_for, '-w', credential_value
    ])

set_security_credential(
    credential_for = 'alans-example-api',
    credential_value = 'lorem ipsum 9000'
)

# get_security_credentail.py

#!/usr/bin/env python3

import getpass
import subprocess

def get_security_credentail(*, credential_for):
    return subprocess.run([
        'security', 'find-generic-password',
        '-w', '-a', getpass.getuser(),
        '-s', credential_for
    ], stdout=subprocess.PIPE).stdout.decode('utf-8')

password_value = get_security_credentail(
    credential_for = 'alans-example-api'
)

print(password_value)

Because the values are both set and retrieved by the security cli tool under your user ID, they don't require an additional password to retrieve them. And, since it's always the security app working under the covers, the functions work across different scripts.

My next step will be to drop these into pages on a localhost website. That'll let me add and update credentials from my own little UI. I'll still keep everything in my password manager, but this is how I'll get to them with scripts.

I really like this approach. No more having to mess with credentials to obfuscate them. Just let the system's built tool take care of it.




Afterword

  • It's important to point out that neither the GPG method nor the security cli method provide extra security since they aren't setup to require their own passwords. The only thing they provide is a straightforward way to keep passwords out of scripts and a way obfuscate/hide them so you don't accidentally expose them.
  • I did this in Python. The same approach can be used by any language/tool that can execute the security process and gather its output.
  • The Keychain Access app is Mac specific. Windows provides Credential locker which should be able to do the same thing. Doing that exploration is left as an exercise for the reader (Probably see keyring for getting started with that).