String-Redactable
view release on metacpan or search on metacpan
lib/String/Redactable.pm view on Meta::CPAN
C<to_str_unsafe>:
$password->to_str_unsafe;
This is not designed to completely protect the sensitive data from
prying eyes. This is simply the UTF-8 encoded version of the value that
is XOR-ed by an object-specific key that is not stored with the object.
All of that is undone by C<to_str_unsafe>.
Beyond that, this module uses L<overload> and other tricks to prevent
the actual string from showing up in output and other strings.
=head2 Notes on serializers
C<String::Redactable> objects resist serialization to the best of
their ability. At worst, the serialization shows the internal string
for the object, which does not expose the key used to XOR the UTF-8
encoded string.
Since the XOR keys are not stored in the object (and those keys are
removed when the object goes out of scope), these values cannot be
serialized and re-inflated. But, that's what you want.
=over 4
=item * L<Data::Dumper> - cannot use C<$Data::Dump::Freezer> because that
requires the
=item * L<Storable> -
=item * JSON modules - this supports C<TO_JSON>
=item * YAML -
=back
=head2 Methods
=over 4
=item new
=cut
use overload
q("") => sub { $_[0]->placeholder },
'0+' => sub { 0 },
'-X' => sub { () },
map { $_ => sub { () } } qw(
<=> cmp
lt le gt ge eq ne
~~
)
;
my %keys = ();
my $new_key = sub ($class, $length = 512) {
state $rc = require List::Util;
substr(
join( '',
List::Util::shuffle(
map { List::Util::shuffle( 'A' .. 'Z', 'a' .. 'z', qw(= ! : ;) ) } 1 .. 25
)
),
0, $length
)
;
};
=item new( STRING )
Creates an object that hides that string by XOR-ing it with another string that
is not stored in the object, and is not a package variable.
This does not mean that the original string can't be recovered in other ways if
someone wanted to try hard enough, but it keeps you from unintentionally dumping
it into output where it shouldn't be.
=cut
sub new ($class, $string, $opts={}) {
unless( length $string ) {
carp sprintf "Argument to %s::new is zero length", __PACKAGE__;
return;
}
my $key = $opts->{'key'} // $new_key->( 5 * length $string );
my $encoded = Encode::encode( 'UTF-8', $string );
my $hidden = ($encoded ^ $key);
my $self = bless \$hidden, $class;
{ local $SIG{__WARN__} = sub {}; $keys{overload::StrVal($self)} = $key };
$self;
}
sub DESTROY ($self) {
local $SIG{__WARN__} = sub {};
delete $keys{overload::StrVal($self)};
}
=item placeholder
The value that is substituted for the actual string.
=cut
sub placeholder ( $class ) {
state $rc = require Carp;
Carp::cluck(
"Possible unintended interpolation of a redactable string",
) if warnings::enabled();
'<redacted data>'
}
=item STORABLE_freeze
Redact strings used in L<Storable>.
=cut
sub STORABLE_freeze ($self, $cloning) {
$self->placeholder;
}
=item TO_JSON
Redact the string in serializers that respect C<TO_JSON>.
=cut
sub TO_JSON {
$_[0]->placeholder;
}
=item to_str_unsafe
Returns the string that you are trying to hide.
=cut
sub to_str_unsafe ($self) {
local $SIG{__WARN__} = sub {};
my $encoded = ($$self ^ $keys{overload::StrVal($self)}) =~ s/\000+\z//r;
Encode::decode( 'UTF-8', $encoded );
}
=back
=head1 TO DO
=head1 SEE ALSO
=head1 SOURCE AVAILABILITY
This source is on Github:
http://github.com/briandfoy/string-redactable
=head1 AUTHOR
brian d foy, C<< <briandfoy@pobox.com> >>
=head1 COPYRIGHT AND LICENSE
Copyright © 2025-2026, brian d foy, All Rights Reserved.
( run in 1.523 second using v1.01-cache-2.11-cpan-39bf76dae61 )