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