]>
Commit | Line | Data |
---|---|---|
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/; | |
9 | ||
10 | our @EXPORT = qw/crypto_scrypt/; | |
11 | our @EXPORT_OK = @EXPORT; | |
12 | our $VERSION = '0.002'; | |
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 | use Object::Tiny 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 | 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; | |
46 | ||
47 | croak "passphrase not set" unless defined $self->passphrase; | |
48 | ||
49 | my $data = "scrypt\x00" . pack 'CNNa32', | |
50 | $self->logN, $self->r, $self->p, $self->salt; | |
51 | $data .= truncated_sha256 $data; | |
52 | $self->{data} = $data; | |
53 | $self->{hmac} = hmac_sha256 $self->data, truncate_hash $self->compute_hash($self->passphrase); | |
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); | |
65 | bless { data => (substr $data, 0, 64), logN => $logN, r => $r, p => $p, salt => $salt, hmac => $hmac }, $class; | |
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 | |
101 | my $sc = Authen::Passphrase::Scrypt->new( | |
102 | passphrase => 'correcthorsebatterystaple' | |
103 | ); | |
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 | |
114 | my $sc = Authen::Passphrase::Scrypt->new( | |
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 | |
120 | ); | |
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 | ||
139 | =item Authen::Passphrase::Scrypt->B<new>(I<%args>) | |
140 | ||
141 | Creates a new L<Authen::Passphrase::Scrypt> from a given passphrase | |
142 | and parameters. Use this to hash a passphrase. This function takes | |
143 | either a key value list or a hashref. The arguments are: | |
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 | ||
180 | Note that C<< 2^logN >> must fit in 64 bits and C<< r * p < 2^30 >>. | |
181 | ||
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 | |
198 | ||
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-2018 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 |