Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP

We’re showing branches in this repository, but you can also compare across forks.

base fork: amiri/Digest-PBKDF2
base: f0d67f626d
...
head fork: amiri/Digest-PBKDF2
compare: f9c588df92
  • 10 commits
  • 8 files changed
  • 0 commit comments
  • 2 contributors
Commits on Apr 03, 2012
James Aitken LoonyPandora Change digest method to return the digest alone.
First draft of changes, still need to write tests and update documentation
b7bb46d
James Aitken LoonyPandora Update documentation to reflect current features 9186de9
Commits on Apr 04, 2012
James Aitken LoonyPandora Clarify usage of the salt method
Also revert to using the VERSION number provided when building via Dist::Zilla
c6f3e49
James Aitken LoonyPandora Add tests for new functionality 7c98e54
James Aitken LoonyPandora Bump version number b0253d1
James Aitken LoonyPandora Centralise error handling when no salt is specified
Slight refactor to improve creation of Crypt::PBKDF2 object. It's a shame the encoding method is readonly in that module, else this would be even more simple.
d35d2b0
James Aitken LoonyPandora This is a backwards incompatible change - bump the major version number cb89329
James Aitken LoonyPandora Add a bare-bones CHANGES file 1dcd293
James Aitken LoonyPandora Typo fixes 00e1dd1
Commits on May 11, 2012
Amiri Barksdale Merge pull request #2 from LoonyPandora/master
Make compatible with other Digest::Modules
f9c588d
55 CHANGES
View
@@ -0,0 +1,55 @@
+1.0.0 2012-04-04
+
+ [ DOCUMENTATION ]
+ * Added example usage to all methods
+ (James Aitken)
+
+ [ FEATURE ]
+ * The digest method now matches other Digest:: namespace modules,
+ and only returns the digest.
+
+ This means specifing the encoding when creating a new Digest::PBKDF2
+ object is no longer necessary or supported.
+
+ To access the different encodings, one should use the new as_ldap
+ and as_crypt methods
+
+ This is a backwards incompatible change.
+ (James Aitken)
+
+ * Added as_ldap and as_crypt methods to mitigate the changing of the
+ digest method to only return the digest
+ (James Aitken)
+
+ * Added salt method to specify the salt directly, rather than parsing
+ it from the data that was added, simplifying the interface. You must
+ explicitly set the empty string to create a salt-less hash.
+
+ The salt method acts as a getter to read the salt as currently set
+ (James Aitken)
+
+ [ BUGFIX ]
+ * hexdigest and b64digest now correctly return the encoded version of the digest
+ (James Aitken)
+
+ [ TESTS ]
+ * Updated tests and split into multiple test files for readability
+ (James Aitken)
+
+
+0.009 2011-08-05
+
+0.008 2011-08-03
+
+0.007 2011-08-03
+
+0.006 2011-07-25
+
+0.005 2011-02-18
+
+0.004 2011-02-18
+
+0.003 2011-02-15
+
+0.001 2011-02-11
+
2  dist.ini
View
@@ -4,7 +4,7 @@ license = Perl_5
copyright_holder = Amiri Barksdale
copyright_year = 2011
-version = 0.009
+version = 1.0.0
[@Basic]
[InstallGuide]
250 lib/Digest/PBKDF2.pm
View
@@ -1,65 +1,126 @@
package Digest::PBKDF2;
+# ABSTRACT: A minimalist Digest module using the PBKDF2 algorithm.
+
use strict;
-use warnings;
+
use parent "Digest::base";
+use Carp qw(croak);
use Crypt::PBKDF2 0.112020;
-BEGIN {
- # VERSION
-}
-
-#ABSTRACT: This module is a subclass of Digest using the Crypt::PBKDF2 algorithm.
+# VERSION
sub new {
- my ( $class, %params ) = @_;
- my $encoding = $params{encoding} || 'crypt';
- return bless { _entries => [], _data => undef, encoding => $encoding }, $class;
+ my $class = shift;
+
+ return bless {
+ _data => '',
+ }, ref($class) || $class;
}
-sub clone {
- my $self = shift;
- my $clone = {
- _data => $self->{_data},
- _entries => $self->{_entries},
- encoding => $self->{encoding},
- };
- return bless $clone, ref $self;
+
+sub as_crypt {
+ my $self = shift;
+
+ $self->{_encoding} = 'crypt';
+
+ return $self->_digest_with_encoding;
}
-sub add {
+
+sub as_ldap {
my $self = shift;
- if (@_) {
- push @{ $self->{_entries} }, join '', @_;
- $self->{_data} .= join '', @_;
+
+ $self->{_encoding} = 'ldap';
+
+ return $self->_digest_with_encoding;
+}
+
+sub salt {
+ my ($self, $salt) = @_;
+
+ # Salt set to the empty string is valid, though strongly discouraged.
+ # It is only accepted for backwards compatibility.
+ if (defined $salt) {
+ $self->{salt} = $salt;
+ return $self;
}
- $self;
+
+ return $self->{salt};
+}
+
+
+sub clone {
+ my $self = shift;
+
+ return bless {
+ salt => $self->salt,
+ _encoding => $self->{_encoding},
+ _data => $self->{_data},
+ }, ref($self);
}
+
sub reset {
my $self = shift;
+
+ delete $self->{salt};
+ delete $self->{_encoding};
delete $self->{_data};
- delete $self->{_entries};
- delete $self->{encoding};
- $self;
+
+ return $self->new;
+}
+
+
+sub add {
+ my $self = shift;
+
+ $self->{_data} .= join('', @_);
+
+ return $self;
}
+
sub digest {
my $self = shift;
- my @string = split '', $self->{_data};
- my $salt;
+ my $hash = $self->_crypt->PBKDF2($self->salt, $self->{_data});
- $salt = join( '', splice( @string, 0, length( $self->{_entries}->[0] ) ) )
- if @{ $self->{_entries} } > 1;
- my $data = join( '', @string );
+ $self->reset;
+
+ return $hash;
+}
+
+
+# Returns the digest, salt, and algorithm as a crypt or ldap string
+sub _digest_with_encoding {
+ my $self = shift;
+
+ my $hash = $self->_crypt->generate($self->{_data}, $self->salt);
- my $crypt = Crypt::PBKDF2->new( encoding => $self->{encoding}, salt_len => length($salt||'') );
- my $return = $crypt->generate( $data, $salt );
$self->reset;
- $return;
+
+ return $hash;
+}
+
+
+# Returns a Crypt::PBKDF2 object, with the encoding set as required
+sub _crypt {
+ my $self = shift;
+
+ if (!defined $self->salt) {
+ croak "Salt must be specified. If you want no salt, you must explicitly set it to the empty string";
+ }
+
+ if ($self->{_encoding}) {
+ return Crypt::PBKDF2->new(encoding => $self->{_encoding});
+ }
+
+ return Crypt::PBKDF2->new;
}
+
+
1;
__END__
@@ -67,26 +128,33 @@ __END__
=head1 NAME
Digest::PBKDF2
+
A minimalist Digest module using the PBKDF2 algorithm.
-=head1 NOTICE
+=head1 SYNOPSIS
+
+ # via the Digest module (recommended)
+ use Digest;
-You can only use one salt, a pre-salt, with this module. It is not smart enough
-to do post-salts.
+ my $pbkdf2 = Digest->new('PBKDF2');
-=head1 SYNOPSIS
+ # $salt is essential, and should be cryptographically random
+ $pbkdf2->salt($salt);
+
+ $pbkdf2->add('password'); # password = 'password'
+ $pbkdf2->add('extension'); # password = 'passwordextension'
- my $digest = Digest::PBKDF2->new; # Or...
- my $digest = Digest::PBKDF2->new(encoding => 'ldap');
- $digest->add('mysalt'); # salt = 'mysalt'
- $digest->add('k3wLP@$$w0rd'); # password = 'k3wLP@$$w0rd'
+ $digest = $pbkdf2->digest; # Binary version, 20 bytes
+ $digest = $pbkdf2->hexdigest; # Hex-encoded, 40 bytes
+ $digest = $pbkdf2->b64digest; # base64 encoded with no padding. 27 bytes
- $digest->add('eX+ens10n'); # password = 'k3wLP@$$w0rdeX+ens10n'
+ # [...]
- my $result = $digest->digest; # $PBKDF2$HMACSHA1:1000:bXlzYWx0$4P9pwp
- # LoF+eq5jwUbMw05qRQyZs=
+ # Using the module directly (same interface as above)
-That's about it.
+ use Digest::PBKDF2;
+
+ my $pbkdf2 = Digest::PBKDF2->new();
=head1 METHODS
@@ -94,28 +162,100 @@ That's about it.
=item new
-Create a new Digest::PBKDF2 object. This defaults to using the "crypt" encoding
-available in Crypt::PBKDF2--please see L<Crypt::PBKDF2> for details.
+ my $pbkdf2 = Digest->new('PBKDF2');
+
+Creates a new C<Digest::PBKDF2> object.
+
+You can also use this module directly
+
+ my $pbkdf2 = Digest::PBKDF2->new();
=item clone
-Copies the data and state from the original Digest::PBKDF2 object,
+ my $pbkdf2->clone;
+
+Copies the data and state from the original C<Digest::PBKDF2> object,
and returns a new object.
=item add
-Pass this method your salt and data chunks. They are stored up
-until you call digest.
+ $pbkdf2->add("a"); $pbkdf2->add("b"); $pbkdf2->add("c");
+ $pbkdf2->add("a")->add("b")->add("c");
+ $pbkdf2->add("a", "b", "c");
+ $pbkdf2->add("abc");
+
+Adds data to the message we are calculating the digest for.
+
+All the above examples have the same effect
+
+=item salt
+
+ $pbkdf2->salt($salt);
+
+Sets the value to be used as a salt. You must specify a salt.
+
+It is recommenced that you use a module like L<Data::Entropy::Algorithms> to
+provide a truly randomised salt.
+
+When called with no arguments, will return the whatever is the current salt
=item digest
-This encrypts your data and returns the encrypted string.
+ $pbkdf2->digest;
+
+Return the binary digest for the message.
+
+The returned string will be 20 bytes long.
+
+=item hexdigest
+
+ $pbkdf2->hexdigest;
+
+Same as L</"digest">, but will return the digest in hexadecimal form.
+
+The C<length> of the returned string will be 40 and will only contain
+characters from the ranges C<'0'..'9'> and C<'a'..'f'>.
+
+=item b64digest
+
+ $pbkdf2->b64digest;
+
+Same as L</"digest">, but will return the digest base64 encoded.
+
+The C<length> of the returned string will be 27 and will only contain characters
+from the ranges C<'0'..'9'>, C<'A'..'Z'>, C<'a'..'z'>, C<'+'>, and C<'.'>
+
+The base64 encoded string returned is not padded to be a multiple of 4 bytes long.
=item reset
-After calling digest, the module calls reset on its self,
-clearing data and the record of how many additions were made to the data
-to be digested.
+ $pbkdf2->reset;
+
+Resets the object to the same internal state it was in when it was constructed.
+
+=item as_crypt
+
+ $pbkdf2->as_crypt;
+
+Returns the digest, salt, and algorithm as a string that is similar to that used
+by the C<crypt()> function. Example:
+
+ $PBKDF2$HMACSHA1:1000:4q9OTg==$9Pb6bCRgnct/dga+4v4Lyv8x31s=
+
+The output of this method is the same as the outpout from the
+C<generate> function of L<Crypt::PBKDF2> when using the C<crypt> encoding method
+
+=item as_ldap
+
+ $pbkdf2->as_ldap;
+
+Returns the digest, salt, and algorithm as a string that is intended to be
+compatible with RFC 2307. Example:
+
+ {X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk=
+
+The output of this method is the same as the outpout from the
+C<generate> function of L<Crypt::PBKDF2> when using the C<crypt> encoding method
=back
@@ -128,6 +268,8 @@ L<Digest>
Amiri Barksdale, E<lt>abarksdale@campusexplorer.comE<gt>
+James Aitken, E<lt>jaitken@cpan.orgE<gt>
+
=head1 COPYRIGHT
Copyright (c) 2011 by Campus Explorer, Inc.
98 t/01_basic.t 100644 → 100755
View
@@ -1,101 +1,7 @@
-#!/usr/bin/env perl
+use Test::More tests => 1;
use strict;
use warnings;
-use Test::More qw/no_plan/;
-use Test::Exception;
-use Scalar::Util qw/refaddr/;
-use lib "lib";
-use_ok "Digest::PBKDF2";
-
-### Base class testing
-
-my $rawsalted = Crypt::PBKDF2->new;
-my $rawunsalted = Crypt::PBKDF2->new;
-
-is( $rawsalted->salt_len, 4, "By default my salt_len is 4" );
-throws_ok { $rawsalted->salt_len(5) } qr/read-only accessor/,
- "I cannot change my salt_len";
-
-my $salted = $rawsalted->generate('bebop');
-my $unsalted = $rawunsalted->generate( 'bebop', salt => undef );
-
-isnt( $salted, $unsalted, "The unsalted does not equal the salted" );
-
-ok( $rawsalted->validate( $salted, 'bebop' ),
- "I can validate my salted password without specifying the salt" );
-ok( $rawunsalted->validate( $unsalted, 'bebop' ),
- "I can validate my unsalted password"
-);
-
-### My module testing
-
-my $orig = Digest::PBKDF2->new;
-can_ok( $orig, qw/new clone add digest/ );
-
-diag "I will try a password_pre_salt first";
-lives_ok( sub { $orig->add('cool') }, "I can add one chunk" );
-lives_ok( sub { $orig->add('jazz') }, "I can add another chunk" );
-my $clone;
-lives_ok( sub { $clone = $orig->clone }, "I can clone my object" );
-isnt(
- refaddr $orig,
- refaddr $clone,
- "Cloning gives me a new Digest::PBKDF2 object"
-);
-isnt(
- refaddr \$orig->{_data},
- refaddr \$clone->{_data},
- "Cloning gives me a new data slot"
-);
-lives_ok( sub { delete $clone->{_data} },
- "I can delete the data in my clone" );
-is( $clone->{_data}, undef, "And the data is gone" );
-is( $orig->{_data}, 'cooljazz', "And the original remains intact" );
-lives_ok( sub { $clone->add('cooljazz') }, "I can put back the clone data" );
-
-###
-my ( $clone_digest, $clone2_digest, $orig_digest, $orig2_digest );
-
-( $clone_digest, $orig_digest ) = ( $clone->digest, $orig->digest );
-
-is( $clone_digest, $orig_digest,
- "Clone and orginal produce the same string" );
-
-is( $clone_digest,
- '$PBKDF2$HMACSHA1:1000:Y29vbA==$6LZU9raZ5BzrMRo0mwa8Z7ON+Mc=',
- "And that string is what it should be"
-);
-is( $orig_digest,
- '$PBKDF2$HMACSHA1:1000:Y29vbA==$6LZU9raZ5BzrMRo0mwa8Z7ON+Mc=',
- "Making sure it is..."
-);
-
-### No salt
-
-diag "I will try no salt this time";
-
-my $orig2 = Digest::PBKDF2->new;
-
-lives_ok( sub { $orig2->add('jazz') }, "I can add the password chunk" );
-
-my $clone2;
-lives_ok( sub { $clone2 = $orig2->clone }, "I can clone my object" );
-isnt(
- refaddr $orig2,
- refaddr $clone2,
- "Cloning gives me a new Digest::PBKDF2 object"
-);
-( $clone2_digest, $orig2_digest ) = ( $clone2->digest, $orig2->digest );
-is( $clone2_digest, $orig2_digest,
- "Clone and orginal produce the same string" );
-is( $clone2_digest,
- '$PBKDF2$HMACSHA1:1000:$zpYCcE4kGAQD37LhEQa56B7/kCc=',
- "And that string is what it should be"
-);
-is( $orig2_digest,
- '$PBKDF2$HMACSHA1:1000:$zpYCcE4kGAQD37LhEQa56B7/kCc=',
- "Making sure it is..."
-);
+use_ok "Digest::PBKDF2";
14 t/02_methods.t
View
@@ -0,0 +1,14 @@
+use Test::More tests => 2;
+
+use strict;
+use warnings;
+
+use Digest;
+use Digest::PBKDF2;
+
+
+my $direct = Digest::PBKDF2->new;
+can_ok($direct, qw/new clone add digest hexdigest b64digest salt as_crypt as_ldap reset/);
+
+my $indirect = Digest->new('PBKDF2');
+can_ok($indirect, qw/new clone add digest hexdigest b64digest salt as_crypt as_ldap reset/);
51 t/03_cloning.t
View
@@ -0,0 +1,51 @@
+use Test::More tests => 12;
+
+use strict;
+use warnings;
+
+use Digest::PBKDF2;
+use Scalar::Util qw(refaddr);
+use Test::Exception;
+
+
+my $orig = Digest::PBKDF2->new;
+
+lives_ok( sub { $orig->add('cool') }, "I can add one chunk" );
+lives_ok( sub { $orig->add('jazz') }, "I can add another chunk" );
+lives_ok( sub { $orig->salt('salt') }, "I can add salt" );
+
+my $clone;
+
+lives_ok( sub { $clone = $orig->clone }, "I can clone my object" );
+
+isnt(
+ refaddr $orig,
+ refaddr $clone,
+ "Cloning gives me a new Digest::PBKDF2 object"
+);
+
+isnt(
+ refaddr \$orig->{_data},
+ refaddr \$clone->{_data},
+ "Cloning gives me a new data slot"
+);
+
+lives_ok(sub { delete $clone->{_data} }, "I can delete the data in my clone");
+
+is($clone->{_data}, undef, "And the data is gone");
+
+is($orig->{_data}, 'cooljazz', "And the original remains intact");
+
+lives_ok(sub { $clone->add('cooljazz') }, "I can put back the clone data");
+
+
+my ($clone_digest, $orig_digest) = ($clone->hexdigest, $orig->hexdigest);
+
+is($clone_digest, $orig_digest, "Clone and orginal produce the same string");
+
+is(
+ $orig_digest,
+ 'ec97d051d529a3d016e17d7a71a69c1124e37f89',
+ "And that string is what it should be"
+);
+
46 t/04_create digests.t
View
@@ -0,0 +1,46 @@
+use Test::More tests => 6;
+
+use strict;
+use warnings;
+
+use Digest::PBKDF2;
+
+# $ctx is reset after each digest, hence why we re-add the salt / data
+my $ctx = Digest::PBKDF2->new;
+
+$ctx->add('cool');
+$ctx->salt('salt');
+
+ok($ctx->digest, "Creates Binary Digest");
+
+
+$ctx->add('cool');
+$ctx->salt('');
+
+ok($ctx->hexdigest eq '7d564b4dd7566702bbea294abc256b7ecff7dc69', "Creates Correct Digest With Empty Salt");
+
+
+$ctx->add('cool');
+$ctx->salt('salt');
+
+ok($ctx->hexdigest eq '889a3bf0c83f691ed3dd09be4dca141a561fbb90', "Creates Correct Hex Digest");
+
+
+$ctx->add('cool');
+$ctx->salt('salt');
+
+ok($ctx->b64digest eq 'iJo78Mg/aR7T3Qm+TcoUGlYfu5A', "Creates Correct Base 64 Digest");
+
+
+$ctx->add('cool');
+$ctx->salt('salt');
+
+ok($ctx->as_crypt eq '$PBKDF2$HMACSHA1:1000:c2FsdA==$iJo78Mg/aR7T3Qm+TcoUGlYfu5A=', "Creates Correct Crypt Digest");
+
+
+$ctx->add('cool');
+$ctx->salt('salt');
+
+ok($ctx->as_ldap eq '{X-PBKDF2}HMACSHA1:AAAD6A:c2FsdA==:iJo78Mg/aR7T3Qm+TcoUGlYfu5A=', "Creates Correct LDAP / RFC 2307 Digest");
+
+
19 t/05_error_handling.t
View
@@ -0,0 +1,19 @@
+use Test::More tests => 4;
+
+use strict;
+use warnings;
+
+use Digest::PBKDF2;
+use Test::Exception;
+
+
+my $ctx = Digest::PBKDF2->new;
+
+$ctx->add('cool');
+
+throws_ok(sub { $ctx->digest }, qr/Salt must be specified/, 'Dies when salt not specified');
+
+lives_ok(sub { $ctx->salt('foo') }, 'Can set salt');
+lives_ok(sub { $ctx->salt('bar') }, 'Can re-set the salt to another value');
+
+is($ctx->salt, 'bar', 'Salt method replaces data');

No commit comments for this range

Something went wrong with that request. Please try again.