Skip to content
This repository

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
  • 10 commits
  • 8 files changed
  • 0 comments
  • 2 contributors
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
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
May 10, 2012
Amiri Barksdale Merge pull request #2 from LoonyPandora/master
Make compatible with other Digest::Modules
f9c588d
55 CHANGES
... ... @@ -0,0 +1,55 @@
  1 +1.0.0 2012-04-04
  2 +
  3 + [ DOCUMENTATION ]
  4 + * Added example usage to all methods
  5 + (James Aitken)
  6 +
  7 + [ FEATURE ]
  8 + * The digest method now matches other Digest:: namespace modules,
  9 + and only returns the digest.
  10 +
  11 + This means specifing the encoding when creating a new Digest::PBKDF2
  12 + object is no longer necessary or supported.
  13 +
  14 + To access the different encodings, one should use the new as_ldap
  15 + and as_crypt methods
  16 +
  17 + This is a backwards incompatible change.
  18 + (James Aitken)
  19 +
  20 + * Added as_ldap and as_crypt methods to mitigate the changing of the
  21 + digest method to only return the digest
  22 + (James Aitken)
  23 +
  24 + * Added salt method to specify the salt directly, rather than parsing
  25 + it from the data that was added, simplifying the interface. You must
  26 + explicitly set the empty string to create a salt-less hash.
  27 +
  28 + The salt method acts as a getter to read the salt as currently set
  29 + (James Aitken)
  30 +
  31 + [ BUGFIX ]
  32 + * hexdigest and b64digest now correctly return the encoded version of the digest
  33 + (James Aitken)
  34 +
  35 + [ TESTS ]
  36 + * Updated tests and split into multiple test files for readability
  37 + (James Aitken)
  38 +
  39 +
  40 +0.009 2011-08-05
  41 +
  42 +0.008 2011-08-03
  43 +
  44 +0.007 2011-08-03
  45 +
  46 +0.006 2011-07-25
  47 +
  48 +0.005 2011-02-18
  49 +
  50 +0.004 2011-02-18
  51 +
  52 +0.003 2011-02-15
  53 +
  54 +0.001 2011-02-11
  55 +
2  dist.ini
@@ -4,7 +4,7 @@ license = Perl_5
4 4 copyright_holder = Amiri Barksdale
5 5 copyright_year = 2011
6 6
7   -version = 0.009
  7 +version = 1.0.0
8 8
9 9 [@Basic]
10 10 [InstallGuide]
250 lib/Digest/PBKDF2.pm
... ... @@ -1,65 +1,126 @@
1 1 package Digest::PBKDF2;
2 2
  3 +# ABSTRACT: A minimalist Digest module using the PBKDF2 algorithm.
  4 +
3 5 use strict;
4   -use warnings;
  6 +
5 7 use parent "Digest::base";
  8 +use Carp qw(croak);
6 9 use Crypt::PBKDF2 0.112020;
7 10
8   -BEGIN {
9   - # VERSION
10   -}
11   -
12   -#ABSTRACT: This module is a subclass of Digest using the Crypt::PBKDF2 algorithm.
  11 +# VERSION
13 12
14 13 sub new {
15   - my ( $class, %params ) = @_;
16   - my $encoding = $params{encoding} || 'crypt';
17   - return bless { _entries => [], _data => undef, encoding => $encoding }, $class;
  14 + my $class = shift;
  15 +
  16 + return bless {
  17 + _data => '',
  18 + }, ref($class) || $class;
18 19 }
19 20
20   -sub clone {
21   - my $self = shift;
22   - my $clone = {
23   - _data => $self->{_data},
24   - _entries => $self->{_entries},
25   - encoding => $self->{encoding},
26   - };
27   - return bless $clone, ref $self;
  21 +
  22 +sub as_crypt {
  23 + my $self = shift;
  24 +
  25 + $self->{_encoding} = 'crypt';
  26 +
  27 + return $self->_digest_with_encoding;
28 28 }
29 29
30   -sub add {
  30 +
  31 +sub as_ldap {
31 32 my $self = shift;
32   - if (@_) {
33   - push @{ $self->{_entries} }, join '', @_;
34   - $self->{_data} .= join '', @_;
  33 +
  34 + $self->{_encoding} = 'ldap';
  35 +
  36 + return $self->_digest_with_encoding;
  37 +}
  38 +
  39 +sub salt {
  40 + my ($self, $salt) = @_;
  41 +
  42 + # Salt set to the empty string is valid, though strongly discouraged.
  43 + # It is only accepted for backwards compatibility.
  44 + if (defined $salt) {
  45 + $self->{salt} = $salt;
  46 + return $self;
35 47 }
36   - $self;
  48 +
  49 + return $self->{salt};
  50 +}
  51 +
  52 +
  53 +sub clone {
  54 + my $self = shift;
  55 +
  56 + return bless {
  57 + salt => $self->salt,
  58 + _encoding => $self->{_encoding},
  59 + _data => $self->{_data},
  60 + }, ref($self);
37 61 }
38 62
  63 +
39 64 sub reset {
40 65 my $self = shift;
  66 +
  67 + delete $self->{salt};
  68 + delete $self->{_encoding};
41 69 delete $self->{_data};
42   - delete $self->{_entries};
43   - delete $self->{encoding};
44   - $self;
  70 +
  71 + return $self->new;
  72 +}
  73 +
  74 +
  75 +sub add {
  76 + my $self = shift;
  77 +
  78 + $self->{_data} .= join('', @_);
  79 +
  80 + return $self;
45 81 }
46 82
  83 +
47 84 sub digest {
48 85 my $self = shift;
49   - my @string = split '', $self->{_data};
50 86
51   - my $salt;
  87 + my $hash = $self->_crypt->PBKDF2($self->salt, $self->{_data});
52 88
53   - $salt = join( '', splice( @string, 0, length( $self->{_entries}->[0] ) ) )
54   - if @{ $self->{_entries} } > 1;
55   - my $data = join( '', @string );
  89 + $self->reset;
  90 +
  91 + return $hash;
  92 +}
  93 +
  94 +
  95 +# Returns the digest, salt, and algorithm as a crypt or ldap string
  96 +sub _digest_with_encoding {
  97 + my $self = shift;
  98 +
  99 + my $hash = $self->_crypt->generate($self->{_data}, $self->salt);
56 100
57   - my $crypt = Crypt::PBKDF2->new( encoding => $self->{encoding}, salt_len => length($salt||'') );
58   - my $return = $crypt->generate( $data, $salt );
59 101 $self->reset;
60   - $return;
  102 +
  103 + return $hash;
  104 +}
  105 +
  106 +
  107 +# Returns a Crypt::PBKDF2 object, with the encoding set as required
  108 +sub _crypt {
  109 + my $self = shift;
  110 +
  111 + if (!defined $self->salt) {
  112 + croak "Salt must be specified. If you want no salt, you must explicitly set it to the empty string";
  113 + }
  114 +
  115 + if ($self->{_encoding}) {
  116 + return Crypt::PBKDF2->new(encoding => $self->{_encoding});
  117 + }
  118 +
  119 + return Crypt::PBKDF2->new;
61 120 }
62 121
  122 +
  123 +
63 124 1;
64 125
65 126 __END__
@@ -67,26 +128,33 @@ __END__
67 128 =head1 NAME
68 129
69 130 Digest::PBKDF2
  131 +
70 132 A minimalist Digest module using the PBKDF2 algorithm.
71 133
72   -=head1 NOTICE
  134 +=head1 SYNOPSIS
  135 +
  136 + # via the Digest module (recommended)
  137 + use Digest;
73 138
74   -You can only use one salt, a pre-salt, with this module. It is not smart enough
75   -to do post-salts.
  139 + my $pbkdf2 = Digest->new('PBKDF2');
76 140
77   -=head1 SYNOPSIS
  141 + # $salt is essential, and should be cryptographically random
  142 + $pbkdf2->salt($salt);
  143 +
  144 + $pbkdf2->add('password'); # password = 'password'
  145 + $pbkdf2->add('extension'); # password = 'passwordextension'
78 146
79   - my $digest = Digest::PBKDF2->new; # Or...
80   - my $digest = Digest::PBKDF2->new(encoding => 'ldap');
81   - $digest->add('mysalt'); # salt = 'mysalt'
82   - $digest->add('k3wLP@$$w0rd'); # password = 'k3wLP@$$w0rd'
  147 + $digest = $pbkdf2->digest; # Binary version, 20 bytes
  148 + $digest = $pbkdf2->hexdigest; # Hex-encoded, 40 bytes
  149 + $digest = $pbkdf2->b64digest; # base64 encoded with no padding. 27 bytes
83 150
84   - $digest->add('eX+ens10n'); # password = 'k3wLP@$$w0rdeX+ens10n'
  151 + # [...]
85 152
86   - my $result = $digest->digest; # $PBKDF2$HMACSHA1:1000:bXlzYWx0$4P9pwp
87   - # LoF+eq5jwUbMw05qRQyZs=
  153 + # Using the module directly (same interface as above)
88 154
89   -That's about it.
  155 + use Digest::PBKDF2;
  156 +
  157 + my $pbkdf2 = Digest::PBKDF2->new();
90 158
91 159 =head1 METHODS
92 160
@@ -94,28 +162,100 @@ That's about it.
94 162
95 163 =item new
96 164
97   -Create a new Digest::PBKDF2 object. This defaults to using the "crypt" encoding
98   -available in Crypt::PBKDF2--please see L<Crypt::PBKDF2> for details.
  165 + my $pbkdf2 = Digest->new('PBKDF2');
  166 +
  167 +Creates a new C<Digest::PBKDF2> object.
  168 +
  169 +You can also use this module directly
  170 +
  171 + my $pbkdf2 = Digest::PBKDF2->new();
99 172
100 173 =item clone
101 174
102   -Copies the data and state from the original Digest::PBKDF2 object,
  175 + my $pbkdf2->clone;
  176 +
  177 +Copies the data and state from the original C<Digest::PBKDF2> object,
103 178 and returns a new object.
104 179
105 180 =item add
106 181
107   -Pass this method your salt and data chunks. They are stored up
108   -until you call digest.
  182 + $pbkdf2->add("a"); $pbkdf2->add("b"); $pbkdf2->add("c");
  183 + $pbkdf2->add("a")->add("b")->add("c");
  184 + $pbkdf2->add("a", "b", "c");
  185 + $pbkdf2->add("abc");
  186 +
  187 +Adds data to the message we are calculating the digest for.
  188 +
  189 +All the above examples have the same effect
  190 +
  191 +=item salt
  192 +
  193 + $pbkdf2->salt($salt);
  194 +
  195 +Sets the value to be used as a salt. You must specify a salt.
  196 +
  197 +It is recommenced that you use a module like L<Data::Entropy::Algorithms> to
  198 +provide a truly randomised salt.
  199 +
  200 +When called with no arguments, will return the whatever is the current salt
109 201
110 202 =item digest
111 203
112   -This encrypts your data and returns the encrypted string.
  204 + $pbkdf2->digest;
  205 +
  206 +Return the binary digest for the message.
  207 +
  208 +The returned string will be 20 bytes long.
  209 +
  210 +=item hexdigest
  211 +
  212 + $pbkdf2->hexdigest;
  213 +
  214 +Same as L</"digest">, but will return the digest in hexadecimal form.
  215 +
  216 +The C<length> of the returned string will be 40 and will only contain
  217 +characters from the ranges C<'0'..'9'> and C<'a'..'f'>.
  218 +
  219 +=item b64digest
  220 +
  221 + $pbkdf2->b64digest;
  222 +
  223 +Same as L</"digest">, but will return the digest base64 encoded.
  224 +
  225 +The C<length> of the returned string will be 27 and will only contain characters
  226 +from the ranges C<'0'..'9'>, C<'A'..'Z'>, C<'a'..'z'>, C<'+'>, and C<'.'>
  227 +
  228 +The base64 encoded string returned is not padded to be a multiple of 4 bytes long.
113 229
114 230 =item reset
115 231
116   -After calling digest, the module calls reset on its self,
117   -clearing data and the record of how many additions were made to the data
118   -to be digested.
  232 + $pbkdf2->reset;
  233 +
  234 +Resets the object to the same internal state it was in when it was constructed.
  235 +
  236 +=item as_crypt
  237 +
  238 + $pbkdf2->as_crypt;
  239 +
  240 +Returns the digest, salt, and algorithm as a string that is similar to that used
  241 +by the C<crypt()> function. Example:
  242 +
  243 + $PBKDF2$HMACSHA1:1000:4q9OTg==$9Pb6bCRgnct/dga+4v4Lyv8x31s=
  244 +
  245 +The output of this method is the same as the outpout from the
  246 +C<generate> function of L<Crypt::PBKDF2> when using the C<crypt> encoding method
  247 +
  248 +=item as_ldap
  249 +
  250 + $pbkdf2->as_ldap;
  251 +
  252 +Returns the digest, salt, and algorithm as a string that is intended to be
  253 +compatible with RFC 2307. Example:
  254 +
  255 + {X-PBKDF2}HMACSHA1:AAAD6A:8ODUPA==:1HSdSVVwlWSZhbPGO7GIZ4iUbrk=
  256 +
  257 +The output of this method is the same as the outpout from the
  258 +C<generate> function of L<Crypt::PBKDF2> when using the C<crypt> encoding method
119 259
120 260 =back
121 261
@@ -128,6 +268,8 @@ L<Digest>
128 268
129 269 Amiri Barksdale, E<lt>abarksdale@campusexplorer.comE<gt>
130 270
  271 +James Aitken, E<lt>jaitken@cpan.orgE<gt>
  272 +
131 273 =head1 COPYRIGHT
132 274
133 275 Copyright (c) 2011 by Campus Explorer, Inc.
98 t/01_basic.t 100644 → 100755
... ... @@ -1,101 +1,7 @@
1   -#!/usr/bin/env perl
  1 +use Test::More tests => 1;
2 2
3 3 use strict;
4 4 use warnings;
5   -use Test::More qw/no_plan/;
6   -use Test::Exception;
7   -use Scalar::Util qw/refaddr/;
8   -use lib "lib";
9   -use_ok "Digest::PBKDF2";
10   -
11   -### Base class testing
12   -
13   -my $rawsalted = Crypt::PBKDF2->new;
14   -my $rawunsalted = Crypt::PBKDF2->new;
15   -
16   -is( $rawsalted->salt_len, 4, "By default my salt_len is 4" );
17   -throws_ok { $rawsalted->salt_len(5) } qr/read-only accessor/,
18   - "I cannot change my salt_len";
19   -
20   -my $salted = $rawsalted->generate('bebop');
21   -my $unsalted = $rawunsalted->generate( 'bebop', salt => undef );
22   -
23   -isnt( $salted, $unsalted, "The unsalted does not equal the salted" );
24   -
25   -ok( $rawsalted->validate( $salted, 'bebop' ),
26   - "I can validate my salted password without specifying the salt" );
27   -ok( $rawunsalted->validate( $unsalted, 'bebop' ),
28   - "I can validate my unsalted password"
29   -);
30   -
31   -### My module testing
32   -
33   -my $orig = Digest::PBKDF2->new;
34   -can_ok( $orig, qw/new clone add digest/ );
35   -
36   -diag "I will try a password_pre_salt first";
37   -lives_ok( sub { $orig->add('cool') }, "I can add one chunk" );
38 5
39   -lives_ok( sub { $orig->add('jazz') }, "I can add another chunk" );
40 6
41   -my $clone;
42   -lives_ok( sub { $clone = $orig->clone }, "I can clone my object" );
43   -isnt(
44   - refaddr $orig,
45   - refaddr $clone,
46   - "Cloning gives me a new Digest::PBKDF2 object"
47   -);
48   -isnt(
49   - refaddr \$orig->{_data},
50   - refaddr \$clone->{_data},
51   - "Cloning gives me a new data slot"
52   -);
53   -lives_ok( sub { delete $clone->{_data} },
54   - "I can delete the data in my clone" );
55   -is( $clone->{_data}, undef, "And the data is gone" );
56   -is( $orig->{_data}, 'cooljazz', "And the original remains intact" );
57   -lives_ok( sub { $clone->add('cooljazz') }, "I can put back the clone data" );
58   -
59   -###
60   -my ( $clone_digest, $clone2_digest, $orig_digest, $orig2_digest );
61   -
62   -( $clone_digest, $orig_digest ) = ( $clone->digest, $orig->digest );
63   -
64   -is( $clone_digest, $orig_digest,
65   - "Clone and orginal produce the same string" );
66   -
67   -is( $clone_digest,
68   - '$PBKDF2$HMACSHA1:1000:Y29vbA==$6LZU9raZ5BzrMRo0mwa8Z7ON+Mc=',
69   - "And that string is what it should be"
70   -);
71   -is( $orig_digest,
72   - '$PBKDF2$HMACSHA1:1000:Y29vbA==$6LZU9raZ5BzrMRo0mwa8Z7ON+Mc=',
73   - "Making sure it is..."
74   -);
75   -
76   -### No salt
77   -
78   -diag "I will try no salt this time";
79   -
80   -my $orig2 = Digest::PBKDF2->new;
81   -
82   -lives_ok( sub { $orig2->add('jazz') }, "I can add the password chunk" );
83   -
84   -my $clone2;
85   -lives_ok( sub { $clone2 = $orig2->clone }, "I can clone my object" );
86   -isnt(
87   - refaddr $orig2,
88   - refaddr $clone2,
89   - "Cloning gives me a new Digest::PBKDF2 object"
90   -);
91   -( $clone2_digest, $orig2_digest ) = ( $clone2->digest, $orig2->digest );
92   -is( $clone2_digest, $orig2_digest,
93   - "Clone and orginal produce the same string" );
94   -is( $clone2_digest,
95   - '$PBKDF2$HMACSHA1:1000:$zpYCcE4kGAQD37LhEQa56B7/kCc=',
96   - "And that string is what it should be"
97   -);
98   -is( $orig2_digest,
99   - '$PBKDF2$HMACSHA1:1000:$zpYCcE4kGAQD37LhEQa56B7/kCc=',
100   - "Making sure it is..."
101   -);
  7 +use_ok "Digest::PBKDF2";
14 t/02_methods.t
... ... @@ -0,0 +1,14 @@
  1 +use Test::More tests => 2;
  2 +
  3 +use strict;
  4 +use warnings;
  5 +
  6 +use Digest;
  7 +use Digest::PBKDF2;
  8 +
  9 +
  10 +my $direct = Digest::PBKDF2->new;
  11 +can_ok($direct, qw/new clone add digest hexdigest b64digest salt as_crypt as_ldap reset/);
  12 +
  13 +my $indirect = Digest->new('PBKDF2');
  14 +can_ok($indirect, qw/new clone add digest hexdigest b64digest salt as_crypt as_ldap reset/);
51 t/03_cloning.t
... ... @@ -0,0 +1,51 @@
  1 +use Test::More tests => 12;
  2 +
  3 +use strict;
  4 +use warnings;
  5 +
  6 +use Digest::PBKDF2;
  7 +use Scalar::Util qw(refaddr);
  8 +use Test::Exception;
  9 +
  10 +
  11 +my $orig = Digest::PBKDF2->new;
  12 +
  13 +lives_ok( sub { $orig->add('cool') }, "I can add one chunk" );
  14 +lives_ok( sub { $orig->add('jazz') }, "I can add another chunk" );
  15 +lives_ok( sub { $orig->salt('salt') }, "I can add salt" );
  16 +
  17 +my $clone;
  18 +
  19 +lives_ok( sub { $clone = $orig->clone }, "I can clone my object" );
  20 +
  21 +isnt(
  22 + refaddr $orig,
  23 + refaddr $clone,
  24 + "Cloning gives me a new Digest::PBKDF2 object"
  25 +);
  26 +
  27 +isnt(
  28 + refaddr \$orig->{_data},
  29 + refaddr \$clone->{_data},
  30 + "Cloning gives me a new data slot"
  31 +);
  32 +
  33 +lives_ok(sub { delete $clone->{_data} }, "I can delete the data in my clone");
  34 +
  35 +is($clone->{_data}, undef, "And the data is gone");
  36 +
  37 +is($orig->{_data}, 'cooljazz', "And the original remains intact");
  38 +
  39 +lives_ok(sub { $clone->add('cooljazz') }, "I can put back the clone data");
  40 +
  41 +
  42 +my ($clone_digest, $orig_digest) = ($clone->hexdigest, $orig->hexdigest);
  43 +
  44 +is($clone_digest, $orig_digest, "Clone and orginal produce the same string");
  45 +
  46 +is(
  47 + $orig_digest,
  48 + 'ec97d051d529a3d016e17d7a71a69c1124e37f89',
  49 + "And that string is what it should be"
  50 +);
  51 +
46 t/04_create digests.t
... ... @@ -0,0 +1,46 @@
  1 +use Test::More tests => 6;
  2 +
  3 +use strict;
  4 +use warnings;
  5 +
  6 +use Digest::PBKDF2;
  7 +
  8 +# $ctx is reset after each digest, hence why we re-add the salt / data
  9 +my $ctx = Digest::PBKDF2->new;
  10 +
  11 +$ctx->add('cool');
  12 +$ctx->salt('salt');
  13 +
  14 +ok($ctx->digest, "Creates Binary Digest");
  15 +
  16 +
  17 +$ctx->add('cool');
  18 +$ctx->salt('');
  19 +
  20 +ok($ctx->hexdigest eq '7d564b4dd7566702bbea294abc256b7ecff7dc69', "Creates Correct Digest With Empty Salt");
  21 +
  22 +
  23 +$ctx->add('cool');
  24 +$ctx->salt('salt');
  25 +
  26 +ok($ctx->hexdigest eq '889a3bf0c83f691ed3dd09be4dca141a561fbb90', "Creates Correct Hex Digest");
  27 +
  28 +
  29 +$ctx->add('cool');
  30 +$ctx->salt('salt');
  31 +
  32 +ok($ctx->b64digest eq 'iJo78Mg/aR7T3Qm+TcoUGlYfu5A', "Creates Correct Base 64 Digest");
  33 +
  34 +
  35 +$ctx->add('cool');
  36 +$ctx->salt('salt');
  37 +
  38 +ok($ctx->as_crypt eq '$PBKDF2$HMACSHA1:1000:c2FsdA==$iJo78Mg/aR7T3Qm+TcoUGlYfu5A=', "Creates Correct Crypt Digest");
  39 +
  40 +
  41 +$ctx->add('cool');
  42 +$ctx->salt('salt');
  43 +
  44 +ok($ctx->as_ldap eq '{X-PBKDF2}HMACSHA1:AAAD6A:c2FsdA==:iJo78Mg/aR7T3Qm+TcoUGlYfu5A=', "Creates Correct LDAP / RFC 2307 Digest");
  45 +
  46 +
19 t/05_error_handling.t
... ... @@ -0,0 +1,19 @@
  1 +use Test::More tests => 4;
  2 +
  3 +use strict;
  4 +use warnings;
  5 +
  6 +use Digest::PBKDF2;
  7 +use Test::Exception;
  8 +
  9 +
  10 +my $ctx = Digest::PBKDF2->new;
  11 +
  12 +$ctx->add('cool');
  13 +
  14 +throws_ok(sub { $ctx->digest }, qr/Salt must be specified/, 'Dies when salt not specified');
  15 +
  16 +lives_ok(sub { $ctx->salt('foo') }, 'Can set salt');
  17 +lives_ok(sub { $ctx->salt('bar') }, 'Can re-set the salt to another value');
  18 +
  19 +is($ctx->salt, 'bar', 'Salt method replaces data');

No commit comments for this range

Something went wrong with that request. Please try again.