]> jfr.im git - irc/SurrealServices/srsv.git/blob - branches/erry-devel/SrSv/Hash/SaltedHash.pm
initial commit of erry's Insp work.
[irc/SurrealServices/srsv.git] / branches / erry-devel / SrSv / Hash / SaltedHash.pm
1 #########################################################################################
2 ## ##
3 ## Copyright(c) 2007 M2000, Inc. ##
4 ## ##
5 ## File: SaltedHash.pm ##
6 ## Author: Adam Schrotenboer ##
7 ## ##
8 ## ##
9 ## Description ##
10 ## =========== ##
11 ## Produces salted hashes for various uses. ##
12 ## This module is licensed under the Lesser GNU Public License version 2.1 ##
13 ## ##
14 ## Revision History ##
15 ## ================ ##
16 ## 11/13/07: Initial version. ##
17 ## ##
18 ## ##
19 #########################################################################################
20 ## ##
21 ## For more details refer to the implementation specification document ##
22 ## DRCS-xxxxxx Section x.x ##
23 ## ##
24 #########################################################################################
25 package SrSv::Hash::SaltedHash;
26
27 use strict;
28
29 =head1 NAME
30
31 SaltedHash
32
33 =head1 SYNOPSIS
34
35 use SaltedHash;
36
37 =head1 DESCRIPTION
38
39 Produces and verifies salted hashes.
40
41 =head2 NOTE
42
43 This module currently only supports SHA256, and requires Digest::SHA.
44 If Digest::SHA is not available, it will however fallback to an included copy of Digest::SHA::PurePerl
45
46 =cut
47
48
49 BEGIN {
50 if(eval { require Digest::SHA; } ) {
51 import Digest::SHA qw( sha256_base64 sha256 sha1 );
52 print "SrSv::Hash::SaltedHash using Digest::SHA\n";
53 }
54 elsif(eval { require Digest::SHA::PurePerl; } ){
55 import Digest::SHA::PurePerl qw( sha256_base64 sha256 sha1 );
56 print "SrSv::Hash::SaltedHash using Digest::SHA::PurePerl\n";
57 } else {
58 die "Unable to find a suitable SHA implementation\n";
59 }
60 }
61
62 =item Hash Notes
63
64 SHA512 requires 64bit int operations, and thus will be SLOW on 32bit platforms.
65 Current hash string length with SHA256 and 16byte (128bit) salts is 85 characters
66 Be aware that SHA512 with 16byte salt would take approximately ~130 characters
67 So make sure that your password field can hold strings large enough.
68 It is generally considered pointless to make your salt
69 longer than your hash, so 32bytes is longest that is useful
70 for SHA256 and 64 is longest for SHA512.
71 SrSv has a limit of 127 characters for password strings, so don't use SHA512.
72
73 =cut
74 use Exporter 'import';
75 BEGIN {
76 my %constants = (
77 HASH_ALGORITHM => 'SHA256',
78 HASH_SALT_LEN => 16,
79 HASH_ROUNDS => 1,
80 );
81 my $version = 'v1-'.$constants{HASH_SALT_LEN}.'-r'.$constants{HASH_ROUNDS};
82 $constants{HASH_VERSION} = $version;
83 our @EXPORT = qw( makeHash verifyHash );
84 our @EXPORT_OK = ( @EXPORT, keys(%constants), qw( extractMeta extractSalt padBase64 makeHash_v0 makeHash_v1 ));
85 our %EXPORT_TAGS = ( constants => [keys(%constants)] );
86 require constant; import constant (\%constants);
87 }
88
89
90 use MIME::Base64 qw( encode_base64 decode_base64 );
91 use SrSv::Hash::Random qw( randomBytes randomByte );
92
93 =item makeHash($;$$$)
94
95 makeHash($secret, $salt, $algorithm, $version)
96
97 Salt is assumed to be a BINARY STRING.
98
99 Algorithm currently can only be 'SHA256'
100
101 =cut
102
103 sub makeHash($;$$$) {
104 return makeHash_v1(@_);
105 }
106
107 =item makeHash_v1($;$$$)
108
109 makeHash_v1 ($secret, $salt, $algorithm, $version)
110
111 returns a string that can be processed thusly
112 my ($algorithm, $version, $salt, $hash) = split(':', $string);
113
114 my ($revision, $saltsize, $rounds) = split('-', $version);
115
116 =cut
117
118 sub makeHash_v1($;$$$) {
119 my ($secret, $salt, $algorithm, $version) = @_;
120 $algorithm = HASH_ALGORITHM unless $algorithm;
121 $salt = makeBinSalt(HASH_SALT_LEN) unless $salt;
122 $version = HASH_VERSION unless $version;
123 my $string = "$algorithm:$version:";
124 $string .= encode_base64($salt, '').':';
125 $string .= padBase64(__makeHash($secret . $salt, $algorithm));
126 return $string;
127 }
128
129 sub __makeHash($$) {
130 my ($plaintext, $algorithm) = @_;
131 $algorithm = 'sha256';
132 if($algorithm =~ /^sha256$/i) {
133 return sha256_base64($plaintext);
134 } else {
135 # Other hash algos haven't been implemented yet
136 die "Unknown hash algorithm \"$algorithm\" \"$plaintext\"\n";
137 }
138 }
139
140 sub makeHash_v0($;$$) {
141 my ($secret, $salt, $algorithm) = @_;
142 $algorithm = 'SHA256' unless $algorithm;
143 $salt = makeBinSalt(4) unless $salt;
144 my $string = "{S$algorithm}";
145 if($algorithm eq 'SHA256') {
146 $string .= encode_base64(sha256($secret . $salt) . $salt, '');
147 } elsif ($algorithm eq 'SHA') {
148 $string .= encode_base64(sha1($secret . $salt) . $salt, '');
149 }
150 return $string;
151 }
152
153 sub padBase64($) {
154 my ($b64_digest) = @_;
155 while (length($b64_digest) % 4) {
156 $b64_digest .= '=';
157 }
158 return $b64_digest;
159 }
160
161 =item makeHash
162
163 verifyHash($hash, $plain)
164
165 Verifies that a given $plain matches $hash
166
167 =cut
168
169 sub verifyHash($$) {
170 my ($hash, $plain) = @_;
171 my ($algorithm, $version, $salt) = extractMeta($hash);
172 my $hash2;
173 if($version eq 'v0') {
174 $hash2 = makeHash_v0($plain, $salt, $algorithm);
175 } else {
176 $hash2 = makeHash_v1($plain, $salt, $algorithm, $version);
177 }
178
179 return ($hash eq $hash2 ? 1 : 0);
180 }
181
182 sub makeBinSalt(;$) {
183 my ($len) = @_;
184 $len = HASH_SALT_LEN unless $len;
185 return randomBytes($len);
186 }
187
188 =item makeHash
189
190 extractMeta($hash)
191
192 return ($algorithm, $version, $salt) from $hash.
193
194 =cut
195 sub extractMeta($) {
196 my ($input) = @_;
197 if($input =~ /^\{S(\S+)\}(.*)$/) {
198 my $algorithm = $1;
199 my $saltedBinHash = decode_base64($2);
200 my $salt = substr($saltedBinHash, -4);
201 return ($algorithm, 'v0', $salt);
202 } else {
203 my ($algorithm, $version, $salt, $hash) = split(':', $input);
204 return ($algorithm, $version, decode_base64($salt));
205 }
206 }
207
208 =item makeHash
209
210 extractSalt($hash)
211
212 return $salt from $hash.
213
214 =cut
215 sub extractSalt($) {
216 my ($input) = @_;
217 my ($algorithm, $version, $salt) = extractMeta($input);
218 return $salt;
219 }
220
221 1;