Commit 179e06d7 authored by mkanat%bugzilla.org's avatar mkanat%bugzilla.org

Bug 211006: Make Bugzilla use SHA-256 instead of crypt() to store hashed passwords in the database

Patch By Max Kanat-Alexander <mkanat@bugzilla.org> r=LpSolit, a=LpSolit
parent 5c8dab45
...@@ -64,6 +64,16 @@ sub check_credentials { ...@@ -64,6 +64,16 @@ sub check_credentials {
# password tokens they may have generated. # password tokens they may have generated.
Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in"); Bugzilla::Token::DeletePasswordTokens($user_id, "user_logged_in");
# If their old password was using crypt() or some different hash
# than we're using now, convert the stored password to using
# whatever hashing system we're using now.
my $current_algorithm = PASSWORD_DIGEST_ALGORITHM;
if ($real_password_crypted !~ /{\Q$current_algorithm\E}$/) {
my $new_crypted = bz_crypt($password);
$dbh->do('UPDATE profiles SET cryptpassword = ? WHERE userid = ?',
undef, $new_crypted, $user_id);
}
return $login_data; return $login_data;
} }
......
...@@ -154,6 +154,9 @@ use File::Basename; ...@@ -154,6 +154,9 @@ use File::Basename;
MAX_COMPONENT_SIZE MAX_COMPONENT_SIZE
MAX_FIELD_VALUE_SIZE MAX_FIELD_VALUE_SIZE
MAX_FREETEXT_LENGTH MAX_FREETEXT_LENGTH
PASSWORD_DIGEST_ALGORITHM
PASSWORD_SALT_LENGTH
); );
@Bugzilla::Constants::EXPORT_OK = qw(contenttypes); @Bugzilla::Constants::EXPORT_OK = qw(contenttypes);
...@@ -437,6 +440,15 @@ use constant MAX_FIELD_VALUE_SIZE => 64; ...@@ -437,6 +440,15 @@ use constant MAX_FIELD_VALUE_SIZE => 64;
# Maximum length allowed for free text fields. # Maximum length allowed for free text fields.
use constant MAX_FREETEXT_LENGTH => 255; use constant MAX_FREETEXT_LENGTH => 255;
# This is the name of the algorithm used to hash passwords before storing
# them in the database. This can be any string that is valid to pass to
# Perl's "Digest" module. Note that if you change this, it won't take
# effect until a user changes his password.
use constant PASSWORD_DIGEST_ALGORITHM => 'SHA-256';
# How long of a salt should we use? Note that if you change this, none
# of your users will be able to log in until they reset their passwords.
use constant PASSWORD_SALT_LENGTH => 8;
sub bz_locations { sub bz_locations {
# We know that Bugzilla/Constants.pm must be in %INC at this point. # We know that Bugzilla/Constants.pm must be in %INC at this point.
# So the only question is, what's the name of the directory # So the only question is, what's the name of the directory
......
...@@ -65,6 +65,11 @@ sub REQUIRED_MODULES { ...@@ -65,6 +65,11 @@ sub REQUIRED_MODULES {
version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21' version => (vers_cmp($perl_ver, '5.10') > -1) ? '3.33' : '3.21'
}, },
{ {
package => 'Digest-SHA',
module => 'Digest::SHA',
version => 0
},
{
package => 'TimeDate', package => 'TimeDate',
module => 'Date::Format', module => 'Date::Format',
version => '2.21' version => '2.21'
......
...@@ -52,6 +52,8 @@ use Date::Parse; ...@@ -52,6 +52,8 @@ use Date::Parse;
use Date::Format; use Date::Format;
use DateTime; use DateTime;
use DateTime::TimeZone; use DateTime::TimeZone;
use Digest;
use Scalar::Util qw(tainted);
use Text::Wrap; use Text::Wrap;
# This is from the perlsec page, slightly modified to remove a warning # This is from the perlsec page, slightly modified to remove a warning
...@@ -476,37 +478,54 @@ sub file_mod_time { ...@@ -476,37 +478,54 @@ sub file_mod_time {
sub bz_crypt { sub bz_crypt {
my ($password, $salt) = @_; my ($password, $salt) = @_;
my $algorithm;
if (!defined $salt) { if (!defined $salt) {
# The list of characters that can appear in a salt. Salts and hashes # If you don't use a salt, then people can create tables of
# are both encoded as a sequence of characters from a set containing # hashes that map to particular passwords, and then break your
# 64 characters, each one of which represents 6 bits of the salt/hash. # hashing very easily if they have a large-enough table of common
# The encoding is similar to BASE64, the difference being that the # (or even uncommon) passwords. So we generate a unique salt for
# BASE64 plus sign (+) is replaced with a forward slash (/). # each password in the database, and then just prepend it to
my @saltchars = (0..9, 'A'..'Z', 'a'..'z', '.', '/'); # the hash.
$salt = generate_random_password(PASSWORD_SALT_LENGTH);
# Generate the salt. We use an 8 character (48 bit) salt for maximum $algorithm = PASSWORD_DIGEST_ALGORITHM;
# security on systems whose crypt uses MD5. Systems with older }
# versions of crypt will just use the first two characters of the salt.
$salt = ''; # We append the algorithm used to the string. This is good because then
for ( my $i=0 ; $i < 8 ; ++$i ) { # we can change the algorithm being used, in the future, without
$salt .= $saltchars[rand(64)]; # disrupting the validation of existing passwords. Also, this tells
} # us if a password is using the old "crypt" method of hashing passwords,
} # because the algorithm will be missing from the string.
if ($salt =~ /{([^}]+)}$/) {
$algorithm = $1;
}
my $crypted_password;
if (!$algorithm) {
# Wide characters cause crypt to die # Wide characters cause crypt to die
if (Bugzilla->params->{'utf8'}) { if (Bugzilla->params->{'utf8'}) {
utf8::encode($password) if utf8::is_utf8($password); utf8::encode($password) if utf8::is_utf8($password);
} }
# Crypt the password. # Crypt the password.
my $cryptedpassword = crypt($password, $salt); $crypted_password = crypt($password, $salt);
# HACK: Perl has bug where returned crypted password is considered tainted # HACK: Perl has bug where returned crypted password is considered
# Upstream Bug: http://rt.perl.org/rt3/Public/Bug/Display.html?id=59998 # tainted. See http://rt.perl.org/rt3/Public/Bug/Display.html?id=59998
trick_taint($cryptedpassword) unless (is_tainted($password) || is_tainted($salt)); unless(tainted($password) || tainted($salt)) {
trick_taint($crypted_password);
}
}
else {
my $hasher = Digest->new($algorithm);
# We only want to use the first characters of the salt, no
# matter how long of a salt we may have been passed.
$salt = substr($salt, 0, PASSWORD_SALT_LENGTH);
$hasher->add($password, $salt);
$crypted_password = $salt . $hasher->b64digest . "{$algorithm}";
}
# Return the crypted password. # Return the crypted password.
return $cryptedpassword; return $crypted_password;
} }
sub generate_random_password { sub generate_random_password {
...@@ -932,11 +951,12 @@ of the "mtime" parameter of the perl "stat" function. ...@@ -932,11 +951,12 @@ of the "mtime" parameter of the perl "stat" function.
=item C<bz_crypt($password, $salt)> =item C<bz_crypt($password, $salt)>
Takes a string and returns a C<crypt>ed value for it, using a random salt. Takes a string and returns a hashed (encrypted) value for it, using a
An optional salt string may also be passed in. random salt. An optional salt string may also be passed in.
Please always use this function instead of the built-in perl "crypt" Please always use this function instead of the built-in perl C<crypt>
when initially encrypting a password. function, when checking or setting a password. Bugzilla does not use
C<crypt>.
=begin undocumented =begin undocumented
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment