Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Browse files

Merge pull request #2 from LoonyPandora/master

Make compatible with other Digest::Modules
  • Loading branch information...
commit f9c588df92794d26cc7bcc6b08d95dd2380806b8 2 parents f0d67f6 + 00e1dd1
@amiri authored
View
55 CHANGES
@@ -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
+
View
2  dist.ini
@@ -4,7 +4,7 @@ license = Perl_5
copyright_holder = Amiri Barksdale
copyright_year = 2011
-version = 0.009
+version = 1.0.0
[@Basic]
[InstallGuide]
View
250 lib/Digest/PBKDF2.pm
@@ -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.
View
98 t/01_basic.t 100644 → 100755
@@ -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";
View
14 t/02_methods.t
@@ -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/);
View
51 t/03_cloning.t
@@ -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"
+);
+
View
46 t/04_create digests.t
@@ -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");
+
+
View
19 t/05_error_handling.t
@@ -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');
Please sign in to comment.
Something went wrong with that request. Please try again.