Skip to content

Instantly share code, notes, and snippets.

@pudquick
Created March 6, 2017 20:53
Show Gist options
  • Save pudquick/e61631ea83c958befa5db92f8bde527d to your computer and use it in GitHub Desktop.
Save pudquick/e61631ea83c958befa5db92f8bde527d to your computer and use it in GitHub Desktop.
Storing and retrieving a generic password in the login.keychain in macOS via python and pyobjc
import objc
from ctypes import c_char
from Foundation import NSBundle
Security = NSBundle.bundleWithIdentifier_('com.apple.security')
S_functions = [
('SecKeychainGetTypeID', 'I'),
('SecKeychainItemGetTypeID', 'I'),
('SecKeychainAddGenericPassword', 'i^{OpaqueSecKeychainRef=}I*I*I*o^^{OpaqueSecKeychainItemRef}'),
('SecKeychainOpen', 'i*o^^{OpaqueSecKeychainRef}'),
('SecKeychainFindGenericPassword', 'i@I*I*o^Io^^{OpaquePassBuff}o^^{OpaqueSecKeychainItemRef}'),
]
objc.loadBundleFunctions(Security, globals(), S_functions)
SecKeychainRef = objc.registerCFSignature('SecKeychainRef', '^{OpaqueSecKeychainRef=}', SecKeychainGetTypeID())
SecKeychainItemRef = objc.registerCFSignature('SecKeychainItemRef', '^{OpaqueSecKeychainItemRef=}', SecKeychainItemGetTypeID())
PassBuffRef = objc.createOpaquePointerType("PassBuffRef", b"^{OpaquePassBuff=}", None)
def resolve_password(password_length, password_buffer):
return (c_char*password_length).from_address(password_buffer.__pointer__)[:].decode('utf-8')
# Get the login keychain
result, login_keychain = SecKeychainOpen('login.keychain', None)
# Password details - service is the display name, acccount is the account name/user name
svc_n = "test-service"
act_n = "test-account"
# Store a password
act_p = "test-pass"
result, keychain_item = SecKeychainAddGenericPassword(login_keychain, len(svc_n), svc_n, len(act_n), act_n, len(act_p), act_p, None)
# Retrieve a password
result, password_length, password_buffer, keychain_item = SecKeychainFindGenericPassword(login_keychain, len(svc_n), svc_n, len(act_n), act_n, None, None, None)
password = None
if (result == 0) and (password_length != 0):
# We apparently were able to find a password
password = resolve_password(password_length, password_buffer)
@biemster
Copy link

biemster commented Sep 8, 2022

Is this better than using import Security directly? (https://pyobjc.readthedocs.io/en/latest/apinotes/Security.html) Sorry yes this is better, Security seems to be an extra dependency.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment