# copy constructor
return $_[0]->clone if @_ == 1 && blessed $_[0] && $_[0]->isa($class);
- my $self = bless {}, $class;
+ my $data;
+ $data = shift if is_plain_hashref($_[0]);
+
+ my $self = bless $data // {}, $class;
$self->init(@_);
- $self->_set_nonlazy_attributes if empty $self;
+ $self->_set_nonlazy_attributes if !$data;
return $self;
}
# HEADERS
has 'headers.comment' => '', coerce => \&to_string;
-has 'headers.cipher_id' => CIPHER_UUID_CHACHA20, coerce => \&to_uuid;
+has 'headers.cipher_id' => sub { $_[0]->version < KDBX_VERSION_4_0 ? CIPHER_UUID_AES256 : CIPHER_UUID_CHACHA20 },
+ coerce => \&to_uuid;
has 'headers.compression_flags' => COMPRESSION_GZIP, coerce => \&to_compression_constant;
has 'headers.master_seed' => sub { random_bytes(32) }, coerce => \&to_string;
-has 'headers.encryption_iv' => sub { random_bytes(16) }, coerce => \&to_string;
+has 'headers.encryption_iv' => sub { random_bytes($_[0]->version < KDBX_VERSION_4_0 ? 16 : 12) },
+ coerce => \&to_string;
has 'headers.stream_start_bytes' => sub { random_bytes(32) }, coerce => \&to_string;
has 'headers.kdf_parameters' => sub {
+{
$kdbx->add_entry($entry, %options);
$kdbx->add_entry(%entry_attributes, %options);
-Add a entry to a database. This is equivalent to identifying a parent group and calling
+Add an entry to a database. This is equivalent to identifying a parent group and calling
L<File::KDBX::Group/add_entry> on the parent group, forwarding the arguments. Available options:
=for :list
sub lock {
my $self = shift;
- $self->_safe and return $self;
-
+ # Find things to lock:
my @strings;
-
$self->entries(history => 1)->each(sub {
- push @strings, grep { $_->{protect} } values %{$_->strings}, values %{$_->binaries};
+ my $strings = $_->strings;
+ for my $string_key (keys %$strings) {
+ my $string = $strings->{$string_key};
+ push @strings, $string if $string->{protect} // $self->memory_protection($string_key);
+ }
+ push @strings, grep { $_->{protect} } values %{$_->binaries};
});
+ return $self if !@strings; # nothing to do
- $self->_safe(File::KDBX::Safe->new(\@strings));
-
+ if (my $safe = $self->_safe) {
+ $safe->add(\@strings);
+ }
+ else {
+ $self->_safe(File::KDBX::Safe->new(\@strings));
+ }
return $self;
}
limit: -1)
* C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: value of
L</history_max_size>, no limit: -1)
-* C<max_age> - Maximum age (in days) of historical entries to keep (default: 365, no limit: -1)
+* C<max_age> - Maximum age (in days) of historical entries to keep (default: value of
+ L</maintenance_history_days>, no limit: -1)
=cut
* L</transform_seed>
Randomizing these values has no effect on a loaded database. These are only used when a database is dumped.
-You normally do not need to call this method explicitly because the dumper does it explicitly by default.
+You normally do not need to call this method explicitly because the dumper does it for you by default.
=cut
sub randomize_seeds {
my $self = shift;
- $self->encryption_iv(random_bytes(16));
+ my $iv_size = 16;
+ $iv_size = $self->cipher(key => "\0" x 32)->iv_size if KDBX_VERSION_4_0 <= $self->version;
+ $self->encryption_iv(random_bytes($iv_size));
$self->inner_random_stream_key(random_bytes(64));
$self->master_seed(random_bytes(32));
$self->stream_start_bytes(random_bytes(32));
my %args = @_ % 2 == 1 ? (params => shift, @_) : @_;
my $params = $args{params};
- my $compat = $args{compatible} // 1;
$params //= $self->kdf_parameters;
$params = {%{$params || {}}};
sub transform_seed {
my $self = shift;
+ my $param = KDF_PARAM_AES_SEED; # Short cut: Argon2 uses the same parameter name ("S")
$self->headers->{+HEADER_TRANSFORM_SEED} =
- $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_SEED} = shift if @_;
+ $self->headers->{+HEADER_KDF_PARAMETERS}{$param} = shift if @_;
$self->headers->{+HEADER_TRANSFORM_SEED} =
- $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_SEED} //= random_bytes(32);
+ $self->headers->{+HEADER_KDF_PARAMETERS}{$param} //= random_bytes(32);
}
sub transform_rounds {
my $self = shift;
+ require File::KDBX::KDF;
+ my $info = $File::KDBX::KDF::ROUNDS_INFO{$self->kdf_parameters->{+KDF_PARAM_UUID} // ''} //
+ $File::KDBX::KDF::DEFAULT_ROUNDS_INFO;
$self->headers->{+HEADER_TRANSFORM_ROUNDS} =
- $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_ROUNDS} = shift if @_;
+ $self->headers->{+HEADER_KDF_PARAMETERS}{$info->{p}} = shift if @_;
$self->headers->{+HEADER_TRANSFORM_ROUNDS} =
- $self->headers->{+HEADER_KDF_PARAMETERS}{+KDF_PARAM_AES_ROUNDS} //= 100_000;
+ $self->headers->{+HEADER_KDF_PARAMETERS}{$info->{p}} //= $info->{d};
}
=method cipher
my $self = shift;
my %args = @_;
- $args{uuid} //= $self->headers->{+HEADER_CIPHER_ID};
- $args{iv} //= $self->headers->{+HEADER_ENCRYPTION_IV};
+ $args{uuid} //= $self->cipher_id;
+ $args{iv} //= $self->encryption_iv;
require File::KDBX::Cipher;
return File::KDBX::Cipher->new(%args);
=attr comment
-A text string associated with the database. Often unset.
+A text string associated with the database stored unencrypted in the file header. Often unset.
=attr cipher_id
=attr transform_rounds
The number of rounds or iterations used in the key derivation function. Increasing this number makes loading
-and saving the database slower by design in order to make dictionary and brute force attacks more costly.
+and saving the database slower in order to make dictionary and brute force attacks more costly.
=attr encryption_iv
);
# Save the database to the filesystem
- $kdbx->dump_file('passwords.kdbx', 'M@st3rP@ssw0rd!');
+ $kdbx->dump_file('passwords.kdbx', 'masterpw changeme');
# Load the database from the filesystem into a new database instance
- my $kdbx2 = File::KDBX->load_file('passwords.kdbx', 'M@st3rP@ssw0rd!');
+ my $kdbx2 = File::KDBX->load_file('passwords.kdbx', 'masterpw changeme');
# Iterate over database entries, print entry titles
- $kdbx2->entries->each(sub {
- my ($entry) = @_;
+ $kdbx2->entries->each(sub($entry, @) {
say 'Entry: ', $entry->title;
});
my $kdbx = File::KDBX->load_file('mypasswords.kdbx', 'master password CHANGEME');
$kdbx->unlock; # cause $entry->password below to be defined
- $kdbx->entries->each(sub {
- my ($entry) = @_;
+ $kdbx->entries->each(sub($entry, @) {
say 'Found password for: ', $entry->title;
say ' Username: ', $entry->username;
say ' Password: ', $entry->password;
generate strong keys.
The KDBX format allows for the key derivation function to be tuned. The idea is that you want each single
-brute-foce attempt to be expensive (in terms of time, CPU usage or memory usage), so that making a lot of
+brute-force attempt to be expensive (in terms of time, CPU usage or memory usage), so that making a lot of
attempts (which would be required if you have a strong master key) gets I<really> expensive.
How expensive you want to make each attempt is up to you and can depend on the application.
It helps to read it right-to-left, like "usage_count is greater than or equal to 5".
-If you find the disambiguating structures to be distracting or confusing, you can also the
+If you find the disambiguating structures to be distracting or confusing, you can also use the
L<File::KDBX::Util/simple_expression_query> function as a more intuitive alternative. The following example is
equivalent to the previous:
Note: L<File::KDBX::Constants/ICON_SMARTPHONE> is just a constant from L<File::KDBX::Constants>. It isn't
special to this example or to queries generally. We could have just used a literal number.
-The important thing to notice here is how we wrapped the condition in another arrayref with a single key-value
+The important thing to notice here is how we wrapped the condition in another hashref with a single key-value
pair where the key is the name of an operator and the value is the thing to match against. The supported
operators are: