]>
Commit | Line | Data |
---|---|---|
0c1f3509 MG |
1 | package Authen::Passphrase::Scrypt; |
2 | ||
3 | use 5.014000; | |
4 | use strict; | |
5 | use warnings; | |
6 | use Carp; | |
7 | ||
8 | use parent qw/Exporter Authen::Passphrase Class::Accessor::Fast/; | |
9 | ||
10 | our @EXPORT = qw/crypto_scrypt/; | |
11 | our @EXPORT_OK = @EXPORT; | |
2fc55944 | 12 | our $VERSION = '0.001001'; |
0c1f3509 MG |
13 | |
14 | use Data::Entropy::Algorithms qw/rand_bits/; | |
15 | use Digest::SHA qw/sha256 hmac_sha256/; | |
16 | use MIME::Base64; | |
17 | ||
18 | require XSLoader; | |
19 | XSLoader::load('Authen::Passphrase::Scrypt', $VERSION); | |
20 | ||
21 | __PACKAGE__->mk_accessors(qw/data logN r p salt hmac passphrase/); | |
22 | ||
23 | sub compute_hash { | |
24 | my ($self, $passphrase) = @_; | |
25 | crypto_scrypt ($passphrase, $self->salt, (1 << $self->logN), $self->r, $self->p, 64); | |
26 | } | |
27 | ||
28 | sub truncated_sha256 { | |
29 | my $sha = sha256 shift; | |
30 | substr $sha, 0, 16 | |
31 | } | |
32 | ||
33 | sub truncate_hash { | |
34 | substr shift, 32 | |
35 | } | |
36 | ||
37 | sub new { | |
38 | my ($class, @args) = @_; | |
39 | my $self = $class->SUPER::new(@args); | |
40 | ||
41 | $self->logN(14) unless defined $self->logN; | |
42 | $self->r(16) unless defined $self->r; | |
43 | $self->p(1) unless defined $self->p; | |
44 | croak "passphrase not set" unless defined $self->passphrase; | |
45 | $self->salt(rand_bits 256) unless $self->salt; | |
46 | ||
47 | my $data = "scrypt\x00" . pack 'CNNa32', | |
48 | $self->logN, $self->r, $self->p, $self->salt; | |
49 | $data .= truncated_sha256 $data; | |
50 | $self->data($data); | |
51 | $self->hmac(hmac_sha256 $self->data, truncate_hash $self->compute_hash($self->passphrase)); | |
52 | $self | |
53 | } | |
54 | ||
55 | sub from_rfc2307 { | |
56 | my ($class, $rfc2307) = @_; | |
57 | croak "Invalid Scrypt RFC2307" unless $rfc2307 =~ m,^{SCRYPT}([A-Za-z0-9+/]{128})$,; | |
58 | my $data = decode_base64 $1; | |
59 | my ($scrypt, $logN, $r, $p, $salt, $cksum, $hmac) = | |
60 | unpack 'Z7CNNa32a16a32', $data; | |
61 | croak 'Invalid Scrypt hash: should start with "scrypt"' unless $scrypt eq 'scrypt'; | |
62 | croak 'Invalid Scrypt hash: bad checksum', unless $cksum eq truncated_sha256 (substr $data, 0, 48); | |
63 | $class->SUPER::new({data => (substr $data, 0, 64), logN => $logN, r => $r, p => $p, salt => $salt, hmac => $hmac}); | |
64 | } | |
65 | ||
66 | sub match { | |
67 | my ($self, $passphrase) = @_; | |
68 | my $correct_hmac = hmac_sha256 $self->data, truncate_hash $self->compute_hash($passphrase); | |
69 | $self->hmac eq $correct_hmac | |
70 | } | |
71 | ||
72 | sub as_rfc2307 { | |
73 | my ($self) = @_; | |
74 | '{SCRYPT}' . encode_base64 ($self->data . $self->hmac, '') | |
75 | } | |
76 | ||
77 | sub from_crypt { | |
78 | croak __PACKAGE__ ." does not support crypt strings, use from_rfc2307 instead"; | |
79 | } | |
80 | ||
81 | sub as_crypt { | |
82 | croak __PACKAGE__ ." does not support crypt strings, use as_rfc2307 instead"; | |
83 | } | |
84 | ||
85 | 1; | |
86 | __END__ | |
87 | ||
88 | =encoding utf-8 | |
89 | ||
90 | =head1 NAME | |
91 | ||
92 | Authen::Passphrase::Scrypt - passphrases using Tarsnap's scrypt algorithm | |
93 | ||
94 | =head1 SYNOPSIS | |
95 | ||
96 | use Authen::Passphrase::Scrypt; | |
97 | ||
98 | # Hash a password | |
99 | my $sc = Authen::Passphrase::Scrypt->new({ | |
100 | passphrase => 'correcthorsebatterystaple' | |
101 | }); | |
102 | my $hash = $sc->as_rfc2307; | |
103 | say "The given password hashes to $hash"; | |
104 | ||
105 | # Verify a password | |
106 | $sc = Authen::Passphrase::Scrypt->from_rfc2307($hash); | |
107 | say 'The password was "correcthorsebatterystaple"' | |
108 | if $sc->match('correcthorsebatterystaple'); | |
109 | say 'The password was "xkcd"' if $sc->match('xkcd'); | |
110 | ||
111 | # Advanced hashing | |
112 | my $sc = Authen::Passphrase::Scrypt->new({ | |
113 | passphrase => 'xkcd', | |
114 | logN => 14, # General work factor | |
115 | r => 16, # Memory work factor | |
116 | p => 1, # CPU (parallellism) work factor | |
117 | salt => 'SodiumChloride && sODIUMcHLORIDE', # Must be 32 bytes | |
118 | }); | |
119 | say 'The given password now hashes to ', $sc->as_rfc2307; | |
120 | ||
121 | =head1 DESCRIPTION | |
122 | ||
123 | B<This is experimental code, DO NOT USE in security-critical software>. | |
124 | ||
125 | Scrypt is a key derivation function that was originally developed for | |
126 | use in the Tarsnap online backup system and is designed to be far more | |
127 | secure against hardware brute-force attacks than alternative functions | |
128 | such as PBKDF2 or bcrypt. | |
129 | ||
130 | Authen::Passphrase::Scrypt is a module for hashing and verifying | |
131 | passphrases using scrypt. It offers the same interface as | |
132 | L<Authen::Passphrase>. It is not however possible to use this module | |
133 | from within L<Authen::Passphrase>. The methods are: | |
134 | ||
135 | =over | |
136 | ||
137 | =item Authen::Passphrase::Scrypt->B<new>(I<\%args>) | |
138 | ||
139 | Creates a new L<Authen::Passphrase::Scrypt> from a given passphrase | |
140 | and parameters. Use this to hash a passphrase. The arguments are: | |
141 | ||
142 | =over | |
143 | ||
144 | =item B<passphrase> | |
145 | ||
146 | The passphrase. Mandatory. | |
147 | ||
148 | =item B<logN> | |
149 | ||
150 | The general work factor (affects both CPU and memory cost). Defaults to 14 | |
151 | ||
152 | =item B<r> | |
153 | ||
154 | The blocksize (affects memory cost). Defaults to 16. | |
155 | ||
156 | =item B<p> | |
157 | ||
158 | The parallelization factor (affects CPU cost). Defaults to 1. | |
159 | ||
160 | =item B<salt> | |
161 | ||
162 | A 32-byte string used as a salt. By default it is randomly generated | |
163 | using L<Data::Entropy>. | |
164 | ||
165 | =back | |
166 | ||
167 | All of the parameters change the result of the hash. They are all | |
168 | stored in the hash, so there is no need to store them separately (or | |
169 | provide them to the hash verification methods). | |
170 | ||
171 | It is normally sufficient to only use the B<logN> parameter to control | |
172 | the speed of scrypt. B<r> and B<p> are intended to be used only for | |
173 | fine-tuning: if scrypt uses too much memory but not enough CPU, | |
174 | decrease logN and increase p; if scrypt uses too much CPU but not | |
175 | enough memory, decrease logN and increase r. | |
176 | ||
177 | =item $sc->B<as_rfc2307> | |
178 | ||
179 | Returns the hash of the passphrase, in RFC2307 format. This is | |
180 | "{SCRYPT}" followed by the base64-encoded 96-byte result described here: L<https://security.stackexchange.com/questions/88678/why-does-node-js-scrypt-function-use-hmac-this-way/91050> | |
181 | ||
182 | =item Authen::Passphrase::Scrypt->B<from_rfc2307>(I<$rfc2307>) | |
183 | ||
184 | Creates a new L<Authen::Passphrase::Scrypt> from a hash in RFC2307 | |
185 | format. Use this to verify if a passphrase matches a hash. | |
186 | ||
187 | =item $sc->B<match>(I<$passphrase>) | |
188 | ||
189 | Returns true if the given passphrase matches the hash, false | |
190 | otherwise. | |
191 | ||
192 | =item Authen::Passphrase::Scrypt->from_crypt | |
193 | =item $sc->as_crypt | |
194 | ||
195 | These functions both croak. They are provided for compatibility with | |
196 | the Authen::Passphrase interface. | |
197 | ||
198 | =back | |
199 | ||
200 | =head1 SEE ALSO | |
201 | ||
202 | L<Authen::Passphrase>, | |
203 | L<https://www.tarsnap.com/scrypt.html>, | |
204 | L<https://www.npmjs.com/package/scrypt> | |
205 | ||
206 | =head1 AUTHOR | |
207 | ||
208 | Marius Gavrilescu, E<lt>marius@ieval.roE<gt> | |
209 | ||
210 | =head1 COPYRIGHT AND LICENSE | |
211 | ||
212 | Copyright (C) 2017 by Marius Gavrilescu | |
213 | ||
214 | This library is free software; you can redistribute it and/or modify | |
215 | it under the same terms as Perl itself, either Perl version 5.24.1 or, | |
216 | at your option, any later version of Perl 5 you may have available. | |
217 | ||
218 | ||
219 | =cut |