1 package File
::KDBX
::Safe
;
2 # ABSTRACT: Keep strings encrypted while in memory
7 use Crypt
::PRNG
qw(random_bytes);
8 use Devel
::GlobalDestruction
;
9 use Encode
qw(encode decode);
10 use File
::KDBX
::Constants
qw(:random_stream);
11 use File
::KDBX
::Error
;
12 use File
::KDBX
::Util
qw(erase erase_scoped);
13 use Ref
::Util
qw(is_arrayref is_coderef is_hashref is_scalarref);
14 use Scalar
::Util
qw(refaddr);
17 our $VERSION = '999.999'; # VERSION
21 $safe = File
::KDBX
::Safe-
>new(%attributes);
22 $safe = File
::KDBX
::Safe-
>new(\
@strings, %attributes);
24 Create a new safe
for storing secret strings encrypted
in memory
.
26 If a cipher
is passed
, its stream will be
reset.
32 my %args = @_ % 2 == 0 ? @_ : (strings
=> shift, @_);
34 if (!$args{cipher
} && $args{key
}) {
35 require File
::KDBX
::Cipher
;
36 $args{cipher
} = File
::KDBX
::Cipher-
>new(stream_id
=> STREAM_ID_CHACHA20
, key
=> $args{key
});
39 my $self = bless \
%args, $class;
40 $self->cipher->finish;
43 my $strings = delete $args{strings
};
46 $self->add($strings) if $strings;
51 sub DESTROY
{ !in_global_destruction
and $_[0]->unlock }
57 Clear a safe
, removing all store contents permanently
.
71 $safe = $safe->lock(@strings);
72 $safe = $safe->lock(\
@strings);
74 Add strings to be encrypted
.
80 sub lock { shift-
>add(@_) }
84 my @strings = map { is_arrayref
($_) ? @$_ : $_ } @_;
86 @strings or throw
'Must provide strings to lock';
88 my $cipher = $self->cipher;
90 for my $string (@strings) {
91 my $item = {str
=> $string, off
=> $self->{counter
}};
92 if (is_scalarref
($string)) {
93 next if !defined $$string;
94 $item->{enc
} = 'UTF-8' if utf8
::is_utf8
($$string);
95 if (my $encoding = $item->{enc
}) {
96 my $encoded = encode
($encoding, $$string);
97 $item->{val
} = $cipher->crypt(\
$encoded);
101 $item->{val
} = $cipher->crypt($string);
105 elsif (is_hashref
($string)) {
106 next if !defined $string->{value
};
107 $item->{enc
} = 'UTF-8' if utf8
::is_utf8
($string->{value
});
108 if (my $encoding = $item->{enc
}) {
109 my $encoded = encode
($encoding, $string->{value
});
110 $item->{val
} = $cipher->crypt(\
$encoded);
114 $item->{val
} = $cipher->crypt(\
$string->{value
});
116 erase \
$string->{value
};
119 throw
'Safe strings must be a hashref or stringref', type
=> ref $string;
121 push @{$self->{items
}}, $item;
122 $self->{index}{refaddr
($string)} = $item;
123 $self->{counter
} += length($item->{val
});
129 =method add_protected
131 $safe = $safe->add_protected(@strings);
132 $safe = $safe->add_protected(\
@strings);
134 Add strings that are already encrypted
.
136 B
<WARNING
:> You must add already-encrypted strings
in the order
in which they were original encrypted
or they
137 will
not decrypt correctly
. You almost certainly
do not want to add both unprotected
and protected strings to
144 my $filter = is_coderef
($_[0]) ? shift : undef;
145 my @strings = map { is_arrayref
($_) ? @$_ : $_ } @_;
147 @strings or throw
'Must provide strings to lock';
149 for my $string (@strings) {
150 my $item = {str
=> $string};
151 $item->{filter
} = $filter if defined $filter;
152 if (is_scalarref
($string)) {
153 next if !defined $$string;
154 $item->{val
} = $$string;
157 elsif (is_hashref
($string)) {
158 next if !defined $string->{value
};
159 $item->{val
} = $string->{value
};
160 erase \
$string->{value
};
163 throw
'Safe strings must be a hashref or stringref', type
=> ref $string;
165 push @{$self->{items
}}, $item;
166 $self->{index}{refaddr
($string)} = $item;
167 $self->{counter
} += length($item->{val
});
175 $safe = $safe->unlock;
177 Decrypt all the strings
. Each stored string
is set to its original value
.
179 This happens automatically
when the safe
is garbage-collected
.
186 my $cipher = $self->cipher;
188 $self->{counter
} = 0;
190 for my $item (@{$self->{items
}}) {
191 my $string = $item->{str
};
192 my $cleanup = erase_scoped \
$item->{val
};
194 if (is_scalarref
($string)) {
195 $$string = $cipher->crypt(\
$item->{val
});
196 if (my $encoding = $item->{enc
}) {
197 my $decoded = decode
($encoding, $string->{value
});
203 elsif (is_hashref
($string)) {
204 $string->{value
} = $cipher->crypt(\
$item->{val
});
205 if (my $encoding = $item->{enc
}) {
206 my $decoded = decode
($encoding, $string->{value
});
207 erase \
$string->{value
};
208 $string->{value
} = $decoded;
210 $str_ref = \
$string->{value
};
215 if (my $filter = $item->{filter
}) {
216 my $filtered = $filter->($$str_ref);
218 $$str_ref = $filtered;
227 $string_value = $safe->peek($string);
231 Peek into the safe at a particular string without decrypting the whole safe
. A copy of the string
is returned
,
232 and in order to ensure integrity of the memory protection you should erase the copy
when you
're done.
240 my $item = $self->{index}{refaddr($string)} // return;
242 my $cipher = $self->cipher->dup(offset => $item->{off});
244 my $value = $cipher->crypt(\$item->{val});
245 if (my $encoding = $item->{enc}) {
246 my $decoded = decode($encoding, $value);
255 $cipher = $safe->cipher;
257 Get the L<File::KDBX::Cipher::Stream> protecting a safe.
263 $self->{cipher} //= do {
264 require File::KDBX::Cipher;
265 File::KDBX::Cipher->new(stream_id => STREAM_ID_CHACHA20, key => random_bytes(64));
274 use File::KDBX::Safe;
276 $safe = File::KDBX::Safe->new;
278 my $msg = 'Secret text
';
280 # $msg is now undef, the original message no longer in RAM
282 my $obj = { value => 'Also secret
' };
284 # $obj is now { value => undef }
286 say $safe->peek($msg); # Secret text
289 say $msg; # Secret text
290 say $obj->{value}; # Also secret
294 This module provides memory protection functionality. It keeps strings encrypted in memory and decrypts them
295 as-needed. Encryption and decryption is done using a L<File::KDBX::Cipher::Stream>.
297 A safe can protect one or more (possibly many) strings. When a string is added to a safe, it gets added to an
298 internal list so it will be decrypted when the entire safe is unlocked.