File::KeePass(3pm) | User Contributed Perl Documentation | File::KeePass(3pm) |
File::KeePass - Interface to KeePass V1 and V2 database files
use File::KeePass; use Data::Dumper qw(Dumper); my $k = File::KeePass->new; # read a version 1 or version 2 database $k->load_db($file, $master_pass); # errors die print Dumper $k->header; print Dumper $k->groups; # passwords are locked $k->unlock; print Dumper $k->groups; # passwords are now visible $k->clear; # delete current db from memory my $group = $k->add_group({ title => 'Foo', }); # root level group my $gid = $group->{'id'}; my $group = $k->find_group({id => $gid}); # OR my $group = $k->find_group({title => 'Foo'}); my $group2 = $k->add_group({ title => 'Bar', group => $gid, # OR group => $group, }); # nested group my $e = $k->add_entry({ title => 'Something', username => 'someuser', password => 'somepass', group => $gid, # OR group => $group, }); my $eid = $e->{'id'}; my $e = $k->find_entry({id => $eid}); # OR my $e = $k->find_entry({title => 'Something'}); $k->lock; print $e->{'password'}; # eq undef print $k->locked_entry_password($e); # eq 'somepass' $k->unlock; print $e->{'password'}; # eq 'somepass' # save out a version 1 database $k->save_db("/some/file/location.kdb", $master_pass); # save out a version 2 database $k->save_db("/some/file/location.kdbx", $master_pass); # save out a version 1 database using a password and key file $k->save_db("/some/file/location.kdb", [$master_pass, $key_filename]); # read database from a file $k->parse_db($pass_db_string, $pass); # generate a keepass version 1 database string my $pass_db_string = $k->gen_db($pass); # generate a keepass version 2 database string my $pass_db_string = $k->gen_db($pass);
File::KeePass gives access to KeePass version 1 (kdb) and version 2 (kdbx) databases.
The version 1 and version 2 databases are very different in construction, but the majority of information overlaps and many algorithms are similar. File::KeePass attempts to iron out as many of the differences.
File::KeePass gives nearly raw data access. There are a few utility methods for manipulating groups and entries. More advanced manipulation can easily be layered on top by other modules.
File::KeePass is only used for reading and writing databases and for keeping passwords scrambled while in memory. Programs dealing with UI or using of auto-type features are the domain of other modules on CPAN. File::KeePass::Agent is one example.
my $k = File::KeePass->new; $k->load_db($file, $pwd); my $k = File::KeePass->load_db($file, $pwd); my $k = File::KeePass->load_db($file, $pwd, {auto_lock => 0});
The contents are read from file and passed to parse_db.
The password passed to load_db may be a composite key in any of the following forms:
"password" # password only ["password"] # same ["password", "keyfilename"] # password and key file [undef, "keyfilename"] # key file only ["password", \"keycontent"] # password and reference to key file content [undef, \"keycontent"] # reference to key file content only
The key file is optional. It may be passed as a filename, or as a scalar reference to the contents of the key file. If a filename is passed it will be read in. The key file can contain any of the following three types:
length 32 # treated as raw key length 64 # must be 64 hexidecimal characters any-other-length # a SHA256 sum will be taken of the data
You will need to unlock the db via $k->unlock before calling this method if the database is currently locked.
The same master password types passed to load_db can be used here.
my $k = File::KeePass->new; $k->parse_db($loaded_kdb, $pwd); my $k = File::KeePass->parse_db($kdb_buffer, $pwd); my $k = File::KeePass->parse_db($kdb_buffer, $pwd, {auto_lock => 0});
The same master password types passed to load_db can be used here.
my $head = File::KeePass->parse_header($kdb_buffer); # errors die printf "This is a version %d database\n", $head->{'version'};
You will need to unlock the db via $k->unlock before calling this method if the database is currently locked.
The same master password types passed to load_db can be used here.
The following fields are present in both version 1 and version 2 style databases (from the header):
enc_iv => "123456789123456", # rand enc_type => "rijndael", header_size => 222, seed_key => "1234567890123456", # rand (32 bytes on v2) seed_rand => "12345678901234567890123456789012", # rand rounds => 6000, sig1 => "2594363651", sig2 => "3041655655", # indicates db version ver => 196608, version => 1, # or 2
The following keys will be present after the reading of a version 2 database (from the header):
cipher => "aes", compression => 1, protected_stream => "salsa20", protected_stream_key => "12345678901234567890123456789012", # rand start_bytes => "12345678901234567890123456789012", # rand
Additionally, items parsed from the Meta section of a version 2 database will be added. The following are the available fields.
color => "#4FFF00", custom_data => {key1 => "val1"}, database_description => "database desc", database_description_changed => "2012-08-17 00:30:56", database_name => "database name", database_name_changed => "2012-08-17 00:30:56", default_user_name => "", default_user_name_changed => "2012-08-17 00:30:34", entry_templates_group => "VL5nOpzlFUevGhqL71/OTA==", entry_templates_group_changed => "2012-08-21 14:05:32", generator => "KeePass", history_max_items => 10, history_max_size => 6291456, # bytes last_selected_group => "SUgL30QQqUK3tOWuNKUYJA==", last_top_visible_group => "dC1sQ1NO80W7klmRhfEUVw==", maintenance_history_days => 365, master_key_change_force => -1, master_key_change_rec => -1, master_key_changed => "2012-08-17 00:30:34", protect_notes => 0, protect_password => 1, protect_title => 0, protect_url => 0, protect_username => 0 recycle_bin_changed => "2012-08-17 00:30:34", recycle_bin_enabled => 1, recycle_bin_uuid => "SUgL30QQqUK3tOWuNKUYJA=="
When writing a database via either save_db or gen_db, these fields can be set and passed along. Optionally, it is possible to pass along a key called reuse_header to let calls to save_db and gen_db automatically use the contents of the previous header.
$k->auto_lock(0); # turn off auto locking
print $k->dump_groups;
You can optionally pass a match argument hashref. Only entries matching the criteria will be returned.
my $g = $k->groups;
Groups will look similar to the following:
$g = [{ expanded => 0, icon => 0, id => 234234234, # under v1 this is a 32 bit int, under v2 it is a 16 char id title => 'Foo', level => 0, entries => [{ accessed => "2010-06-24 15:09:19", comment => "", created => "2010-06-24 15:09:19", expires => "2999-12-31 23:23:59", icon => 0, modified => "2010-06-24 15:09:19", title => "Something", password => 'somepass', # will be hidden if the database is locked url => "", username => "someuser", id => "0a55ac30af68149f", # v1 is any hex char, v2 is any 16 char }], groups => [{ expanded => 0, icon => 0, id => 994414667, level => 1, title => "Bar" }], }];
my $group = $k->add_group({title => 'Foo'}); my $gid = $group->{'id'}; my $group2 = $k->add_group({title => 'Bar', group => $gid});
The group argument's value may also be a reference to a group - such as that returned by find_group.
{title => 'Foo'} # will check if title equals Foo {'title !' => 'Foo'} # will check if title does not equal Foo {'title =~' => qr{^Foo$}} # will check if title does matches the regex {'title !~' => qr{^Foo$}} # will check if title does not match the regex
my @groups = $k->find_groups({title => 'Foo'}); my @all_groups_flattened = $k->find_groups({});
The find_groups method also checks to make sure group ids are unique and that all needed values are defined.
The following fields can be passed to both v1 and v2 databases.
accessed => "2010-06-24 15:09:19", # last accessed date auto_type => [{keys => "{USERNAME}{TAB}{PASSWORD}{ENTER}", window => "Foo*"}], binary => {foo => 'content'}; # hashref of filename/content pairs comment => "", # a comment for the system - auto-type info is normally here created => "2010-06-24 15:09:19", # entry creation date expires => "2999-12-31 23:23:59", # date entry expires icon => 0, # icon number for use with agents modified => "2010-06-24 15:09:19", # last modified title => "Something", password => 'somepass', # will be hidden if the database is locked url => "http://", username => "someuser", id => "0a55ac30af68149f", # auto generated if needed, v1 is any hex char, v2 is any 16 char group => $gid, # which group to add the entry to
For compatibility with earlier versions of File::KeePass, it is possible to pass in a binary and binary_name when creating an entry. They will be automatically converted to the hashref of filename/content pairs
binary_name => "foo", # description of the stored binary - typically a filename binary => "content", # raw data to be stored in the system - typically a file # results in binary => {"foo" => "content"}
Typically, version 1 databases store their Auto-Type information inside of the comment. They are also limited to having only one key sequence per entry. File::KeePass 2+ will automatically parse Auto-Type values passed in the entry comment and store them out as the auto_type arrayref. This arrayref is serialized back into the comment section when saving as a version 1 database. Version 2 databases have a separate storage mechanism for Auto-Type.
If you passed in: comment => " Auto-Type: {USERNAME}{TAB}{PASSWORD}{ENTER} Auto-Type-Window: Foo* Auto-Type-Window: Bar* ", Will result in: auto_type => [{ keys => "{USERNAME}{TAB}{PASSWORD}{ENTER}", window => "Foo*" }, { keys => "{USERNAME}{TAB}{PASSWORD}{ENTER}", window => "Bar*" }],
The group argument value may be either an existing group id, or a reference to a group - such as that returned by find_group.
When using a version 2 database, the following additional fields are also available:
expires_enabled => 0, location_changed => "2012-08-05 12:12:12", usage_count => 0, tags => {}, background_color => '#ff0000', foreground_color => '#ffffff', custom_icon_uuid => '234242342aa', history => [], # arrayref of previous entry changes override_url => $node->{'OverrideURL'}, auto_type_enabled => 1, auto_type_munge => 0, # whether or not to attempt two channel auto typing protected => {password => 1}, # indicating which strings were/should be salsa20 protected strings => {'other key' => 'other value'},
my @entries = $k->find_entries({title => 'Something'}); my @all_entries_flattened = $k->find_entries({});
The following methods are general purpose methods used during the parsing and generating of kdb databases.
my $data = $self->parse_xml($buffer, { top => 'KeePassFile', force_array => {Group => 1, Entry => 1}, start_handlers => {Group => sub { $level++ }}, end_handlers => {Group => sub { $level-- }}, });
If a data string is passed, the string is salsa20 encrypted and returned.
If no data string is passed a salsa20 encrypting coderef is returned.
my $encoded = $self->salsa20({key => $key, iv => $iv, data => $data}); my $uncoded = $self->salsa20({key => $key, iv => $iv, data => $encoded}); # $data eq $uncoded my $encoder = $self->salsa20({key => $key, iv => $Iv}); # no data my $encoded = $encoder->($data); my $part2 = $encoder->($more_data); # continues from previous state
my $encoder = $self->salsa20_stream({key => $key, iv => $Iv}); # no data my $encoded = $encoder->("1234"); # calls salsa20->() my $part2 = $encoder->("1234"); # uses the same pad until 64 bytes are used
(Long one liners)
Here is a version 1 to version 2, or version 2 to version 1 converter. Simply change the extension of the two files. Someday we will include a kdb2kdbx utility to do this for you.
perl -MFile::KeePass -e 'use IO::Prompt; $p="".prompt("Pass:",-e=>"*",-tty); File::KeePass->load_db(+shift,$p,{auto_lock=>0})->save_db(+shift,$p)' ~/test.kdb ~/test.kdbx # OR using graphical prompt perl -MFile::KeePass -e 'chop($p=`zenity --password`); File::KeePass->load_db(+shift,$p,{auto_lock=>0})->save_db(+shift,$p)' ~/test.kdbx ~/test.kdb # OR using pure perl (but echoes password) perl -MFile::KeePass -e 'print "Pass:"; chop($p=<STDIN>); File::KeePass->load_db(+shift,$p,{auto_lock=>0})->save_db(+shift,$p)' ~/test.kdbx ~/test.kdb
Dumping the XML from a version 2 database.
perl -MFile::KeePass -e 'chop($p=`zenity --password`); print File::KeePass->load_db(+shift,$p,{keep_xml=>1})->{xml_in},"\n"' ~/test.kdbx
Outlining group information.
perl -MFile::KeePass -e 'chop($p=`zenity --password`); print File::KeePass->load_db(+shift,$p)->dump_groups' ~/test.kdbx
Dumping header information
perl -MFile::KeePass -MData::Dumper -e 'chop($p=`zenity --password`); print Dumper +File::KeePass->load_db(+shift,$p)->header' ~/test.kdbx
Only Rijndael is supported when using v1 databases.
This module makes no attempt to act as a password agent. That is the job of File::KeePass::Agent. This isn't really a bug but some people will think it is.
Groups and entries don't have true objects associated with them. At the moment this is by design. The data is kept as plain boring data.
Knowledge about the algorithms necessary to decode a KeePass DB v1 format was gleaned from the source code of keepassx-0.4.3. That source code is published under the GPL2 license. KeePassX 0.4.3 bears the copyright of
Copyright (C) 2005-2008 Tarek Saidi <tarek.saidi@arcor.de> Copyright (C) 2007-2009 Felix Geyer <debfx-keepassx {at} fobos.de>
Knowledge about the algorithms necessary to decode a KeePass DB v2 format was gleaned from the source code of keepassx-2.0-alpha1. That source code is published under the GPL2 or GPL3 license. KeePassX 2.0-alpha1 bears the copyright of
Copyright: 2010-2012, Felix Geyer <debfx@fobos.de> 2011-2012, Florian Geyer <blueice@fobos.de>
The salsa20 algorithm is based on http://cr.yp.to/snuffle/salsa20/regs/salsa20.c which is listed as Public domain (D. J. Bernstein).
The ordering and layering of encryption/decryption algorithms of File::KeePass are of derivative nature from KeePassX and could not have been created without this insight - though the perl code is from scratch.
Paul Seamons <paul@seamons.com>
This module may be distributed under the same terms as Perl itself.
2021-01-02 | perl v5.32.0 |