view release on metacpan or search on metacpan
Changes for Crypt::PBKDF2
Version 0.261630: 2026-06-11
* Change the default hash algorithm to HMAC-SHA256, and increase the
default number of iterations to 600,000, in line with current OWASP
recommendations (CVE-2026-9641).
* Generate salts using Crypt::URandom (a strong system RNG) instead of
perl's builtin `rand()`, which is not cryptographically secure
(CVE-2026-9638).
* Use a constant-time comparison in `validate` to avoid timing attacks
(CVE-2017-20240).
Version 0.161520: 2016-05-31
* Require an up-to-date Types::Standard to prevent errors about ConsumerOf
and Enum not being found on installation. There is no need to upgrade if
lib/Crypt/PBKDF2.pm view on Meta::CPAN
my $class = $self->hash_class;
if ($class !~ s/^\+//) {
$class = "Crypt::PBKDF2::Hash::$class";
}
my $hash_args = $self->hash_args;
return Module::Runtime::use_module($class)->new( %$hash_args );
}
has iterations => (
is => 'ro',
isa => Int,
default => 600000,
);
has output_len => (
is => 'ro',
isa => Int,
predicate => 'has_output_len',
lib/Crypt/PBKDF2.pm view on Meta::CPAN
my $hasher = try {
$self->hasher_from_algorithm($info->{algorithm}, $info->{algorithm_options});
} catch {
my $opts = defined($info->{algorithm_options}) ? " (options ''$info->{algorithm_options}'')" : "";
croak "Couldn't construct hasher for ''$info->{algorithm}''$opts: $_";
};
my $checker = $self->clone(
hasher => $hasher,
iterations => $info->{iterations},
output_len => length($info->{hash}),
);
my $check_hash = $checker->PBKDF2($info->{salt}, $password);
return _secure_compare($check_hash, $info->{hash});
}
# Constant-time string comparison, to avoid timing attacks on the hash check.
sub _secure_compare {
lib/Crypt/PBKDF2.pm view on Meta::CPAN
$a = $b if $r;
$r |= ord(substr($a, $_)) ^ ord(substr($b, $_)) for 0 .. length($a) - 1;
return $r == 0;
}
sub PBKDF2 {
my ($self, $salt, $password) = @_;
my $iterations = $self->iterations;
my $hasher = $self->hasher;
my $output_len = $self->output_len || $hasher->hash_len;
my $hLen = $hasher->hash_len;
my $l = int($output_len / $hLen);
my $r = $output_len % $hLen;
if ($l > 0xffffffff or $l == 0xffffffff && $r > 0) {
croak "output_len too large for PBKDF2";
}
my $output;
for my $i (1 .. $l) {
$output .= $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $i);
}
if ($r) {
$output .= substr( $self->_PBKDF2_F($hasher, $salt, $password, $iterations, $l + 1), 0, $r);
}
return $output;
}
sub PBKDF2_base64 {
my $self = shift;
return MIME::Base64::encode( $self->PBKDF2(@_), "" );
}
sub PBKDF2_hex {
my $self = shift;
return unpack "H*", $self->PBKDF2(@_);
}
sub _PBKDF2_F {
my ($self, $hasher, $salt, $password, $iterations, $i) = @_;
my $result =
my $hash =
$hasher->generate( $salt . pack("N", $i), $password );
for my $iter (2 .. $iterations) {
$hash = $hasher->generate( $hash, $password );
$result ^= $hash;
}
return $result;
}
sub encode_string {
my ($self, $salt, $hash) = @_;
lib/Crypt/PBKDF2.pm view on Meta::CPAN
my ($self, $salt, $hash) = @_;
my $hasher = $self->hasher;
my $hasher_class = blessed($hasher);
if (!defined $hasher_class || $hasher_class !~ s/^Crypt::PBKDF2::Hash:://) {
croak "Can't ''encode_string'' with a hasher class outside of Crypt::PBKDF2::Hash::*";
}
my $algo_string = $hasher->to_algo_string;
$algo_string = defined($algo_string) ? "{$algo_string}" : "";
return '$PBKDF2$' . "$hasher_class$algo_string:" . $self->iterations . ':'
. MIME::Base64::encode($salt, "") . '$'
. MIME::Base64::encode($hash, "");
}
sub _encode_string_ldaplike {
my ($self, $salt, $hash) = @_;
my $hasher = $self->hasher;
my $hasher_class = blessed($hasher);
if (!defined $hasher_class || $hasher_class !~ s/^Crypt::PBKDF2::Hash:://) {
croak "Can't ''encode_string'' with a hasher class outside of Crypt::PBKDF2::Hash::*";
}
my $algo_string = $hasher->to_algo_string;
$algo_string = defined($algo_string) ? "+$algo_string" : "";
return '{X-PBKDF2}' . "$hasher_class$algo_string:"
. $self->_b64_encode_int32($self->iterations) . ':'
. MIME::Base64::encode($salt, "") . ':'
. MIME::Base64::encode($hash, "");
}
sub decode_string {
my ($self, $hashed) = @_;
if ($hashed =~ /^\$PBKDF2\$/) {
return $self->_decode_string_cryptlike($hashed);
} elsif ($hashed =~ /^\{X-PBKDF2}/i) {
lib/Crypt/PBKDF2.pm view on Meta::CPAN
croak "Unrecognized hash";
}
}
sub _decode_string_cryptlike {
my ($self, $hashed) = @_;
if ($hashed !~ /^\$PBKDF2\$/) {
croak "Unrecognized hash";
}
if (my ($algorithm, $opts, $iterations, $salt, $hash) = $hashed =~
/^\$PBKDF2\$([^:}]+)(?:\{([^}]+)\})?:(\d+):([^\$]+)\$(.*)/) {
return {
algorithm => $algorithm,
algorithm_options => $opts,
iterations => $iterations,
salt => MIME::Base64::decode($salt),
hash => MIME::Base64::decode($hash),
}
} else {
croak "Invalid format";
}
}
sub _decode_string_ldaplike {
my ($self, $hashed) = @_;
if ($hashed !~ /^\{X-PBKDF2}/i) {
croak "Unrecognized hash";
}
if (my ($algo_str, $iterations, $salt, $hash) = $hashed =~
/^\{X-PBKDF2}([^:]+):([^:]{6}):([^\$]+):(.*)/i) {
my ($algorithm, $opts) = split /\+/, $algo_str;
return {
algorithm => $algorithm,
algorithm_options => $opts,
iterations => $self->_b64_decode_int32($iterations),
salt => MIME::Base64::decode($salt),
hash => MIME::Base64::decode($hash),
}
} else {
croak "Invalid format";
}
}
sub hasher_from_algorithm {
lib/Crypt/PBKDF2.pm view on Meta::CPAN
# the clone. But if it was set by the user, then we need to copy it. We're
# assuming that the hasher has no state, so it doesn't need a deep clone.
# This is true of all of the ones that I'm shipping, but if it's not true for
# you, let me know.
my %new_args = (
$self->has_hash_class ? (hash_class => $self->hash_class) : (),
$self->has_hash_args ? (hash_args => $self->hash_args) : (),
$self->has_output_len ? (output_len => $self->output_len) : (),
$self->has_lazy_hasher ? () : (hasher => $self->hasher),
iterations => $self->iterations,
salt_len => $self->salt_len,
%params,
);
return $class->new(%new_args);
}
sub _b64_encode_int32 {
my ($self, $value) = @_;
my $b64 = MIME::Base64::encode(pack("N", $value), "");
lib/Crypt/PBKDF2.pm view on Meta::CPAN
=head1 VERSION
version 0.261630
=head1 SYNOPSIS
use Crypt::PBKDF2;
my $pbkdf2 = Crypt::PBKDF2->new(
hash_class => 'HMACSHA2', # this is the default (HMAC-SHA256)
iterations => 600000, # so is this
output_len => 32, # and this
salt_len => 4, # and this.
);
my $hash = $pbkdf2->generate("s3kr1t_password");
if ($pbkdf2->validate($hash, "s3kr1t_password")) {
access_granted();
}
=head1 DESCRIPTION
PBKDF2 is a secure password hashing algorithm that uses the techniques of
"key strengthening" to make the complexity of a brute-force attack
arbitrarily high. PBKDF2 uses any other cryptographic hash or cipher (by
convention, usually HMAC-SHA1, but C<Crypt::PBKDF2> is fully pluggable), and
allows for an arbitrary number of iterations of the hashing function, and a
nearly unlimited output hash size (up to 2**32 - 1 times the size of the
output of the backend hash). The hash is salted, as any password hash should
be, and the salt may also be of arbitrary size.
=head1 ATTRIBUTES
=head2 hash_class
B<Type:> String, B<Default:> HMACSHA2
lib/Crypt/PBKDF2.pm view on Meta::CPAN
Arguments to be passed to the C<hash_class> constructor.
=head2 hasher
B<Type:> Object (must fulfill role L<Crypt::PBKDF2::Hash>), B<Default:> None.
It is also possible to provide a hash object directly; in this case the
C<hash_class> and C<hash_args> are ignored.
=head2 iterations
B<Type:> Integer, B<Default:> 600000.
The default number of iterations of the hashing function to use for the
C<generate> and C<PBKDF2> methods.
=head2 output_len
B<Type:> Integer.
The default size (in bytes, not bits) of the output hash. If a value isn't
provided, the output size depends on the C<hash_class>S< / >C<hasher>
selected, and will equal the output size of the backend hash (e.g. 20 bytes
for HMACSHA1).
lib/Crypt/PBKDF2.pm view on Meta::CPAN
=over 4
=item *
C<algorithm>: A string representing the hash algorithm used. See
L</hasher_from_algorithm ($algo_str)>.
=item *
C<iterations>: The number of iterations used.
=item *
C<salt>: The salt, in raw binary form.
=item *
C<hash>: The hash, in raw binary form.
=back
t/01-raeburn.t view on Meta::CPAN
# Tests from draft-raeburn-krb-rijndael-krb-04.txt
BEGIN {
use_ok 'Crypt::PBKDF2';
}
my $pbkdf2 = Crypt::PBKDF2->new(hash_class => 'HMACSHA1');
sub PBKDF2 {
my ($iterations, $bits, $salt, $password) = @_;
return $pbkdf2->clone(iterations => $iterations, output_len => $bits / 8)->PBKDF2_hex($salt, $password);
}
is PBKDF2(1, 128, "ATHENA.MIT.EDUraeburn", "password"),
"cdedb5281bb2f801565a1122b2563515", "raeburn 1 iter, 128-bit";
is PBKDF2(1, 256, "ATHENA.MIT.EDUraeburn", "password"),
"cdedb5281bb2f801565a1122b25635150ad1f7a04bb9f3a333ecc0e2e1f70837", "raeburn 1 iter, 256-bit";
is PBKDF2(2, 128, "ATHENA.MIT.EDUraeburn", "password"),
t/02-validate.t view on Meta::CPAN
use Test::More;
use constant TEST_PASSWORDS => 500;
BEGIN {
plan tests => 8 * TEST_PASSWORDS + 1; # + use_ok
use_ok 'Crypt::PBKDF2';
}
for my $encoding (qw(ldap crypt)) {
my $pbkdf2 = Crypt::PBKDF2->new(iterations => 40, encoding => $encoding);
for my $i (1 .. TEST_PASSWORDS) {
my $password = join "", map ["A".."Z","a".."z","0".."9"]->[rand 62], 1..8;
my $hash = $pbkdf2->generate($password);
ok $pbkdf2->validate($hash, $password), "Validate password $i: $password ($encoding)";
is length $pbkdf2->PBKDF2('test', $password), 32, "raw length $password";
is length $pbkdf2->PBKDF2_hex('test', $password), 64, "hex length $password";
is length $pbkdf2->PBKDF2_base64('test', $password), 44, "base64 length $password";
}
t/04-params.t view on Meta::CPAN
BEGIN {
plan tests => 2 * TEST_PASSWORDS + 1; # + use_ok
use_ok 'Crypt::PBKDF2';
}
for my $encoding (qw(ldap crypt)) {
my $pbkdf2 = Crypt::PBKDF2->new(
hash_class => 'HMACSHA2',
hash_args => { sha_size => 512 },
iterations => 100,
salt_len => 32,
encoding => $encoding,
);
for my $i (1 .. TEST_PASSWORDS) {
my $password = join "", map ["A".."Z","a".."z","0".."9"]->[rand 62], 1..8;
my $hash = $pbkdf2->generate($password);
ok $pbkdf2->validate($hash, $password), "Validate password $i: $password ($encoding)";
}
}
t/05-crypt-ldap.t view on Meta::CPAN
use Test::More;
BEGIN {
plan tests => 1 + 1;
use_ok 'Crypt::PBKDF2';
}
my $encoder = Crypt::PBKDF2->new(
hash_class => 'HMACSHA2',
hash_args => { sha_size => 512 },
iterations => 100,
salt_len => 32,
encoding => 'crypt',
);
my $password = "testpass";
my $hash = $encoder->generate($password);
my $decoder = Crypt::PBKDF2->new; # Defaults to LDAP encoding, different hash, etc.