Due to a small project I’m developing at the moment I had to access the Mac OS X keyring over python. There is some code of seen around but was not very ‘clean’. The following is my attempt to access the Mac OS X Keyring via python using ctypes, I hope it helps someone out there:

from ctypes import (
    byref,
    c_int32,
    c_uint32,
    c_void_p,
    create_string_buffer,
    memmove,
    cdll,
)
from ctypes.util import find_library
 
from secrets.utils import are_not_none, valid_args
 
 
# Types
 
OSStatus = c_int32
SecKeychainItemRef = c_void_p
SecKeychainRef = c_void_p
 
# Constants
 
errSecSuccess = 0
errSecItemNotFound = -25300
 
 
# Native libraries
 
_security = cdll.LoadLibrary(find_library('Security'))
_core = cdll.LoadLibrary(find_library('CoreServices'))
 
 
def keyring_is_open():
    """Assert that the keyring is not None."""
 
    def keyring_not_none(name, value, method_name='Method'):
        """Ensure that the keyring is open."""
        if not getattr(value, '', None):
            raise OSError('Keyring most be open.')
 
    return valid_args(keyring_not_none, (('Keyring', 0)))
 
 
class Backend(object):
    """Keyring backend for Mac Os X."""
 
    def __init__(self):
        """Create a new Mac OS X backend."""
        super(Backend, self).__init__()
        self.keychain = None
 
    def _get_item(self, realm, username):
        """Return the item that matched the given info."""
        item = SecKeychainItemRef()
        status = _security.SecKeychainFindGenericPassword(self.keychain,
                                         len(realm), realm, len(username),
                                         username, None, None, byref(item))
        return item, status
 
    def open(self):
        """Open the keyring."""
        # we should not be open
        if self.keychain is not None:
            raise OSError()
 
        self.keychain = SecKeychainRef()
        # try to open the keyring
        if _security.SecKeychainOpen('login.keychain', byref(self.keychain)):
            raise OSError("Can't access the keychain")
 
    @keyring_is_open
    def close(self):
        """Close the keyring."""
        _core.CFRelease(self.keychain)
        self.keychain = None
 
    @keyring_is_open
    @are_not_none((
        ('realm', 1),
        ('username', 2),
        ('password', 3)))
    def set_password(self, realm, username, password):
        """Set the password for the given real and username."""
        item, status = self._get_item(realm, username)
 
        if status == errSecItemNotFound:
            status = _security.SecKeychainAddGenericPassword(self.keychain,
                                                len(realm), realm,
                                                len(username), username,
                                                len(password), password, None)
            if status:
                raise OSError()
        else:
            raise OSError()
 
    @keyring_is_open
    @are_not_none((
        ('realm', 1),
        ('username', 2),
        ('password', 3)))
    def update_password(self, realm, username, password):
        """Update the password for the given realm."""
        item, status = self._get_item(realm, username)
 
        if status and status == errSecItemNotFound:
            raise OSError("Can't store password in keychain")
        else:
            status = _security.SecKeychainItemModifyAttributesAndData(item, None,
                                                len(password), password)
            _core.CFRelease(item)
            if status:
                raise OSError()
        if status:
            raise OSError("Can't store password in keychain")
 
    @keyring_is_open
    @are_not_none((
        ('realm', 1),
        ('username', 2)))
    def get_password(self, realm, username):
        """Get the password for the given real and username."""
        length = c_uint32()
        data = c_void_p()
        status = _security.SecKeychainFindGenericPassword(self.keychain,
                                          len(realm), realm,
                                          len(username), username,
                                          byref(length), byref(data), None)
        if status == errSecSuccess:
            password = create_string_buffer(length.value)
            memmove(password, data.value, length.value)
            password = password.raw
            _security.SecKeychainItemFreeContent(None, data)
        elif status == errSecItemNotFound:
            password = None
        else:
            raise OSError("Can't fetch password from system")
        return password
 
    @keyring_is_open
    @are_not_none((
        ('realm', 1),
        ('username', 2)))
    def delete_password(self, realm, username):
        """Delete the password of the given real and username."""
        item, status = self._get_item(realm, username)
        if status == errSecSuccess:
            _security.SecKeychainItemDelete(item)
            _core.CFRelease(item)
        else:
            raise OSError()

I have not added the code of the decorator because they are just noise, the only thing they do is to check that the keyring was indeed opened (self.keyring != None) and that the parameters with the given index are not None (I’m lazy and I prefer to use decorators for this mundane tasks that are done everywhere.

Read more