perl/keyring/osxkeychain / lib / Passwd / Keyring /

package Passwd::Keyring::OSXKeychain;

use warnings;
use strict;

use IPC::System::Simple qw(capturex systemx);

=head1 NAME

Passwd::Keyring::OSXKeychain - Password storage implementation based on OSX/Keychain.

=head1 VERSION

Version 0.1001


our $VERSION = '0.1001';

=head1 WARNING

I do not have Mac. I wrote the library mimicking actions
of some python libraries and tested using mocks, but help
of somebody able to test it on true Mac is really needed.


OSXKeychain Keyring based implementation of L<Keyring>. Provide secure
storage for passwords and similar sensitive data.

    use Passwd::Keyring::OSXKeychain;

    my $keyring = Passwd::Keyring::OSXKeychain->new(
         app=>"blahblah scraper",
         group=>"Johnny web scrapers",

    my $username = "John";  # or get from .ini, or from .argv...

    my $password = $keyring->get_password($username, "");
    unless( $password ) {
        $password = <somehow interactively prompt for password>;

        # securely save password for future use
        $keyring->set_password($username, "");

    login_somewhere_using($username, $password);
    if( password_was_wrong ) {
        $keyring->clear_password($username, "");

Note: see L<Passwd::Keyring::Auto::KeyringAPI> for detailed comments
on keyring method semantics (this document is installed with
C<Passwd::Keyring::Auto> package).


=head2 new(app=>'app name', group=>'passwords folder')

Initializes the processing. Croaks if osxkeychain keyring does not 
seem to be available.

Handled named parameters: 

- app - symbolic application name (not used at the moment, but can be
  used in future as comment and in prompts, so set sensibly)

- group - name for the password group (will be visible in seahorse so
  can be used by end user to manage passwords, different group means
  different password set, a few apps may share the same group if they
  need to use the same passwords set)


- security_prog - location of security program (/usr/bin/security by
  default, possibility to overwrite is mostly needed for testing)

- keychain - keychain to use (if not default)


sub new {
    my ($cls, %opts) = @_;
    my $self = {
        app => $opts{app} || 'Passwd::Keyring',
        group => $opts{group} || 'Passwd::Keyring unclassified passwords',
        security => $opts{security_prog} || '/usr/bin/security',
        keychain => $opts{keychain},
    bless $self;

    unless( -x $self->{security} ) {
        croak("OSXKeychain not available: security program $self->{security} is missing");
    if($self->{keychain}) {
        # Add .keychain suffix if missing
        $self->{keychain} .= '.keychain' 
          unless $self->{keychain} =~ /\.keychain$/;

    # Some test operation
    my $reply = capturex($self->_make_sys_args(

    return $self;

# Prepares args by prefixing with command and suffixing with keychain
# if specified
sub _make_sys_args {
    my ($self, @args) = @_;
    unshift @args, $self->{security};
    push @args, $self->{keychain} if $self->{keychain};
    return @args;

=head2 set_password(username, password, realm)

Sets (stores) password identified by given realm for given user 


sub set_password {
    my ($self, $user_name, $user_password, $realm) = @_;

    # TODO: maybe use -l (label) instead of -D
        "-q", # quiet
        "-a", $user_name,
        "-s", $realm,
        "-D", $self->{group}, # "kind", can be used to match so let be
        "-w", $user_password,
        "-j", $self->{app}, # comment
        "-A", # any app can access
        "-U", # allow update

sub _parse_password_from_find_output {
    my ($text) = @_;

    if($text =~ /^ *password: *"([^"]*)"/m)  {
        return $1;
    elsif($text =~ /^ *password: *\$([0-9A-Fa-f]*)/m) {
        return pack("H*", $1);
    elsif($text =~ /^ *password: *$/m)  {
        return "";


=head2 get_password($user_name, $realm)

Reads previously stored password for given user in given app.
If such password can not be found, returns undef.


sub get_password {
    my ($self, $user_name, $realm) = @_;

    my $reply = capturex($self->_make_sys_args(
        "-q", # quiet
        "-a", $user_name,
        "-s", $realm,
        "-D", $self->{group}, # "kind", can be used to match so let be
        "-g", # display the password

    return _parse_password_from_find_output($reply);

=head2 clear_password($user_name, $realm)

Removes given password (if present)

Returns how many passwords actually were removed 


sub clear_password {
    my ($self, $user_name, $realm) = @_;

    my $reply = systemx($self->_make_sys_args(
        "-a", $user_name,
        "-s", $realm,
        "-D", $self->{group}, # "kind", can be used to match so let be


=head2 is_persistent

Returns info, whether this keyring actually saves passwords persistently.

(true in this case)


sub is_persistent {
    my ($self) = @_;
    return 1;

=head1 AUTHOR

Marcin Kasperski

=head1 BUGS

Please report any bugs or feature requests to 
issue tracker at L<>.

=head1 SUPPORT

You can find documentation for this module with the perldoc command.

    perldoc Passwd::Keyring::OSXKeychain

You can also look for information at:


Source code is tracked at:



Copyright 2012 Marcin Kasperski.

This program is free software; you can redistribute it and/or modify it
under the terms of either: the GNU General Public License as published
by the Free Software Foundation; or the Artistic License.

See for more information.


1; # End of Passwd::Keyring::OSXKeychain