summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorYorhel <git@yorhel.nl>2014-10-15 14:20:56 +0200
committerYorhel <git@yorhel.nl>2014-10-15 14:20:56 +0200
commit6e0a0e1d00e11da9b4eab2163e19314f752b05b5 (patch)
treea65e4b62d81d395c9988f7045b4e83deec8b2485
parent13e967810a8b336164d22167bb047ad1dbb5a836 (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.pl1
-rw-r--r--data/global.pl2
-rw-r--r--lib/VNDB/Handler/Users.pm8
-rw-r--r--lib/VNDB/Util/Auth.pm30
-rw-r--r--util/sql/schema.sql8
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(),