File::KeePass(3pm)
π File::KeePass(3pm) π
okay bestie, gather round, because this is the manual page for File::KeePass β like, the actual File::KeePass(3pm) page from your friendly neighbourhood perl docs (User Contributed Perl Documentation section, if you're keeping track π). She's a big one so buckle up, this is part one of three π«Ά
β¨ NAME β¨
File::KeePass β the interface to KeePass V1 and V2 database files, no cap π
πΈ SYNOPSIS πΈ
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);
π¦ DESCRIPTION π¦
File::KeePass gives you access to KeePass version 1 (kdb) and version 2 (kdbx) databases. Both of them, yeah. We contain multitudes π
So here's the tea: version 1 and version 2 databases are wildly different in how they're built, right? But likeβ¦ most of the actual information overlaps, and a whole bunch of the algorithms are basically the same vibe. So File::KeePass does the work of ironing out as many of those differences as it possibly can for you. Iconic behaviour, honestly β¨
File::KeePass gives you nearly raw data access. There are a few little utility methods for messing with groups and entries, but the advanced manipulation stuff? That can easily be layered on top by other modules. We're not gonna gatekeep, we're just not gonna do it ourselves π
And important: File::KeePass is only for reading and writing databases and for keeping passwords scrambled while they're in memory. Programs dealing with UI, or with auto-type features? That's the domain of other modules on CPAN. File::KeePass::Agent is one example π
π METHODS π
newβ takes a hashref or hash of arguments. Returns a brand newFile::KeePassobject. Any named arguments you pass get added to self. Cute, simple, done πload_dbβ takes akdbfilename, a master password, and an optional argument hashref. Returns theFile::KeePassobject on success (and yes, you can call it as a class method π«Ά). Errors die. The resulting database can be accessed via various methods, including$k->groups.
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 get read from the file and then passed along to parse_db. Chain of command, we love to see it π
The password you pass to load_db can be a composite key in any of these forms, bestie:
"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. You can pass it as a filename, or as a scalar reference to the contents of the key file. If you pass a filename, it will be read in. And the key file can contain any of these 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
(yes, it really says hexidecimal in the original, we're not touching that spelling, periodt π)
save_dbβ takes akdbfilename and a master password. Stores out the current groups that are chilling in the object. The writes attempt to write first to$file.new.$epochand are then renamed into the correct location. Sneaky little atomic-ish move, we love her π
β οΈ You will need to unlock the db via $k->unlock before calling this method if the database is currently locked. Don't skip this step, bestie, it will not go well π
The same master password types passed to load_db can be used here too π
parse_dbβ takes a string or a reference to a string containing an encryptedkdbdatabase, a master password, and an optional argument hashref. Returns theFile::KeePassobject on success (can be called as a class method π«Ά). Errors die. The resulting database can be accessed via various methods, including$k->groups.
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});
And yep, the same master password types passed to load_db can be used here as well β¨
parse_headerβ used byparse_db. Reads just the header information. This one's handy as a basic KeePass file check β The returned hash will containversion => 1orversion => 2, depending on which type of header it finds. Can be called as a class method.
my $head = File::KeePass->parse_header($kdb_buffer); # errors die
printf "This is a version %d database\n", $head->{'version'};
gen_dbβ takes a master password. Optionally takes a"groups"arrayref and a"headers"hashref. If groups are not passed, it defaults to using the currently loaded groups. If headers are not passed, a fresh set of headers gets generated based on the groups and the master password. The headers can be passed in to test round trip portability β cute for the paranoid girlies π§
β οΈ 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 π
headerβ returns a hashref representing the combined current header and meta information for the currently loaded database. She contains so much, let's go through it πΈ
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 you're writing a database via either save_db or gen_db, these fields can be set and passed along π Optionally, it's 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. Recycle, reduce, reuse π
clearβ clears any currently loaded database. Poof, gone, memory freed, we love a clean slate πauto_lockβ defaults to true. If true, passwords are automatically hidden when a database gets loaded viaparse_dborload_db.
$k->auto_lock(0); # turn off auto locking
is_lockedβ returns true if the current database is locked. Vibe check on the lock status πlockβ locks the database. This moves all passwords into a protected, in memory, encrypted storage location. Returns1on success. Returns2if the db is already locked. And if a database is loaded viaparse_dborload_dbandauto_lockis true, the newly loaded database will start out locked. Safety first, bestie π‘οΈunlockβ unlocks a previously locked database. You will need to unlock a database before callingsave_dborgen_db. No unlock, no save, that's the rule π
(part 1 done, bestie β more incoming, don't go anywhere π)
π GROUP/ENTRY METHODS π
okay bestie this is the part where we actually touch the data. groups, entries, finding them, deleting them β the whole social life of your database. let's go π¦
πΈ dump_groups
gives you back a cute little simplified string version of whatever database is currently loaded. like a screenshot but for your KeePass vibes.
print $k->dump_groups;
you can optionally hand it a match-argument hashref, and then only the entries that match your criteria come back. picky, but make it fashion π
π groups
returns an arrayref of all the groups in the currently loaded database. and they come back hierarchal, as they should. important heads-up though: groups is literally just handing you a reference to all the data. it makes zero attempts at cleaning anything up. it's giving raw. (find_groups is the one that actually grooms the data, iykyk).
my $g = $k->groups;
the groups will look something like this:
$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"
}],
}];
β¨ add_group
adds a brand new group to the database and returns a reference to it. if there's no database loaded yet, it just starts a fresh one, no drama. you pass a hashref of arguments for the new group β title, icon, expanded, all that. a new random group id gets generated for you. π
there's also an optional group argument. pass one, and your shiny new group gets added under that parent group. nepotism but make it structural.
my $group = $k->add_group({title => 'Foo'});
my $gid = $group->{'id'};
my $group2 = $k->add_group({title => 'Bar', group => $gid});
oh and the group argument's value can also be a reference to a group itself β like the one you'd get back from find_group. flexible queen π
π§ finder_tests
this one is used by find_groups and find_entries. you give it a hashref of arguments, and it gives you back a list of test code refs. the little matchmakers of the whole operation.
{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
π find_groups
takes a hashref of search criteria and returns all the matching groups. you can pass id, title, icon, and level. the search arguments get parsed by finder_tests, obviously.
my @groups = $k->find_groups({title => 'Foo'});
my @all_groups_flattened = $k->find_groups({});
find_groups is also the responsible one β it double-checks that group ids are unique and that all the needed values are actually defined. main character energy with a to-do list. π
π¦ find_group
calls find_groups and returns the first group it finds. and bestie, heads up: it dies if multiple results come up. it's not about that indecisive life.
in scalar context it returns just the group. in list context it returns the group and the arrayref where it's stored β either the root level group or a sub group's group item. πΈ
π
delete_group
passes your arguments to find_group to figure out which group you mean, then deletes it. returns the group that just got deleted. gone but never forgotten π«Ά
π₯ add_entry
adds a new entry to the database and returns a reference to it. there's an optional group argument β and if you don't pass one, the entry lands in the first group in the database. no favourites here. a new entry id gets created if you don't pass one, or if the one you passed conflicts with an existing group. it sorts itself out.
these 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, you can also pass in a binary and a binary_name when creating an entry. those get automatically converted into the hashref of filename/content pairs, cute little glow-up:
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"}
so like, typically, version 1 databases store their Auto-Type info inside the comment. they're also limited to only one key sequence per entry. not very demure of them. File::KeePass 2+ will automatically parse Auto-Type values passed in the entry comment and store them out as the auto_type arrayref. and that arrayref gets serialized right back into the comment section when saving as a version 1 database. version 2 databases have a whole separate storage mechanism for Auto-Type, because they're fancy like that. β¨
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 can be either an existing group id, or a reference to a group β like the one find_group hands back. dealer's choice π
when you're using a version 2 database, these 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'},
β find_entries
takes a hashref of search criteria and returns all matching groups. (yes bestie, the original says groups here, we're keeping it exactly as it is, no editing the source π). you can pass an entry id, title, username, comment, url, active, group_id, group_title, or literally any other entry property. the search arguments get parsed by finder_tests.
my @entries = $k->find_entries({title => 'Something'});
my @all_entries_flattened = $k->find_entries({});
π find_entry
calls find_entries and returns the first entry found. dies if multiple results come up β it does not do ambiguity. in scalar context it returns just the entry. in list context it returns the entry and its group. two for one, slay. π
π delete_entry
passes arguments to find_entry to find the entry you want gone, then deletes it. returns the entry that was just deleted. rip to that entry fr fr π
π§ locked_entry_password
this one lets you access individual passwords for a database that is locked. and yes β it dies if the database is not locked. it only works when it's locked, that's the whole bit. π
β¨ UTILITY METHODS β¨
Okay bestie, these are the general purpose little helpers that do the heavy lifting while kdb databases are being parsed and generated. Think of them as the backstage crew. Not the main characters, but the show does not go on without them. π
nowβ hands you the current localtime datetime stamp. clock check, slay. πdefault_expβ returns the string that represents an entry's default expires time. It uses$self->{'default_exp'}, or if that's not around it falls back (the docs literally say "fails", and honestly same, bestie π) to the string'2999-12-31 23:23:59'.decrypt_rijndael_cbcβ you give it an encrypted string, a key, and anencryption_ivstring. It gives you back a plaintext string. unlocking, but make it cryptography. πencrypt_rijndael_cbcβ the reverse glow-up: takes a plaintext string, a key, and anencryption_ivstring, and returns an encrypted string.decode_base64β loads theMIME::Base64library and decodes whatever string you pass it.encode_base64β loads theMIME::Base64library and encodes whatever string you pass it.unchunksumβ parses and reassembles a buffer, reading in the lengths and the checksums of the chunks. putting the puzzle back together fr fr. π§©decompressβ loads theCompress::Raw::Zliblibrary and inflates the contents.compressβ loads theCompress::Raw::Zliblibrary and deflates the contents.parse_xmlβ loads theXML::Parserlibrary and sets up a basic parser that can call hooks at various events. Without the hooks, it runs similarly toXML::Simple::parse. vibe check incoming:
my $data = $self->parse_xml($buffer, {
top => 'KeePassFile',
force_array => {Group => 1, Entry => 1},
start_handlers => {Group => sub { $level++ }},
end_handlers => {Group => sub { $level-- }},
});
gen_xmlβ generates XML from the data structure you pass in. The output ofparse_xmlcan be passed as is. And bonus: you can drop in hints like__sort__to order the tags of a node, and__attr__to say which items of a node are attributes. icon behavior. πsalsa20β takes a hashref containing asalsa20key string (length 32 or 16), asalsa20iv string (length 8), a number ofsalsa20rounds (8, 12, or 20 β default 20), and an optional data string. The key and iv get used to initialize the salsa20 encryption.
If a data string is passed, that string is salsa20 encrypted and returned.
If no data string is passed, a salsa20 encrypting coderef is returned instead. cute, right? π«
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
salsa20_streamβ takes a hashref that will be passed straight tosalsa20. Then it uses the resulting encoder to generate a more continuous encoded stream. Here's the tea: thesalsa20method encodes in chunks of 64 bytes, so if a string isn't a multiple of 64, some of the xor bytes just sit there unused.salsa20_streammaintains a buffer of xor bytes so none of them get wasted. no byte left behind. π«Ά
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
π OTHER METHODS π
These are the little undercover agents β they start with an underscore, they mind their business, they do the work. π
_parse_v1_header_parse_v1_body_parse_v1_groups_parse_v1_entries_parse_v1_date
These are the utilities used for parsing version 1 type databases.
_parse_v2_header_parse_v2_body_parse_v2_date
And these are the utilities used for parsing version 2 type databases.
_gen_v1_db_gen_v1_header_gen_v1_date
Utilities used to generate version 1 type databases.
_gen_v2_db_gen_v2_header_gen_v2_date
Utilities used to generate version 2 type databases.
_master_keyβ takes the password and the parsed headers, and returns the master key based on the database type. the master key. the main character. π
π ONE LINERS π
(Long one liners)
Here's a version 1 to version 2 converter, and also a version 2 to version 1 converter, because it goes both ways, periodt. Simply change the extension of the two files. Someday we will include a kdb2kdbx utility to do this for you, but for now we're doing it live.
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
Heads up bestie: that pure perl one echoes your password. π
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
π BUGS π
- 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. (and honestly, they will. π) - 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. lowkey iconic for keeping it simple ngl.
π SOURCES π
The 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>
The 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. β¨
π AUTHOR π
Paul Seamons <paul@seamons.com>
π LICENSE π
This module may be distributed under the same terms as Perl itself.
and that's a wrap, bestie β this was File::KeePass(3pm) from perl v5.32.0, dated 2021-01-02. go forth and keep your passwords safe, ily π«ΆπΈ
π¦ where this page comes from
- debian bookworm: libfile-keepass-perl 2.03-1.1 Β· Artistic-1.0-Perl OR GPL-1.0-only
- debian trixie: libfile-keepass-perl 2.03-1.1 Β· Artistic-1.0-Perl OR GPL-1.0-only
- ubuntu jammy: libfile-keepass-perl 2.03-1.1 Β· Artistic-1.0-Perl OR GPL-1.0-only
- ubuntu noble: libfile-keepass-perl 2.03-1.1 Β· Artistic-1.0-Perl OR GPL-1.0-only
- π girlified by deepseek-ai/DeepSeek-V4.1-Flash on : a language model's rewrite of the original, under its terms