Crypt-PBKDF2

 view release on metacpan or  search on metacpan

Changes  view on Meta::CPAN

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.



( run in 1.980 second using v1.01-cache-2.11-cpan-96521ef73a4 )