]>
| 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; | |
| bef2caa6 | 12 | our $VERSION = '0.002'; |
| 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 | ||
| bef2caa6 | 218 | Copyright (C) 2017-2018 by Marius Gavrilescu |
| 0c1f3509 MG |
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 |