From 0e78a33060a85ac0ac6aea40c55d09f77325e255 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Mon, 27 Jul 2015 17:23:06 +0200 Subject: [PATCH] Save passwords as bcrypt instead of plain SHA-1 hashes (includes migration of old passwords). --- doc/modules.txt | 1 + perl/Sesse/pr0n/Common.pm | 45 ++++++++++++++++++++++++++++++++++++--- sql/pr0n.sql | 5 +++-- 3 files changed, 46 insertions(+), 5 deletions(-) diff --git a/doc/modules.txt b/doc/modules.txt index 919e388..c2acc00 100644 --- a/doc/modules.txt +++ b/doc/modules.txt @@ -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 diff --git a/perl/Sesse/pr0n/Common.pm b/perl/Sesse/pr0n/Common.pm index 1cd6504..d03ed6d 100644 --- a/perl/Sesse/pr0n/Common.pm +++ b/perl/Sesse/pr0n/Common.pm @@ -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) = @_; diff --git a/sql/pr0n.sql b/sql/pr0n.sql index 3b262b5..4089a6a 100644 --- a/sql/pr0n.sql +++ b/sql/pr0n.sql @@ -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 -- 2.39.2