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 List
::Util
qw(sum0);
14 use Ref
::Util
qw(is_plain_hashref is_ref);
15 use Scalar
::Util
qw(looks_like_number refaddr);
16 use Storable
qw(dclone);
21 use parent
'File::KDBX::Object';
23 our $VERSION = '999.999'; # VERSION
25 my $PLACEHOLDER_MAX_DEPTH = 10;
27 my %STANDARD_STRINGS = map { $_ => 1 } qw(Title UserName Password URL Notes);
29 sub _parent_container
{ 'entries' }
33 128-bit UUID identifying the entry within the database
.
37 Integer representing a
default icon
. See L
<File
::KDBX
::Constants
/":icon"> for valid
values.
39 =attr custom_icon_uuid
41 128-bit UUID identifying a custom icon within the database
.
43 =attr foreground_color
45 Text color represented as a string of the form C
<#000000>.
47 =attr background_color
49 Background color represented as a string of the form C
<#FFFFFF>.
57 Text string with arbitrary tags which can be used to build a taxonomy
.
65 data_transfer_obfuscation
=> 0,
66 default_sequence
=> '{USERNAME}{TAB}{PASSWORD}{ENTER}',
69 window
=> 'My Bank - Mozilla Firefox',
70 keystroke_sequence
=> '{PASSWORD}{ENTER}',
75 =attr previous_parent_group
77 128-bit UUID identifying a group within the database
.
81 Boolean indicating whether the entry password should be tested
for weakness
and show up
in reports
.
85 Hash with entry strings
, including the standard strings as well as any custom ones
.
88 # Every entry has these five strings:
89 Title
=> { value
=> 'Example Entry' },
90 UserName
=> { value
=> 'jdoe' },
91 Password
=> { value
=> 's3cr3t', protect
=> true
},
92 URL
=> { value
=> 'https://example.com' }
93 Notes
=> { value
=> '' },
94 # May also have custom strings:
95 MySystem
=> { value
=> 'The mainframe' },
100 Files
or attachments
.
104 A set of key-value pairs used to store arbitrary data
, usually used by software to keep track of
state rather
105 than by end users
(who typically work with the strings
and binaries
).
109 Array of historical entries
. Historical entries are prior versions of the same entry so they all share the
110 same UUID with the current entry
.
112 =attr last_modification_time
114 Date
and time when the entry was
last modified
.
118 Date
and time when the entry was created
.
120 =attr last_access_time
122 Date
and time when the entry was
last accessed
.
126 Date
and time when the entry expired
or will expire
.
130 Boolean value indicating whether
or not an entry
is expired
.
134 The number of
times an entry
has been used
, which typically means how many
times the C
<Password
> string
has
137 =attr location_changed
139 Date
and time when the entry was
last moved to a different group
.
143 Alias
for the C
<Notes
> string value
.
147 Alias
for the C
<Password
> string value
.
151 Alias
for the C
<Title
> string value
.
155 Alias
for the C
<URL
> string value
.
159 Aliases
for the C
<UserName
> string value
.
165 if (@_ || !defined $self->{uuid
}) {
166 my %args = @_ % 2 == 1 ? (uuid
=> shift, @_) : @_;
167 my $old_uuid = $self->{uuid
};
168 my $uuid = $self->{uuid
} = delete $args{uuid
} // generate_uuid
;
169 for my $entry (@{$self->history}) {
170 $entry->{uuid
} = $uuid;
172 # if (defined $old_uuid and my $kdbx = $KDBX{refaddr($self)}) {
173 # $kdbx->_update_entry_uuid($old_uuid, $uuid, $self);
179 my @ATTRS = qw(uuid custom_data history);
181 # uuid => sub { generate_uuid(printable => 1) },
182 icon_id
=> ICON_PASSWORD
,
183 custom_icon_uuid
=> undef,
184 foreground_color
=> '',
185 background_color
=> '',
188 auto_type
=> sub { +{} },
189 previous_parent_group
=> undef,
190 quality_check
=> true
,
191 strings
=> sub { +{} },
192 binaries
=> sub { +{} },
193 # custom_data => sub { +{} },
194 # history => sub { +[] },
197 last_modification_time
=> sub { gmtime },
198 creation_time
=> sub { gmtime },
199 last_access_time
=> sub { gmtime },
200 expiry_time
=> sub { gmtime },
203 location_changed
=> sub { gmtime },
205 my %ATTRS_STRINGS = (
207 username
=> 'UserName',
208 password
=> 'Password',
213 while (my ($attr, $default) = each %ATTRS) {
214 no strict
'refs'; ## no critic (ProhibitNoStrict)
217 $self->{$attr} = shift if @_;
218 $self->{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
221 while (my ($attr, $default) = each %ATTRS_TIMES) {
222 no strict
'refs'; ## no critic (ProhibitNoStrict)
225 $self->{times} //= {};
226 $self->{times}{$attr} = shift if @_;
227 $self->{times}{$attr} //= (ref $default eq 'CODE') ? $default->($self) : $default;
230 while (my ($attr, $string_key) = each %ATTRS_STRINGS) {
231 no strict
'refs'; ## no critic (ProhibitNoStrict)
232 *{$attr} = sub { shift-
>string_value($string_key, @_) };
233 *{"expanded_${attr}"} = sub { shift-
>expanded_string_value($string_key, @_) };
236 sub _set_default_attributes
{
238 $self->$_ for @ATTRS, keys %ATTRS, keys %ATTRS_TIMES, keys %ATTRS_STRINGS;
245 while (my ($key, $val) = each %args) {
246 if (my $method = $self->can($key)) {
247 $self->$method($val);
250 $self->string($key => $val);
257 ##############################################################################
261 \
%string = $entry->string($string_key);
263 $entry->string($string_key, \
%string);
264 $entry->string($string_key, %attributes);
265 $entry->string($string_key, $value); # same as: value => $value
267 Get
or set a string
. Every string
has a unique
(to the entry
) key
and flags
and so are returned as a hash
268 structure
. For example
:
272 protect
=> true
, # optional
275 Every string should have a value
(but might be C
<undef> due to memory protection
) and these optional flags
279 * C<protect> - Whether or not the string value should be memory-protected.
285 my %args = @_ == 2 ? (key
=> shift, value
=> shift)
286 : @_ % 2 == 1 ? (key
=> shift, @_) : @_;
288 if (!defined $args{key
} && !defined $args{value
}) {
289 my %standard = (value
=> 1, protect
=> 1);
290 my @other_keys = grep { !$standard{$_} } keys %args;
291 if (@other_keys == 1) {
292 my $key = $args{key
} = $other_keys[0];
293 $args{value
} = delete $args{$key};
297 my $key = delete $args{key
} or throw
'Must provide a string key to access';
299 return $self->{strings
}{$key} = $args{value
} if is_plain_hashref
($args{value
});
301 while (my ($field, $value) = each %args) {
302 $self->{strings
}{$key}{$field} = $value;
305 # Auto-vivify the standard strings.
306 if ($STANDARD_STRINGS{$key}) {
307 return $self->{strings
}{$key} //= {value
=> '', $self->_protect($key) ? (protect
=> true
) : ()};
309 return $self->{strings
}{$key};
312 ### Get whether or not a standard string is configured to be protected
316 return false
if !$STANDARD_STRINGS{$key};
317 if (my $kdbx = eval { $self->kdbx }) {
318 my $protect = $kdbx->memory_protection($key);
319 return $protect if defined $protect;
321 return $key eq 'Password';
326 $string = $entry->string_value;
328 Access a string value directly
. Returns C
<undef> if the string
is not set
.
334 my $string = $self->string(@_) // return undef;
335 return $string->{value
};
338 =method expanded_string_value
340 $string = $entry->expanded_string_value;
342 Same as L
</string_value
> but will substitute placeholders
and resolve field references
. Any placeholders that
343 do not expand to
values are left as-is
.
345 See L
</Placeholders
>.
347 Some placeholders
(notably field references
) require the entry be associated with a database
and will throw an
348 error
if there
is no association
.
352 sub _expand_placeholder
{
354 my $placeholder = shift;
359 my $placeholder_key = $placeholder;
361 $placeholder_key = $File::KDBX
::PLACEHOLDERS
{"${placeholder}:${arg}"} ? "${placeholder}:${arg}"
364 return if !defined $File::KDBX
::PLACEHOLDERS
{$placeholder_key};
366 my $local_key = join('/', refaddr
($self), $placeholder_key);
367 local $PLACEHOLDERS{$local_key} = my $handler = $PLACEHOLDERS{$local_key} // do {
368 my $handler = $File::KDBX
::PLACEHOLDERS
{$placeholder_key} or next;
369 memoize recurse_limit
($handler, $PLACEHOLDER_MAX_DEPTH, sub {
370 alert
"Detected deep recursion while expanding $placeholder placeholder",
371 placeholder
=> $placeholder;
376 return $handler->($self, $arg, $placeholder);
383 my $expand = memoize
$self->can('_expand_placeholder'), $self;
385 # placeholders (including field references):
386 $str =~ s!\{([^:\}]+)(?::([^\}]*))?\}!$expand->(uc($1), $2, @_) // $&!egi;
388 # environment variables (alt syntax):
389 my $vars = join('|', map { quotemeta($_) } keys %ENV);
390 $str =~ s!\%($vars)\%!$expand->(ENV => $1, @_) // $&!eg;
395 sub expanded_string_value
{
397 my $str = $self->string_value(@_) // return undef;
398 return $self->_expand_string($str);
401 =method other_strings
403 $other = $entry->other_strings;
404 $other = $entry->other_strings($delimiter);
406 Get a concatenation of all non-standard string
values. The
default delimiter
is a newline
. This
is is useful
407 for executing queries to search
for entities based on the contents of these other strings
(if any
).
413 my $delim = shift // "\n";
415 my @strings = map { $self->string_value($_) } grep { !$STANDARD_STRINGS{$_} } sort keys %{$self->strings};
416 return join($delim, @strings);
421 my $string = $self->string(@_);
422 return defined $string->{value
} ? $string->{value
} : $self->kdbx->peek($string);
425 sub password_peek
{ $_[0]->string_peek('Password') }
427 ##############################################################################
431 my $key = shift or throw
'Must provide a binary key to access';
433 my $arg = @_ == 1 ? shift : undef;
435 @args{keys %$arg} = values %$arg if ref $arg eq 'HASH';
436 $args{value
} = $arg if !ref $arg;
437 while (my ($field, $value) = each %args) {
438 $self->{binaries
}{$key}{$field} = $value;
441 my $binary = $self->{binaries
}{$key} //= {value
=> ''};
442 if (defined (my $ref = $binary->{ref})) {
443 $binary = $self->{binaries
}{$key} = dclone
($self->kdbx->binaries->{$ref});
448 sub binary_novivify
{
450 my $binary_key = shift;
451 return if !$self->{binaries
}{$binary_key} && !@_;
452 return $self->binary($binary_key, @_);
457 my $binary = $self->binary_novivify(@_) // return undef;
458 return $binary->{value
};
461 sub auto_type_enabled
{
466 ##############################################################################
470 $otp = $entry->hmac_otp(%options);
472 Generate an HMAC-based one-time password
, or C
<undef> if HOTP
is not configured
for the entry
. The entry
's
473 strings generally must first be unprotected, just like when accessing the password. Valid options are:
476 * C<counter> - Specify the counter value
478 To configure HOTP, see L</"One-time Passwords">.
484 load_optional('Pass
::OTP
');
486 my %params = ($self->_hotp_params, @_);
487 return if !defined $params{type} || !defined $params{secret};
489 $params{secret} = encode_b32r($params{secret}) if !$params{base32};
492 my $otp = eval {Pass::OTP::otp(%params, @_) };
494 throw 'Unable to generate HOTP
', error => $err;
497 $self->_hotp_increment_counter($params{counter});
504 $otp = $entry->time_otp(%options);
506 Generate a time-based one-time password, or C<undef> if TOTP is not configured for the entry. The entry's
507 strings generally must first be unprotected
, just like
when accessing the password
. Valid options are
:
510 * C<now> - Specify the value for determining the time-step counter
512 To configure TOTP, see L</"One-time Passwords">.
518 load_optional
('Pass::OTP');
520 my %params = ($self->_totp_params, @_);
521 return if !defined $params{type
} || !defined $params{secret
};
523 $params{secret
} = encode_b32r
($params{secret
}) if !$params{base32
};
526 my $otp = eval {Pass
::OTP
::otp
(%params, @_) };
528 throw
'Unable to generate TOTP', error
=> $err;
538 $uri_string = $entry->hmac_otp_uri;
539 $uri_string = $entry->time_otp_uri;
541 Get a HOTP
or TOTP otpauth URI
for the entry
, if available
.
543 To configure OTP
, see L
</"One-time Passwords">.
547 sub hmac_otp_uri
{ $_[0]->_otp_uri($_[0]->_hotp_params) }
548 sub time_otp_uri
{ $_[0]->_otp_uri($_[0]->_totp_params) }
554 return if 4 != grep { defined } @params{qw(type secret issuer account)};
555 return if $params{type
} !~ /^[ht]otp$/i;
557 my $label = delete $params{label
};
558 $params{$_} = uri_escape_utf8
($params{$_}) for keys %params;
560 my $type = lc($params{type
});
561 my $issuer = $params{issuer
};
562 my $account = $params{account
};
564 $label //= "$issuer:$account";
566 my $secret = $params{secret
};
567 $secret = uc(encode_b32r
($secret)) if !$params{base32
};
569 delete $params{algorithm
} if defined $params{algorithm
} && $params{algorithm
} eq 'sha1';
570 delete $params{period
} if defined $params{period
} && $params{period
} == 30;
571 delete $params{digits
} if defined $params{digits
} && $params{digits
} == 6;
572 delete $params{counter
} if defined $params{counter
} && $params{counter
} == 0;
574 my $uri = "otpauth://$type/$label?secret=$secret&issuer=$issuer";
576 if (defined $params{encoder
}) {
577 $uri .= "&encoder=$params{encoder}";
580 $uri .= '&algorithm=' . uc($params{algorithm
}) if defined $params{algorithm
};
581 $uri .= "&digits=$params{digits}" if defined $params{digits
};
582 $uri .= "&counter=$params{counter}" if defined $params{counter
};
583 $uri .= "&period=$params{period}" if defined $params{period
};
593 issuer
=> $self->title || 'KDBX',
594 account
=> $self->username || 'none',
596 counter
=> $self->string_value('HmacOtp-Counter') // 0,
597 $self->_otp_secret_params('Hmac'),
599 return %params if $params{secret
};
601 my %otp_params = $self->_otp_params;
602 return () if !$otp_params{secret
} || $otp_params{type
} ne 'hotp';
604 # $otp_params{counter} = 0
606 return (%params, %otp_params);
613 'HMAC-SHA-1' => 'sha1',
614 'HMAC-SHA-256' => 'sha256',
615 'HMAC-SHA-512' => 'sha512',
619 issuer
=> $self->title || 'KDBX',
620 account
=> $self->username || 'none',
621 digits
=> $self->string_value('TimeOtp-Length') // 6,
622 algorithm
=> $algorithms{$self->string_value('TimeOtp-Algorithm') || ''} || 'sha1',
623 period
=> $self->string_value('TimeOtp-Period') // 30,
624 $self->_otp_secret_params('Time'),
626 return %params if $params{secret
};
628 my %otp_params = $self->_otp_params;
629 return () if !$otp_params{secret
} || $otp_params{type
} ne 'totp';
631 return (%params, %otp_params);
637 load_optional
('Pass::OTP::URI');
639 my $uri = $self->string_value('otp') || '';
641 %params = Pass
::OTP
::URI
::parse
($uri) if $uri =~ m!^otpauth://!;
642 return () if !$params{secret
} || !$params{type
};
644 if (($params{encoder
} // '') eq 'steam') {
646 $params{chars
} = '23456789BCDFGHJKMNPQRTVWXY';
649 # Pass::OTP::URI doesn't provide the issuer and account separately, so get them from the label
650 my ($issuer, $user) = split(':', $params{label
} // ':', 2);
651 $params{issuer
} //= uri_unescape_utf8
($issuer);
652 $params{account
} //= uri_unescape_utf8
($user);
654 $params{algorithm
} = lc($params{algorithm
}) if $params{algorithm
};
655 $params{counter
} = $self->string_value('HmacOtp-Counter') if $params{type
} eq 'hotp';
660 sub _otp_secret_params
{
662 my $type = shift // return ();
664 my $secret_txt = $self->string_value("${type}Otp-Secret");
665 my $secret_hex = $self->string_value("${type}Otp-Secret-Hex");
666 my $secret_b32 = $self->string_value("${type}Otp-Secret-Base32");
667 my $secret_b64 = $self->string_value("${type}Otp-Secret-Base64");
669 my $count = grep { defined } ($secret_txt, $secret_hex, $secret_b32, $secret_b64);
670 return () if $count == 0;
671 alert
"Found multiple ${type}Otp-Secret strings", count
=> $count if 1 < $count;
673 return (secret
=> $secret_b32, base32
=> 1) if defined $secret_b32;
674 return (secret
=> decode_b64
($secret_b64)) if defined $secret_b64;
675 return (secret
=> pack('H*', $secret_hex)) if defined $secret_hex;
676 return (secret
=> encode
('UTF-8', $secret_txt));
679 sub _hotp_increment_counter
{
681 my $counter = shift // $self->string_value('HmacOtp-Counter') || 0;
683 looks_like_number
($counter) or throw
'HmacOtp-Counter value must be a number', value
=> $counter;
684 my $next = $counter + 1;
685 $self->string('HmacOtp-Counter', $next);
689 ##############################################################################
693 $size = $entry->size;
695 Get the size
(in bytes
) of an entry
.
697 B
<NOTE
:> This
is not an exact figure because there
is no canonical serialization of an entry
. This size should
698 only be used as a rough estimate
for comparison with other entries
or to impose data size limitations
.
708 $size += length(encode
('UTF-8', $self->tags // ''));
710 # attributes (strings)
711 while (my ($key, $string) = each %{$self->strings}) {
712 next if !defined $string->{value
};
713 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $string->{value
} // ''));
717 while (my ($key, $item) = each %{$self->custom_data}) {
718 next if !defined $item->{value
};
719 $size += length(encode
('UTF-8', $key)) + length(encode
('UTF-8', $item->{value
} // ''));
723 while (my ($key, $binary) = each %{$self->binaries}) {
724 next if !defined $binary->{value
};
725 my $value_len = utf8
::is_utf8
($binary->{value
}) ? length(encode
('UTF-8', $binary->{value
}))
726 : length($binary->{value
});
727 $size += length(encode
('UTF-8', $key)) + $value_len;
730 # autotype associations
731 for my $association (@{$self->auto_type->{associations
} || []}) {
732 $size += length(encode
('UTF-8', $association->{window
}))
733 + length(encode
('UTF-8', $association->{keystroke_sequence
} // ''));
739 ##############################################################################
743 my $entries = $self->{history
} //= [];
744 # FIXME - Looping through entries on each access is too expensive.
745 @$entries = map { $self->_wrap_entry($_, $self->kdbx) } @$entries;
751 $size = $entry->history_size;
753 Get the size
(in bytes
) of all historical entries combined
.
759 return sum0
map { $_->size } @{$self->history};
762 =method prune_history
764 $entry->prune_history(%options);
766 Remove as many older historical entries as necessary to get under the database limits
. The limits are taken
767 from the associated database
(if any
) or can be overridden with C
<%options>:
770 * C<max_items> - Maximum number of historical entries to keep (default: 10, no limit: -1)
771 * C<max_size> - Maximum total size (in bytes) of historical entries to keep (default: 6 MiB, no limit: -1)
779 my $max_items = $args{max_items
} // eval { $self->kdbx->history_max_items }
780 // HISTORY_DEFAULT_MAX_ITEMS
;
781 my $max_size = $args{max_size
} // eval { $self->kdbx->history_max_size }
782 // HISTORY_DEFAULT_MAX_SIZE
;
784 # history is ordered oldest to youngest
785 my $history = $self->history;
787 if (0 <= $max_items && $max_items < @$history) {
788 splice @$history, -$max_items;
791 if (0 <= $max_size) {
792 my $current_size = $self->history_size;
793 while ($max_size < $current_size) {
794 my $entry = shift @$history;
795 $current_size -= $entry->size;
802 delete $_->{history
} for @_;
803 push @{$self->{history
} //= []}, map { $self->_wrap_entry($_) } @_;
806 ##############################################################################
810 require File
::KDBX
::Transaction
;
811 return File
::KDBX
::Transaction-
>new($self, @_);
817 $self->add_history($txn->original);
818 $self->last_modification_time(gmtime);
821 sub label
{ shift-
>expanded_title(@_) }
828 An entry in a KDBX database is a record that can contains strings (also called "fields") and binaries (also
829 called "files" or "attachments"). Every string and binary has a key or name. There is a default set of strings
830 that every entry has:
839 Beyond this, you can store any number of other strings and any number of binaries that you can use for
840 whatever purpose you want.
842 There is also some metadata associated with an entry. Each entry in a database is identified uniquely by
843 a UUID. An entry can also have an icon associated with it, and there are various timestamps. Take a look at
844 the attributes to see what's available.
848 Entry string and auto-type key sequences can have placeholders or template tags that can be replaced by other
849 values. Placeholders can appear like C<{PLACEHOLDER}>. For example, a B<URL> string might have a value of
850 C<http://example.com?user={USERNAME}>. C<{USERNAME}> is a placeholder for the value of the B<UserName> string
851 of the same entry. If the C<UserName> string had a value of "batman", the B<URL> string would expand to
852 C<http://example.com?user=batman>.
854 Some placeholders take an argument, where the argument follows the tag after a colon but before the closing
855 brace, like C<{PLACEHOLDER:ARGUMENT}>.
857 Placeholders are documented in the L<KeePass Help Center|https://keepass.info/help/base/placeholders.html>.
858 This software supports many (but not all) of the placeholders documented there.
860 =head3 Entry Placeholders
863 * ☑ C<{TITLE}> - B<Title> string
864 * ☑ C<{USERNAME}> - B<UserName> string
865 * ☑ C<{PASSWORD}> - B<Password> string
866 * ☑ C<{NOTES}> - B<Notes> string
867 * ☑ C<{URL}> - B<URL> string
868 * ☑ C<{URL:SCM}> / C<{URL:SCHEME}>
869 * ☑ C<{URL:USERINFO}>
870 * ☑ C<{URL:USERNAME}>
871 * ☑ C<{URL:PASSWORD}>
876 * ☑ C<{URL:FRAGMENT}> / C<{URL:HASH}>
877 * ☑ C<{URL:RMVSCM}> / C<{URL:WITHOUTSCHEME}>
878 * ☑ C<{S:Name}> - Custom string where C<Name> is the name or key of the string
879 * ☑ C<{UUID}> - Identifier (32 hexidecimal characters)
880 * ☑ C<{HMACOTP}> - Generate an HMAC-based one-time password (its counter B<will> be incremented)
881 * ☑ C<{TIMEOTP}> - Generate a time-based one-time password
882 * ☑ C<{GROUP_NOTES}> - Notes of the parent group
883 * ☑ C<{GROUP_PATH}> - Full path of the parent group
884 * ☑ C<{GROUP}> - Name of the parent group
886 =head3 Field References
889 * ☑ C<{REF:Wanted@SearchIn:Text}> - See L<File::KDBX/resolve_reference>
891 =head3 File path Placeholders
894 * ☑ C<{APPDIR}> - Program directory path
895 * ☑ C<{FIREFOX}> - Path to the Firefox browser executable
896 * ☑ C<{GOOGLECHROME}> - Path to the Chrome browser executable
897 * ☑ C<{INTERNETEXPLORER}> - Path to the Firefox browser executable
898 * ☑ C<{OPERA}> - Path to the Opera browser executable
899 * ☑ C<{SAFARI}> - Path to the Safari browser executable
900 * ☒ C<{DB_PATH}> - Full file path of the database
901 * ☒ C<{DB_DIR}> - Directory path of the database
902 * ☒ C<{DB_NAME}> - File name (including extension) of the database
903 * ☒ C<{DB_BASENAME}> - File name (excluding extension) of the database
904 * ☒ C<{DB_EXT}> - File name extension
905 * ☑ C<{ENV_DIRSEP}> - Directory separator
906 * ☑ C<{ENV_PROGRAMFILES_X86}> - One of C<%ProgramFiles(x86)%> or C<%ProgramFiles%>
908 =head3 Date and Time Placeholders
911 * ☑ C<{DT_SIMPLE}> - Current local date and time as a sortable string
912 * ☑ C<{DT_YEAR}> - Year component of the current local date
913 * ☑ C<{DT_MONTH}> - Month component of the current local date
914 * ☑ C<{DT_DAY}> - Day component of the current local date
915 * ☑ C<{DT_HOUR}> - Hour component of the current local time
916 * ☑ C<{DT_MINUTE}> - Minute component of the current local time
917 * ☑ C<{DT_SECOND}> - Second component of the current local time
918 * ☑ C<{DT_UTC_SIMPLE}> - Current UTC date and time as a sortable string
919 * ☑ C<{DT_UTC_YEAR}> - Year component of the current UTC date
920 * ☑ C<{DT_UTC_MONTH}> - Month component of the current UTC date
921 * ☑ C<{DT_UTC_DAY}> - Day component of the current UTC date
922 * ☑ C<{DT_UTC_HOUR}> - Hour component of the current UTC time
923 * ☑ C<{DT_UTC_MINUTE}> Minute Year component of the current UTC time
924 * ☑ C<{DT_UTC_SECOND}> - Second component of the current UTC time
926 If the current date and time is <2012-07-25 17:05:34>, the "simple" form would be C<20120725170534>.
928 =head3 Special Key Placeholders
930 Certain placeholders for use in auto-type key sequences are not supported for replacement, but they will
931 remain as-is so that an auto-type engine (not included) can parse and replace them with the appropriate
932 virtual key presses. For completeness, here is the list that the KeePass program claims to support:
934 C<{TAB}>, C<{ENTER}>, C<{UP}>, C<{DOWN}>, C<{LEFT}>, C<{RIGHT}>, C<{HOME}>, C<{END}>, C<{PGUP}>, C<{PGDN}>,
935 C<{INSERT}>, C<{DELETE}>, C<{SPACE}>
937 C<{BACKSPACE}>, C<{BREAK}>, C<{CAPSLOCK}>, C<{ESC}>, C<{WIN}>, C<{LWIN}>, C<{RWIN}>, C<{APPS}>, C<{HELP}>,
938 C<{NUMLOCK}>, C<{PRTSC}>, C<{SCROLLLOCK}>
940 C<{F1}>, C<{F2}>, C<{F3}>, C<{F4}>, C<{F5}>, C<{F6}>, C<{F7}>, C<{F8}>, C<{F9}>, C<{F10}>, C<{F11}>, C<{F12}>,
941 C<{F13}>, C<{F14}>, C<{F15}>, C<{F16}>
943 C<{ADD}>, C<{SUBTRACT}>, C<{MULTIPLY}>, C<{DIVIDE}>, C<{NUMPAD0}>, C<{NUMPAD1}>, C<{NUMPAD2}>, C<{NUMPAD3}>,
944 C<{NUMPAD4}>, C<{NUMPAD5}>, C<{NUMPAD6}>, C<{NUMPAD7}>, C<{NUMPAD8}>, C<{NUMPAD9}>
946 =head3 Miscellaneous Placeholders
950 * ☒ C<{BASE:SCM}> / C<{BASE:SCHEME}>
951 * ☒ C<{BASE:USERINFO}>
952 * ☒ C<{BASE:USERNAME}>
953 * ☒ C<{BASE:PASSWORD}>
958 * ☒ C<{BASE:FRAGMENT}> / C<{BASE:HASH}>
959 * ☒ C<{BASE:RMVSCM}> / C<{BASE:WITHOUTSCHEME}>
960 * ☒ C<{CLIPBOARD-SET:/Text/}>
962 * ☒ C<{CMD:/CommandLine/Options/}>
963 * ☑ C<{C:Comment}> - Comments are simply replaced by nothing
964 * ☑ C<{ENV:} and C<%ENV%> - Environment variables
965 * ☒ C<{GROUP_SEL_NOTES}>
966 * ☒ C<{GROUP_SEL_PATH}>
969 * ☒ C<{NEWPASSWORD:/Profile/}>
970 * ☒ C<{PASSWORD_ENC}>
972 * ☒ C<{PICKCHARS:Field:Options}>
974 * ☒ C<{T-CONV:/Text/Type/}>
975 * ☒ C<{T-REPLACE-RX:/Text/Type/Replace/}>
977 Some of these that remain unimplemented, such as C<{CLIPBOARD}>, cannot be implemented portably. Some of these
978 I haven't implemented (yet) just because they don't seem very useful. You can create your own placeholder to
979 augment the list of default supported placeholders or to replace a built-in placeholder handler. To create
980 a placeholder, just set it in the C<%File::KDBX::PLACEHOLDERS> hash. For example:
982 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER'} = sub {
987 If the placeholder is expanded in the context of an entry, C<$entry> is the B<File::KDBX::Entry> object in
988 context. Otherwise it is C<undef>. An entry is in context if, for example, the placeholder is in an entry's
989 strings or auto-complete key sequences.
991 $File::KDBX::PLACEHOLDERS{'MY_PLACEHOLDER:'} = sub {
992 my ($entry, $arg) = @_; # ^ Notice the colon here
996 If the name of the placeholder ends in a colon, then it is expected to receive an argument. During expansion,
997 everything after the colon and before the end of the placeholder is passed to your placeholder handler
998 subroutine. So if the placeholder is C<{MY_PLACEHOLDER:whatever}>, C<$arg> will have the value C<whatever>.
1000 An argument is required for placeholders than take one. I.e. The placeholder handler won't be called if there
1001 is no argument. If you want a placeholder to support an optional argument, you'll need to set the placeholder
1002 both with and without a colon (or they could be different subroutines):
1004 $File::KDBX::PLACEHOLDERS{'RAND'} = $File::KDBX::PLACEHOLDERS{'RAND:'} = sub {
1005 (undef, my $arg) = @_;
1006 return defined $arg ? rand($arg) : rand;
1009 You can also remove placeholder handlers. If you want to disable placeholder expansion entirely, just delete
1012 %File::KDBX::PLACEHOLDERS = ();
1014 =head2 One-time Passwords
1016 An entry can be configured to generate one-time passwords, both HOTP (HMAC-based) and TOTP (time-based). The
1017 configuration storage isn't completely standardized, but this module supports two predominant configuration
1021 * L<KeePass 2|https://keepass.info/help/base/placeholders.html#otp>
1024 B<NOTE:> To use this feature, you must install the suggested dependency:
1029 To configure TOTP in the KeePassXC style, there is only one string to set: C<otp>. The value should be any
1030 valid otpauth URI. When generating an OTP, all of the relevant OTP properties are parsed from the URI.
1032 To configure TOTP in the KeePass 2 style, set the following strings:
1035 * C<TimeOtp-Algorithm> - Cryptographic algorithm, one of C<HMAC-SHA-1> (default), C<HMAC-SHA-256> and
1037 * C<TimeOtp-Length> - Number of digits each one-time password is (default: 6, maximum: 8)
1038 * C<TimeOtp-Period> - Time-step size in seconds (default: 30)
1039 * C<TimeOtp-Secret> - Text string secret, OR
1040 * C<TimeOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1041 * C<TimeOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1042 * C<TimeOtp-Secret-Base64> - Base64-encoded secret
1044 To configure HOTP in the KeePass 2 style, set the following strings:
1047 * C<HmacOtp-Counter> - Counting value in decimal, starts on C<0> by default and increments when L</hmac_otp>
1049 * C<HmacOtp-Secret> - Text string secret, OR
1050 * C<HmacOtp-Secret-Hex> - Hexidecimal-encoded secret, OR
1051 * C<HmacOtp-Secret-Base32> - Base32-encoded secret (most common), OR
1052 * C<HmacOtp-Secret-Base64> - Base64-encoded secret
1054 B<NOTE:> The multiple "Secret" strings are simply a way to store a secret in different formats. Only one of
1055 these should actually be set or an error will be thrown.
1057 Here's a basic example:
1059 $entry->string(otp => 'otpauth://totp/Issuer:user?secret=NBSWY3DP&issuer=Issuer');
1061 $entry->string('TimeOtp-Secret-Base32' => 'NBSWY3DP');
1063 my $otp = $entry->time_otp;