1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 197 198 199 200 201 202 203 204 205 206
|
package DBIx::Class::EncodedColumn::Digest;
use strict;
use warnings;
use Digest;
use Encode qw( str2bytes );
our $VERSION = '0.00001';
my %digest_lengths =
(
'MD2' => { base64 => 22, binary => 16, hex => 32 },
'MD4' => { base64 => 22, binary => 16, hex => 32 },
'MD5' => { base64 => 22, binary => 16, hex => 32 },
'SHA-1' => { base64 => 27, binary => 20, hex => 40 },
'SHA-256' => { base64 => 43, binary => 32, hex => 64 },
'SHA-384' => { base64 => 64, binary => 48, hex => 96 },
'SHA-512' => { base64 => 86, binary => 64, hex => 128 },
'CRC-CCITT' => { base64 => 2, binary => 3, hex => 3 },
'CRC-16' => { base64 => 6, binary => 5, hex => 4 },
'CRC-32' => { base64 => 14, binary => 10, hex => 8 },
'Adler-32' => { base64 => 6, binary => 4, hex => 8 },
'Whirlpool' => { base64 => 86, binary => 64, hex => 128 },
'Haval-256' => { base64 => 44, binary => 32, hex => 64 },
);
my @salt_pool = ('A' .. 'Z', 'a' .. 'z', 0 .. 9, '+','/','=');
sub make_encode_sub {
my($class, $col, $args) = @_;
my $for = $args->{format} ||= 'base64';
my $alg = $args->{algorithm} ||= 'SHA-256';
my $slen = $args->{salt_length} ||= 0;
my $encode = $args->{charset};
die("Valid Digest formats are 'binary', 'hex' or 'base64'. You used '$for'.")
unless $for =~ /^(?:hex|base64|binary)$/;
defined(my $object = eval{ Digest->new($alg) }) ||
die("Can't use Digest algorithm ${alg}: $@");
my $format_method = $for eq 'binary' ? 'digest' :
($for eq 'hex' ? 'hexdigest' : 'b64digest');
#thanks Haval for breaking the standard. thanks!
$format_method = 'base64digest 'if ($alg eq 'Haval-256' && $for eq 'base64');
my $encoder = sub {
my ($plain_text, $salt) = @_;
$plain_text = str2bytes($encode, $plain_text, Encode::FB_PERLQQ | Encode::LEAVE_SRC) if $encode;
$salt ||= join('', map { $salt_pool[int(rand(65))] } 1 .. $slen);
$object->reset()->add($plain_text.$salt);
my $digest = $object->$format_method;
#print "${plain_text}\t ${salt}:\t${digest}${salt}\n" if $salt;
return $digest.$salt;
};
#in case i didn't prepopulate it
$digest_lengths{$alg}{$for} ||= length($encoder->('test1'));
return $encoder;
}
sub make_check_sub {
my($class, $col, $args) = @_;
#this is the digest length
my $len = $digest_lengths{$args->{algorithm}}{$args->{format}};
die("Unable to find digest length") unless defined $len;
my $encode = $args->{charset} || '';
#fast fast fast
return eval qq^ sub {
my \$col_v = \$_[0]->get_column('${col}');
\$col_v = str2bytes('${encode}', \$col_v, Encode::FB_PERLQQ | Encode::LEAVE_SRC) if '${encode}';
my \$salt = substr(\$col_v, ${len});
\$_[0]->_column_encoders->{${col}}->(\$_[1], \$salt) eq \$col_v;
} ^ || die($@);
}
1;
__END__;
=head1 NAME
DBIx::Class::EncodedColumn::Digest - Digest backend
=head1 SYNOPSYS
#SHA-1 / hex encoding / generate check method
__PACKAGE__->add_columns(
'password' => {
data_type => 'CHAR',
size => 40 + 10,
encode_column => 1,
encode_class => 'Digest',
encode_args => {
algorithm => 'SHA-1',
format => 'hex',
salt_length => 10,
charset => 'utf-8',
},
encode_check_method => 'check_password',
}
#SHA-256 / base64 encoding / generate check method
__PACKAGE__->add_columns(
'password' => {
data_type => 'CHAR',
size => 40,
encode_column => 1,
encode_class => 'Digest',
encode_check_method => 'check_password',
#no encode_args necessary because these are the defaults ...
}
=head1 DESCRIPTION
=head1 ACCEPTED ARGUMENTS
=head2 format
The encoding to use for the digest. Valid values are 'binary', 'hex', and
'base64'. Will default to 'base64' if not specified.
=head2 algorithm
The digest algorithm to use for the digest. You may specify any valid L<Digest>
algorithm. Examples are L<MD5|Digest::MD5>, L<SHA-1|Digest::SHA>,
L<Whirlpool|Digest::Whirlpool> etc. Will default to 'SHA-256' if not specified.
See L<Digest> for supported digest algorithms.
=head2 salt_length
If you would like to use randomly generated salts to encode values make sure
this option is set to > 0. Salts will be automatically generated at encode time
and will be appended to the end of the digest. Please make sure that you
remember to make sure that to expand the size of your db column to have enough
space to store both the digest AND the salt. Please see list below for common
digest lengths.
=head2 charset
If the string is not restricted to ASCII, then you will need to
specify a character set encoding.
See L<Encode> for a list of encodings.
=head1 METHODS
=head2 make_encode_sub $column_name, \%encode_args
Returns a coderef that takes two arguments, a plaintext value and an optional
salt and returns the encoded value with the salt appended to the end of the
digest. If a salt is not provided and the salt_length option was greater than
zero it will be randomly generated.
=head2 make_check_sub $column_name, \%encode_args
Returns a coderef that takes the row object and a plaintext value and will
return a boolean if the plaintext matches the encoded value. This is typically
used for password authentication.
=head1 COMMON DIGEST LENGTHS
CIPHER | Binary | Base64 | Hex
---------------------------------------
| MD2 | 16 | 22 | 32 |
| MD4 | 16 | 22 | 32 |
| MD5 | 16 | 22 | 32 |
| SHA-1 | 20 | 27 | 40 |
| SHA-256 | 32 | 43 | 64 |
| SHA-384 | 48 | 64 | 96 |
| SHA-512 | 64 | 86 | 128 |
| CRC-CCITT | 3 | 2 | 3 |
| CRC-16 | 5 | 6 | 4 |
| CRC-32 | 10 | 14 | 8 |
| Adler-32 | 4 | 6 | 8 |
| Whirlpool | 64 | 86 | 128 |
| Haval-256 | 32 | 44 | 64 |
---------------------------------------
=head1 SEE ALSO
L<DBIx::Class::EncodedColumn::Crypt::Eksblowfish::Bcrypt>,
L<DBIx::Class::EncodedColumn>, L<Digest>
=head1 AUTHOR
Guillermo Roditi (groditi) <groditi@cpan.org>
Based on the Vienna WoC ToDo manager code by Matt S trout (mst)
=head1 CONTRIBUTORS
See L<DBIx::Class::EncodedColumn>
=head1 LICENSE
This module is free software; you can redistribute it and/or modify it under
the same terms as Perl itself.
=cut
|