Save passwords as bcrypt instead of plain SHA-1 hashes (includes migration of old...
authorSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 27 Jul 2015 15:23:06 +0000 (17:23 +0200)
committerSteinar H. Gunderson <sgunderson@bigfoot.com>
Mon, 27 Jul 2015 15:23:06 +0000 (17:23 +0200)
doc/modules.txt
perl/Sesse/pr0n/Common.pm
sql/pr0n.sql

index 919e388290d468b645438dd30b5771233cfa50d7..c2acc001395bf7308a23e37c752a4ba667fd1f7b 100644 (file)
@@ -9,6 +9,7 @@ DBD::Pg                     libdbd-pg-perl             PostgreSQL connection
 Image::ExifTool             libimage-exiftool-perl     Parsing EXIF data
 Digest::SHA1                libdigest-sha1-perl        Verifying passwords
 Digest::HMAC_SHA1           libdigest-hmac-perl        Verifying digest passwords
+Crypt::Eksblowfish::Bcrypt  libcrypt-eksblowfish-perl  Verifying passwords
 HTML::TagCloud              libhtml-tagcloud-perl      Tag cloud on /+tags/
 jpegtran                    libjpeg-progs              Lossless JPEG rotation
 dcraw                       dcraw                      NEF support
index 1cd65046d9769787de6aa3ca44205fb19137fb79..d03ed6d3a2ef79e342a73ff86837347ec0606369 100644 (file)
@@ -29,6 +29,7 @@ use Image::ExifTool;
 use HTML::Entities;
 use URI::Escape;
 use File::Basename;
+use Crypt::Eksblowfish::Bcrypt;
 
 BEGIN {
        use Exporter ();
@@ -390,10 +391,18 @@ sub check_basic_auth {
 
        my ($raw_user, $pass) = split /:/, MIME::Base64::decode_base64($auth);
        my ($user, $takenby) = extract_takenby($raw_user);
-       
-       my $ref = $dbh->selectrow_hashref('SELECT sha1password,digest_ha1_hex FROM users WHERE username=? AND vhost=?',
+
+       my $ref = $dbh->selectrow_hashref('SELECT sha1password,cryptpassword,digest_ha1_hex FROM users WHERE username=? AND vhost=?',
                undef, $user, $r->get_server_name);
-       if (!defined($ref) || $ref->{'sha1password'} ne Digest::SHA::sha1_base64($pass)) {
+       my ($sha1_matches, $bcrypt_matches) = (0, 0);
+       if (defined($ref) && defined($ref->{'sha1password'})) {
+               $sha1_matches = (Digest::SHA::sha1_base64($pass) eq $ref->{'sha1password'});
+       }
+       if (defined($ref) && defined($ref->{'cryptpassword'})) {
+               $bcrypt_matches = (Crypt::Eksblowfish::Bcrypt::bcrypt($pass, $ref->{'cryptpassword'}) eq $ref->{'cryptpassword'});
+       }
+
+       if (!defined($ref) || (!$sha1_matches && !$bcrypt_matches)) {
                $r->content_type('text/plain; charset=utf-8');
                $r->log->warn("Authentication failed for $user/$takenby");
                output_401($r);
@@ -410,9 +419,39 @@ sub check_basic_auth {
                $r->log->info("Updated Digest auth hash for for $user");
        }
 
+       # Make sure we can use bcrypt authentication in the future with this password.
+       # Also remove old-style SHA1 password when we migrate.
+       if (!$bcrypt_matches) {
+               my $salt = get_pseudorandom_bytes(16);  # Doesn't need to be cryptographically secur.
+               my $hash = "\$2a\$07\$" . Crypt::Eksblowfish::Bcrypt::en_base64($salt);
+               my $cryptpassword = Crypt::Eksblowfish::Bcrypt::bcrypt($pass, $hash);
+               $dbh->do('UPDATE users SET sha1password=NULL,cryptpassword=? WHERE username=? AND vhost=?',
+                       undef, $cryptpassword, $user, $r->get_server_name)
+                       or die "Couldn't update: " . $dbh->errstr;
+               $r->log->info("Updated bcrypt hash for $user");
+       }
+
        return ($user, $takenby);
 }
 
+sub get_pseudorandom_bytes {
+       my $num_left = shift;
+       my $bytes = "";
+       open my $randfh, "<", "/dev/urandom"
+               or die "/dev/urandom: $!";
+       binmode $randfh;
+       while ($num_left > 0) {
+               my $tmp;
+               if (sysread($randfh, $tmp, $num_left) == -1) {
+                       die "sysread(/dev/urandom): $!";
+               }
+               $bytes .= $tmp;
+               $num_left -= length($bytes);
+       }
+       close $randfh;
+       return $bytes;
+}
+
 sub check_digest_auth {
        my ($r, $auth) = @_;    
 
index 3b262b52181c245672caa918d399a10ad7596d07..4089a6a577790984a74672c9580fa6d8d80ef957 100644 (file)
@@ -73,9 +73,10 @@ CREATE TABLE shadow_files (
 
 CREATE TABLE users (
     username character varying NOT NULL,
-    sha1password character(27) NOT NULL,
+    sha1password character(27),
     vhost character varying NOT NULL,
-    digest_ha1_hex character(32)
+    digest_ha1_hex character(32),
+    cryptpassword character varying
 );
 
 -- Mainly used for manual queries -- usually too slow to be very useful