diff options
author | Yorhel <git@yorhel.nl> | 2014-10-15 14:20:56 +0200 |
---|---|---|
committer | Yorhel <git@yorhel.nl> | 2014-10-15 14:20:56 +0200 |
commit | 6e0a0e1d00e11da9b4eab2163e19314f752b05b5 (patch) | |
tree | a65e4b62d81d395c9988f7045b4e83deec8b2485 | |
parent | 13e967810a8b336164d22167bb047ad1dbb5a836 (diff) |
Use scrypt for new password hashes
I increased the N parameter to approximate about 500ms to generate the
hash. This is quite a paranoid setting for a website, but login attempts
are throttled so there's not much of a DoS factor. (Alright, password
changing feature isn't throttled so the DoS factor still exists. But
really, there's some pages with longer page generation times anyway.)
I did lower the size of the salt a bit (Crypt::ScryptKDF uses 256 bits
by default), because 64 bits of randomness should have low enough chance
of collision with only ~100k users (even with a million users,
seriously).
-rw-r--r-- | data/config_example.pl | 1 | ||||
-rw-r--r-- | data/global.pl | 2 | ||||
-rw-r--r-- | lib/VNDB/Handler/Users.pm | 8 | ||||
-rw-r--r-- | lib/VNDB/Util/Auth.pm | 30 | ||||
-rw-r--r-- | util/sql/schema.sql | 8 |
5 files changed, 39 insertions, 10 deletions
diff --git a/data/config_example.pl b/data/config_example.pl index 68c12145..6e0bbe37 100644 --- a/data/config_example.pl +++ b/data/config_example.pl @@ -18,6 +18,7 @@ package VNDB; url_static => 'http://your.static.site.root/', global_salt => '<some long unique string>', form_salt => '<another unique string>', + scrypt_salt => '<yet another unique string>', ); diff --git a/data/global.pl b/data/global.pl index 636ab327..477574c8 100644 --- a/data/global.pl +++ b/data/global.pl @@ -25,6 +25,8 @@ our %S = (%S, skin_default => 'angel', global_salt => 'any-private-string-here', form_salt => 'a-different-private-string-here', + scrypt_args => [ 131072, 8, 1 ], # N, r, p + scrypt_salt => 'another-random-string', regen_static => 0, source_url => 'http://git.blicky.net/vndb.git/?h=master', admin_email => 'contact@vndb.org', diff --git a/lib/VNDB/Handler/Users.pm b/lib/VNDB/Handler/Users.pm index bcf94fb4..804b9467 100644 --- a/lib/VNDB/Handler/Users.pm +++ b/lib/VNDB/Handler/Users.pm @@ -210,7 +210,6 @@ sub newpass { my $token; ($token, $o{passwd}) = $self->authPrepareReset(); $self->dbUserEdit($u->{id}, %o); - #warn "$self->{url}/u$u->{id}/setpass?t=$token"; $self->mail(mt('_newpass_mail_body', $u->{username}, "$self->{url}/u$u->{id}/setpass?t=$token"), To => $frm->{mail}, From => 'VNDB <noreply@vndb.org>', @@ -310,7 +309,6 @@ sub register { if(!$frm->{_err}) { my($token, $pass) = $self->authPrepareReset(); my $uid = $self->dbUserAdd($frm->{usrname}, $pass, $frm->{mail}); - warn "$self->{url}/u$uid/setpass?t=$token"; $self->mail(mt('_register_mail_body', $frm->{usrname}, "$self->{url}/u$uid/setpass?t=$token"), To => $frm->{mail}, From => 'VNDB <noreply@vndb.org>', @@ -384,8 +382,8 @@ sub edit { $frm->{skin} = '' if $frm->{skin} eq $self->{skin_default}; $self->dbUserPrefSet($uid, $_ => $frm->{$_}) for (qw|skin customcss show_nsfw hide_list |); my %o; - $o{username} = $frm->{usrname} if $frm->{usrname}; if($self->authCan('usermod')) { + $o{username} = $frm->{usrname} if $frm->{usrname}; $o{perm} = 0; $o{perm} |= $self->{permissions}{$_} for(@{ delete $frm->{perms} }); } @@ -393,9 +391,7 @@ sub edit { $o{passwd} = $self->authPreparePass($frm->{usrpass}) if $frm->{usrpass}; $o{ign_votes} = $frm->{ign_votes} ? 1 : 0 if $self->authCan('usermod'); $self->dbUserEdit($uid, %o); - $self->dbSessionDel($uid) if $frm->{usrpass}; - return $self->resRedirect("/u$uid/edit?d=1", 'post') if $uid != $self->authInfo->{id} || !$frm->{usrpass}; - return $self->authLogin($frm->{usrname}||$u->{username}, $frm->{usrpass}, "/u$uid/edit?d=1"); + return $self->resRedirect("/u$uid/edit?d=1", 'post'); } } diff --git a/lib/VNDB/Util/Auth.pm b/lib/VNDB/Util/Auth.pm index 2fd06828..5228b6eb 100644 --- a/lib/VNDB/Util/Auth.pm +++ b/lib/VNDB/Util/Auth.pm @@ -7,6 +7,7 @@ use warnings; use Exporter 'import'; use Digest::SHA qw|sha1 sha1_hex sha256|; use Crypt::URandom 'urandom'; +use Crypt::ScryptKDF 'scrypt_raw'; use Encode 'encode_utf8'; use TUWF ':html'; use VNDB::Func; @@ -107,9 +108,21 @@ sub _authCheck { return 0 if !$user || length($user) > 15 || length($user) < 2 || !$pass; my $d = $self->dbUserGet(username => $user, what => 'extended notifycount')->[0]; - return 0 if !$d->{id} || length $d->{passwd} != 41; + return 0 if !$d->{id}; - if($self->authPreparePass($pass, substr $d->{passwd}, 0, 9) eq $d->{passwd}) { + # Old-style hashes + if(length $d->{passwd} == 41) { + return 0 if _authPreparePassSha256($self, $pass, substr $d->{passwd}, 0, 9) ne $d->{passwd}; + $self->{_auth} = $d; + # Update database with new hash format, now that we have the plain text password + $self->dbUserEdit($d->{id}, passwd => $self->authPreparePass($pass)); + return 1; + } + + # New scrypt hashes + if(length $d->{passwd} == 46) { + my($N, $r, $p, $salt) = unpack 'NCCa8', $d->{passwd}; + return 0 if $self->authPreparePass($pass, $salt, $N, $r, $p) ne $d->{passwd}; $self->{_auth} = $d; return 1; } @@ -119,9 +132,20 @@ sub _authCheck { # Prepares a plaintext password for database storage -# Arguments: pass, optionally salt +# Arguments: pass, optionally: salt, N, r, p # Returns: encrypted password (as a binary string) sub authPreparePass { + my($self, $pass, $salt, $N, $r, $p) = @_; + ($N, $r, $p) = @{$self->{scrypt_args}} if !$N; + $salt ||= urandom(8); + return pack 'NCCa8a*', $N, $r, $p, $salt, scrypt_raw($pass, $self->{scrypt_salt} . $salt, $N, $r, $p, 32); +} + + +# Same as authPreparePass, but for the old sha256 hash. +# Arguments: pass, optionally salt +# Returns: encrypted password (as a binary string) +sub _authPreparePassSha256 { my($self, $pass, $salt) = @_; $salt ||= encode_utf8(randomascii(9)); return $salt.sha256($self->{global_salt} . encode_utf8($pass) . $salt); diff --git a/util/sql/schema.sql b/util/sql/schema.sql index 09b334f9..32561dc9 100644 --- a/util/sql/schema.sql +++ b/util/sql/schema.sql @@ -368,9 +368,15 @@ CREATE TABLE users ( -- First 9 bytes: salt (ASCII) -- Latter 20 bytes: sha1(hex(token) + salt) -- 'token' is a sha1 digest obtained from random data. - -- * 41 bytes: Hashed/salted password + -- * 41 bytes: sha256 password -- First 9 bytes: salt (ASCII) -- Latter 32 bytes: sha256(global_salt + password + salt) + -- * 46 bytes: scrypt password + -- 4 bytes: N (big endian) + -- 1 byte: r + -- 1 byte: p + -- 8 bytes: salt + -- 32 bytes: scrypt(passwd, global_salt + salt, N, r, p, 32) -- * Anything else: Invalid, account disabled. passwd bytea NOT NULL DEFAULT '', registered timestamptz NOT NULL DEFAULT NOW(), |