1 package File
::KDBX
::Entry
;
2 # ABSTRACT: A KDBX database entry
7 use Crypt
::Misc
0.029 qw(encode_b32r decode_b64);
8 use Devel
::GlobalDestruction
;
10 use File
::KDBX
::Constants
qw(:history :icon);
11 use File
::KDBX
::Error
;
12 use File
::KDBX
::Util
qw(:function :uri generate_uuid load_optional);
13 use Hash
::Util
::FieldHash
;
14 use List
::Util
qw(sum0);
15 use Ref
::Util
qw(is_plain_hashref);
16 use Scalar
::Util
qw(looks_like_number);
17 use Storable
qw(dclone);
22 use parent
'File::KDBX::Object';
24 our $VERSION = '999.999'; # VERSION
26 my $PLACEHOLDER_MAX_DEPTH = 10;
28 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
30 sub _parent_container
{ 'entries' }
34 128-bit UUID identifying the entry within the database
.
38 Integer representing a
default icon
. See L
<File
::KDBX
::Constants
/":icon"> for valid
values.
40 =attr custom_icon_uuid
42 128-bit UUID identifying a custom icon within the database
.
44 =attr foreground_color
46 Text color represented as a string of the form C
<#000000>.
48 =attr background_color
50 Background color represented as a string of the form C
<#FFFFFF>.
58 Text string with arbitrary tags which can be used to build a taxonomy
.
66 data_transfer_obfuscation
=> 0,
67 default_sequence
=> '{USERNAME}{TAB}{PASSWORD}{ENTER}',
70 window
=> 'My Bank - Mozilla Firefox',
71 keystroke_sequence
=> '{PASSWORD}{ENTER}',
76 =attr previous_parent_group
78 128-bit UUID identifying a group within the database
.
82 Boolean indicating whether the entry password should be tested
for weakness
and show up
in reports
.
86 Hash with entry strings
, including the standard strings as well as any custom ones
.
89 # Every entry has these five strings:
90 Title
=> { value
=> 'Example Entry' },
91 UserName
=> { value
=> 'jdoe' },
92 Password
=> { value
=> 's3cr3t', protect
=> true
},
93 URL
=> { value
=> 'https://example.com' }
94 Notes
=> { value
=> '' },
95 # May also have custom strings:
96 MySystem
=> { value
=> 'The mainframe' },
101 Files
or attachments
.
105 A set of key-value pairs used to store arbitrary data
, usually used by software to keep track of
state rather
106 than by end users
(who typically work with the strings
and binaries
).
110 Array of historical entries
. Historical entries are prior versions of the same entry so they all share the
111 same UUID with the current entry
.
113 =attr last_modification_time
115 Date
and time when the entry was
last modified
.
119 Date
and time when the entry was created
.
121 =attr last_access_time
123 Date
and time when the entry was
last accessed
.
127 Date
and time when the entry expired
or will expire
.
131 Boolean value indicating whether
or not an entry
is expired
.
135 The number of
times an entry
has been used
, which typically means how many
times the C
<Password
> string
has
138 =attr location_changed
140 Date
and time when the entry was
last moved to a different group
.
144 Alias
for the C
<Notes
> string value
.
148 Alias
for the C
<Password
> string value
.
152 Alias
for the C
<Title
> string value
.
156 Alias
for the C
<URL
> string value
.
160 Aliases
for the C
<UserName
> string value
.
166 if (@_ || !defined $self->{uuid
}) {
167 my %args = @_ % 2 == 1 ? (uuid
=> shift, @_) : @_;
168 my $old_uuid = $self->{uuid
};
169 my $uuid = $self->{uuid
} = delete $args{uuid
} // generate_uuid
;
170 for my $entry (@{$self->history}) {
171 $entry->{uuid
} = $uuid;
173 # if (defined $old_uuid and my $kdbx = $KDBX{$self}) {
174 # $kdbx->_update_entry_uuid($old_uuid, $uuid, $self);
180 my @ATTRS = qw(uuid custom_data history);
182 # uuid => sub { generate_uuid(printable => 1) },
183 icon_id
=> ICON_PASSWORD
,
184 custom_icon_uuid
=> undef,
185 foreground_color
=> '',
186 background_color
=> '',
189 auto_type
=> sub { +{} },
190 previous_parent_group
=> undef,
191 quality_check
=> true
,
192 strings
=> sub { +{} },
193 binaries
=> sub { +{} },
194 # custom_data => sub { +{} },
195 # history => sub { +[] },
198 last_modification_time
=> sub { gmtime },
199 creation_time
=> sub { gmtime },
200 last_access_time
=> sub { gmtime },
201 expiry_time
=> sub { gmtime },
204 location_changed
=> sub { gmtime },
206 my %ATTRS_STRINGS = (
208 username
=> 'UserName',
209 password
=> 'Password',
214 while (my ($attr, $default) = each %ATTRS) {
215 no strict
'refs'; ## no critic (ProhibitNoStrict)
218 $self->{$attr} = shift if @_;
219 $self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
222 while (my ($attr, $default) = each %ATTRS_TIMES) {
223 no strict
'refs'; ## no critic (ProhibitNoStrict)
226 $self->{times} //= {};
227 $self->{times}{$attr} = shift if @_;
228 $self->{times}{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
231 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
232 no strict
'refs'; ## no critic (ProhibitNoStrict)
233 *{$attr} = sub { shift-
>string_value($string_key, @_) };
234 *{"expanded_${attr}"} = sub { shift-
>expanded_string_value($string_key, @_) };
237 sub _set_default_attributes
{
239 $self->$_ for @ATTRS, keys %ATTRS, keys %ATTRS_TIMES, keys %ATTRS_STRINGS;
246 while (my ($key, $val) = each %args) {
247 if (my $method = $self->can($key)) {
248 $self->$method($val);
251 $self->string($key => $val);
258 ##############################################################################
262 \
%string = $entry->string($string_key);
264 $entry->string($string_key, \
%string);
265 $entry->string($string_key, %attributes);
266 $entry->string($string_key, $value); # same as: value => $value
268 Get
or set a string
. Every string
has a unique
(to the entry
) key
and flags
and so are returned as a hash
269 structure
. For example
:
273 protect
=> true
, # optional
276 Every string should have a value
(but might be C
<undef> due to memory protection
) and these optional flags
280 * C<protect> - Whether or not the string value should be memory-protected.
286 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
287 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
289 if (!defined $args{key
} && !defined $args{value
}) {
290 my %standard = (value
=> 1, protect
=> 1);
291 my @other_keys = grep { !$standard{$_} } keys %args;
292 if (@other_keys == 1) {
293 my $key = $args{key
} = $other_keys[0];
294 $args{value
} = delete $args{$key};
298 my $key = delete $args{key
} or throw
'Must provide a string key to access';
300 return $self->{strings
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
302 while (my ($field, $value) = each %args) {
303 $self->{strings
}{$key}{$field} = $value;
306 # Auto-vivify the standard strings.
307 if ($STANDARD_STRINGS{$key}) {
308 return $self->{strings
}{$key} //= {value
=> '', $self->_protect($key) ? (protect
=> true
) : ()};
310 return $self->{strings
}{$key};
313 ### Get whether or not a standard string is configured to be protected
317 return false
if !$STANDARD_STRINGS{$key};
318 if (my $kdbx = eval { $self->kdbx }) {
319 my $protect = $kdbx->memory_protection($key);
320 return $protect if defined $protect;
322 return $key eq 'Password';
327 $string = $entry->string_value;
329 Access a string value directly
. Returns C
<undef> if the string
is not set
.
335 my $string = $self->string(@_) // return undef;
336 return $string->{value
};
339 =method expanded_string_value
341 $string = $entry->expanded_string_value;
343 Same as L
</string_value
> but will substitute placeholders
and resolve field references
. Any placeholders that
344 do not expand to
values are left as-is
.
346 See L
</Placeholders
>.
348 Some placeholders
(notably field references
) require the entry be associated with a database
and will throw an
349 error
if there
is no association
.
353 sub _expand_placeholder
{
355 my $placeholder = shift;
360 my $placeholder_key = $placeholder;
362 $placeholder_key = $File::KDBX
::PLACEHOLDERS
{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
365 return if !defined $File::KDBX
::PLACEHOLDERS
{$placeholder_key};
367 my $local_key = join('/', Hash
::Util
::FieldHash
::id
($self), $placeholder_key);
368 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
369 my $handler = $File::KDBX
::PLACEHOLDERS
{$placeholder_key} or next;
370 memoize recurse_limit
($handler, $PLACEHOLDER_MAX_DEPTH, sub {
371 alert
"Detected deep recursion while expanding $placeholder placeholder",
372 placeholder
=> $placeholder;
377 return $handler->($self, $arg, $placeholder);
384 my $expand = memoize
$self->can('_expand_placeholder'), $self;
386 # placeholders (including field references):
387 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
389 # environment variables (alt syntax):
390 my $vars = join('|', map { quotemeta($_) } keys %ENV);
391 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
396 sub expanded_string_value
{
398 my $str = $self->string_value(@_) // return undef;
399 return $self->_expand_string($str);
402 =method other_strings
404 $other = $entry->other_strings;
405 $other = $entry->other_strings($delimiter);
407 Get a concatenation of all non-standard string
values. The
default delimiter
is a newline
. This
is is useful
408 for executing queries to search
for entities based on the contents of these other strings
(if any
).
414 my $delim = shift // "\n";
416 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
417 return join($delim, @strings);
422 my $string = $self->string(@_);
423 return defined $string->{value
} ? $string->{value
} : $self->kdbx->peek($string);
426 sub password_peek
{ $_[0]->string_peek('Password') }
428 ##############################################################################
432 my $key = shift or throw
'Must provide a binary key to access';
434 my $arg = @_ == 1 ? shift : undef;
436 @args{keys %$arg} = values %$arg if ref $arg eq 'HASH';
437 $args{value
} = $arg if !ref $arg;
438 while (my ($field, $value) = each %args) {
439 $self->{binaries
}{$key}{$field} = $value;
442 my $binary = $self->{binaries
}{$key} //= {value
=> ''};
443 if (defined (my $ref = $binary->{ref})) {
444 $binary = $self->{binaries
}{$key} = dclone
($self->kdbx->binaries->{$ref});
449 sub binary_novivify
{
451 my $binary_key = shift;
452 return if !$self->{binaries
}{$binary_key} && !@_;
453 return $self->binary($binary_key, @_);
458 my $binary = $self->binary_novivify(@_) // return undef;
459 return $binary->{value
};
462 sub auto_type_enabled
{
467 ##############################################################################
471 $otp = $entry->hmac_otp(%options);
473 Generate an HMAC-based one-time password
, or C
<undef> if HOTP
is not configured
for the entry
. The entry
's
474 strings generally must first be unprotected, just like when accessing the password. Valid options are:
477 * C<counter> - Specify the counter value
479 To configure HOTP, see L</"One-time Passwords">.
485 load_optional('Pass
::OTP
');
487 my %params = ($self->_hotp_params, @_);
488 return if !defined $params{type} || !defined $params{secret};
490 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
493 my $otp = eval {Pass::OTP::otp(%params, @_) };
495 throw 'Unable to generate HOTP
', error => $err;
498 $self->_hotp_increment_counter($params{counter});
505 $otp = $entry->time_otp(%options);
507 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
508 strings generally must first be unprotected
, just like
when accessing the password
. Valid options are
:
511 * C<now> - Specify the value for determining the time-step counter
513 To configure TOTP, see L</"One-time Passwords">.
519 load_optional
('Pass::OTP');
521 my %params = ($self->_totp_params, @_);
522 return if !defined $params{type
} || !defined $params{secret
};
524 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
527 my $otp = eval {Pass
::OTP
::otp
(%params, @_) };
529 throw
'Unable to generate TOTP', error
=> $err;
539 $uri_string = $entry->hmac_otp_uri;
540 $uri_string = $entry->time_otp_uri;
542 Get a HOTP
or TOTP otpauth URI
for the entry
, if available
.
544 To configure OTP
, see L
</"One-time Passwords">.
548 sub hmac_otp_uri
{ $_[0]->_otp_uri($_[0]->_hotp_params) }
549 sub time_otp_uri
{ $_[0]->_otp_uri($_[0]->_totp_params) }
555 return if 4 != grep { defined } @params{qw(type secret issuer account)};
556 return if $params{type
} !~ /^[ht]otp$/i;
558 my $label = delete $params{label
};
559 $params{$_} = uri_escape_utf8
($params{$_}) for keys %params;
561 my $type = lc($params{type
});
562 my $issuer = $params{issuer
};
563 my $account = $params{account
};
565 $label //= "$issuer:$account";
567 my $secret = $params{secret
};
568 $secret = uc(encode_b32r
($secret)) if !$params{base32
};
570 delete $params{algorithm
} if defined $params{algorithm
} && $params{algorithm
} eq 'sha1';
571 delete $params{period
} if defined $params{period
} && $params{period
} == 30;
572 delete $params{digits
} if defined $params{digits
} && $params{digits
} == 6;
573 delete $params{counter
} if defined $params{counter
} && $params{counter
} == 0;
575 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
577 if (defined $params{encoder
}) {
578 $uri .= "&encoder=$params{encoder}";
581 $uri .= '&algorithm=' . uc($params{algorithm
}) if defined $params{algorithm
};
582 $uri .= "&digits=$params{digits}" if defined $params{digits
};
583 $uri .= "&counter=$params{counter}" if defined $params{counter
};
584 $uri .= "&period=$params{period}" if defined $params{period
};
594 issuer
=> $self->title || 'KDBX',
595 account
=> $self->username || 'none',
597 counter
=> $self->string_value('HmacOtp-Counter') // 0,
598 $self->_otp_secret_params('Hmac'),
600 return %params if $params{secret
};
602 my %otp_params = $self->_otp_params;
603 return () if !$otp_params{secret
} || $otp_params{type
} ne 'hotp';
605 # $otp_params{counter} = 0
607 return (%params, %otp_params);
614 'HMAC-SHA-1' => 'sha1',
615 'HMAC-SHA-256' => 'sha256',
616 'HMAC-SHA-512' => 'sha512',
620 issuer
=> $self->title || 'KDBX',
621 account
=> $self->username || 'none',
622 digits
=> $self->string_value('TimeOtp-Length') // 6,
623 algorithm
=> $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
624 period
=> $self->string_value('TimeOtp-Period') // 30,
625 $self->_otp_secret_params('Time'),
627 return %params if $params{secret
};
629 my %otp_params = $self->_otp_params;
630 return () if !$otp_params{secret
} || $otp_params{type
} ne 'totp';
632 return (%params, %otp_params);
638 load_optional
('Pass::OTP::URI');
640 my $uri = $self->string_value('otp') || '';
642 %params = Pass
::OTP
::URI
::parse
($uri) if $uri =~ m!^otpauth://!;
643 return () if !$params{secret
} || !$params{type
};
645 if (($params{encoder
} // '') eq 'steam') {
647 $params{chars
} = '23456789BCDFGHJKMNPQRTVWXY';
650 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
651 my ($issuer, $user) = split(':', $params{label
} // ':', 2);
652 $params{issuer
} //= uri_unescape_utf8
($issuer);
653 $params{account
} //= uri_unescape_utf8
($user);
655 $params{algorithm
} = lc($params{algorithm
}) if $params{algorithm
};
656 $params{counter
} = $self->string_value('HmacOtp-Counter') if $params{type
} eq 'hotp';
661 sub _otp_secret_params
{
663 my $type = shift // return ();
665 my $secret_txt = $self->string_value("${type}Otp-Secret");
666 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
667 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
668 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
670 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
671 return () if $count == 0;
672 alert
"Found multiple ${type}Otp-Secret strings", count
=> $count if 1 < $count;
674 return (secret
=> $secret_b32, base32
=> 1) if defined $secret_b32;
675 return (secret
=> decode_b64
($secret_b64)) if defined $secret_b64;
676 return (secret
=> pack('H*', $secret_hex)) if defined $secret_hex;
677 return (secret
=> encode
('UTF-8', $secret_txt));
680 sub _hotp_increment_counter
{
682 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
684 looks_like_number
($counter) or throw
'HmacOtp-Counter value must be a number', value
=> $counter;
685 my $next = $counter + 1;
686 $self->string('HmacOtp-Counter', $next);
690 ##############################################################################
694 $size = $entry->size;
696 Get the size
(in bytes
) of an entry
.
698 B
<NOTE
:> This
is not an exact figure because there
is no canonical serialization of an entry
. This size should
699 only be used as a rough estimate
for comparison with other entries
or to impose data size limitations
.
709 $size += length(encode
('UTF-8', $self->tags // ''));
711 # attributes (strings)
712 while (my ($key, $string) = each %{$self->strings}) {
713 next if !defined $string->{value
};
714 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $string->{value
} // ''));
718 while (my ($key, $item) = each %{$self->custom_data}) {
719 next if !defined $item->{value
};
720 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $item->{value
} // ''));
724 while (my ($key, $binary) = each %{$self->binaries}) {
725 next if !defined $binary->{value
};
726 my $value_len = utf8
::is_utf8
($binary->{value
}) ? length(encode
('UTF-8', $binary->{value
}))
727 : length($binary->{value
});
728 $size += length(encode
('UTF-8', $key)) + $value_len;
731 # autotype associations
732 for my $association (@{$self->auto_type->{associations
} || []}) {
733 $size += length(encode
('UTF-8', $association->{window
}))
734 + length(encode
('UTF-8', $association->{keystroke_sequence
} // ''));
740 ##############################################################################
744 my $entries = $self->{history
} //= [];
745 # FIXME - Looping through entries on each access is too expensive.
746 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
752 $size = $entry->history_size;
754 Get the size
(in bytes
) of all historical entries combined
.
760 return sum0
map { $_->size } @{$self->history};
763 =method prune_history
765 $entry->prune_history(%options);
767 Remove as many older historical entries as necessary to get under the database limits
. The limits are taken
768 from the associated database
(if any
) or can be overridden with C
<%options>:
771 * C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
772 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
780 my $max_items = $args{max_items
} // eval { $self->kdbx->history_max_items }
781 // HISTORY_DEFAULT_MAX_ITEMS
;
782 my $max_size = $args{max_size
} // eval { $self->kdbx->history_max_size }
783 // HISTORY_DEFAULT_MAX_SIZE
;
785 # history is ordered oldest to youngest
786 my $history = $self->history;
788 if (0 <= $max_items && $max_items < @$history) {
789 splice @$history, -$max_items;
792 if (0 <= $max_size) {
793 my $current_size = $self->history_size;
794 while ($max_size < $current_size) {
795 my $entry = shift @$history;
796 $current_size -= $entry->size;
803 delete $_->{history
} for @_;
804 push @{$self->{history
} //= []}, map { $self->_wrap_entry($_) } @_;
807 ##############################################################################
811 require File
::KDBX
::Transaction
;
812 return File
::KDBX
::Transaction-
>new($self, @_);
818 $self->add_history($txn->original);
819 $self->last_modification_time(gmtime);
822 sub label
{ shift-
>expanded_title(@_) }
829 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
830 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
831 that every entry has:
840 Beyond this, you can store any number of other strings and any number of binaries that you can use for
841 whatever purpose you want.
843 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
844 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
845 the attributes to see what's available.
849 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
850 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
851 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
852 of the same entry. If the C<UserName> string had a value of "batman", the B<URL> string would expand to
853 C<http://example.com?user=batman>.
855 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
856 brace, like C<{PLACEHOLDER:ARGUMENT}>.
858 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
859 This software supports many (but not all) of the placeholders documented there.
861 =head3 Entry Placeholders
864 * ☑ C<{TITLE}> - B<Title> string
865 * ☑ C<{USERNAME}> - B<UserName> string
866 * ☑ C<{PASSWORD}> - B<Password> string
867 * ☑ C<{NOTES}> - B<Notes> string
868 * ☑ C<{URL}> - B<URL> string
869 * ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
870 * ☑ C<{URL:USERINFO}>
871 * ☑ C<{URL:USERNAME}>
872 * ☑ C<{URL:PASSWORD}>
877 * ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
878 * ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
879 * ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
880 * ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
881 * ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
882 * ☑ C<{TIMEOTP}> - Generate a time-based one-time password
883 * ☑ C<{GROUP_NOTES}> - Notes of the parent group
884 * ☑ C<{GROUP_PATH}> - Full path of the parent group
885 * ☑ C<{GROUP}> - Name of the parent group
887 =head3 Field References
890 * ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
892 =head3 File path Placeholders
895 * ☑ C<{APPDIR}> - Program directory path
896 * ☑ C<{FIREFOX}> - Path to the Firefox browser executable
897 * ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
898 * ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
899 * ☑ C<{OPERA}> - Path to the Opera browser executable
900 * ☑ C<{SAFARI}> - Path to the Safari browser executable
901 * ☒ C<{DB_PATH}> - Full file path of the database
902 * ☒ C<{DB_DIR}> - Directory path of the database
903 * ☒ C<{DB_NAME}> - File name (including extension) of the database
904 * ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
905 * ☒ C<{DB_EXT}> - File name extension
906 * ☑ C<{ENV_DIRSEP}> - Directory separator
907 * ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
909 =head3 Date and Time Placeholders
912 * ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
913 * ☑ C<{DT_YEAR}> - Year component of the current local date
914 * ☑ C<{DT_MONTH}> - Month component of the current local date
915 * ☑ C<{DT_DAY}> - Day component of the current local date
916 * ☑ C<{DT_HOUR}> - Hour component of the current local time
917 * ☑ C<{DT_MINUTE}> - Minute component of the current local time
918 * ☑ C<{DT_SECOND}> - Second component of the current local time
919 * ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
920 * ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
921 * ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
922 * ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
923 * ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
924 * ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
925 * ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
927 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
929 =head3 Special Key Placeholders
931 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
932 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
933 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
935 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
936 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
938 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
939 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
941 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
942 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
944 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
945 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
947 =head3 Miscellaneous Placeholders
951 * ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
952 * ☒ C<{BASE:USERINFO}>
953 * ☒ C<{BASE:USERNAME}>
954 * ☒ C<{BASE:PASSWORD}>
959 * ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
960 * ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
961 * ☒ C<{CLIPBOARD-SET:/Text/}>
963 * ☒ C<{CMD:/CommandLine/Options/}>
964 * ☑ C<{C:Comment}> - Comments are simply replaced by nothing
965 * ☑ C<{ENV:} and C<%ENV%> - Environment variables
966 * ☒ C<{GROUP_SEL_NOTES}>
967 * ☒ C<{GROUP_SEL_PATH}>
970 * ☒ C<{NEWPASSWORD:/Profile/}>
971 * ☒ C<{PASSWORD_ENC}>
973 * ☒ C<{PICKCHARS:Field:Options}>
975 * ☒ C<{T-CONV:/Text/Type/}>
976 * ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
978 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
979 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
980 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
981 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
983 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
988 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
989 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
990 strings or auto-complete key sequences.
992 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
993 my ($entry, $arg) = @_; # ^ Notice the colon here
997 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
998 everything after the colon and before the end of the placeholder is passed to your placeholder handler
999 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value C<whatever>.
1001 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1002 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1003 both with and without a colon (or they could be different subroutines):
1005 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1006 (undef, my $arg) = @_;
1007 return defined $arg ? rand($arg) : rand;
1010 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1013 %File::KDBX::PLACEHOLDERS = ();
1015 =head2 One-time Passwords
1017 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1018 configuration storage isn't completely standardized, but this module supports two predominant configuration
1022 * L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1025 B<NOTE:> To use this feature, you must install the suggested dependency:
1030 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1031 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1033 To configure TOTP in the KeePass 2 style, set the following strings:
1036 * C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and
1038 * C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1039 * C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1040 * C<TimeOtp-Secret> - Text string secret, OR
1041 * C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1042 * C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1043 * C<TimeOtp-Secret-Base64> - Base64-encoded secret
1045 To configure HOTP in the KeePass 2 style, set the following strings:
1048 * C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp>
1050 * C<HmacOtp-Secret> - Text string secret, OR
1051 * C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1052 * C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1053 * C<HmacOtp-Secret-Base64> - Base64-encoded secret
1055 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1056 these should actually be set or an error will be thrown.
1058 Here's a basic example:
1060 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1062 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1064 my $otp = $entry->time_otp;