[rt-devel] [Rt-commit] rt branch, 4.2/bcrypt-passwords, created. rt-4.1.19-32-g07ac7c5

Reed Loden reed at reedloden.com
Tue Sep 3 03:03:41 EDT 2013


Keep in mind that using bcrypt alone creates a limit on the maximum
length of passwords[0]. I recommend you hash the password itself (using
SHA-256 or even HMAC-SHA-256 with a pepper) and then pass the SHA hash
to bcrypt as the 'password' input. That way, your users can have
passwords of whatever length they want, and you're not dropping bits on
the floor.

Also, bcrypt cost of 8 is not great at all (no idea why that is a
default, as it's pretty terrible[1]). I recommend you use at least 12.
It would also be nice to have the cost be a setting somewhere and have
it "upgrade" a person's password hash on log in if their old hash used a
different cost than what the current default is (since a bcrypt hash
will include the cost factor that was used to create it originally).

As a side note, have you looked at scrypt[2] yet? It's still fairly new,
but it adds a memory requirement to hash generation, making it even
less susceptible to offline brute-force attacks.

~reed

[0]
http://security.stackexchange.com/questions/39849/does-bcrypt-have-a-maximum-password-length
[1] http://www.perlmonks.org/?node_id=975703
[2] https://www.tarsnap.com/scrypt.html

On Tue,  3 Sep 2013 02:02:10 -0400 (EDT)
alexmv at bestpractical.com (Alex Vandiver) wrote:

> The branch, 4.2/bcrypt-passwords has been created
>         at  07ac7c51167a9427a2857fd4a09671ed8b9cab9c (commit)
> 
> - Log -----------------------------------------------------------------
> commit 07ac7c51167a9427a2857fd4a09671ed8b9cab9c
> Author: Alex Vandiver <alexmv at bestpractical.com>
> Date:   Thu Aug 22 17:59:25 2013 -0400
> 
>     Switch to Blowfish-based bcrypt for password hashing
>     
>     A SHA-512 with a 16-character salt, drawn from 64 possible characters,
>     yields 2^96 possible salts.  While this makes rainbow tables unrealistic
>     given modern hardware (the failure mode of RT 3.8's MD5 hashing), it
>     does very little to deter against offline brute force attacks on the
>     database.
>     
>     Specifically, given the complete hashed password and salt from the
>     database, a dictionary of weak passwords can be hashed with the stored
>     salt to attempt to find matches.  Given that a single round of the
>     SHA-512 hash is not designed to be computationally expensive, possible
>     passwords may be hashed and checked very quickly.
>     
>     The bcrypt hashing function is designed to be computationally expensive
>     to mitigate these types of attacks.  For instance, on a development
>     laptop:
>     
>                        Rate  bcrypt sha-512
>             bcrypt   13.3/s      --   -100%
>             sha-512 18183/s 136934%      --
>     
>     That is, bcrypt is three orders of magnitude slower to compute, thus
>     notably increasing the computational cost of brute-forcing passwords.
>     bcrypt also includes a tuning parameter, the number of "rounds" to run,
>     which allows the same algorithm to be increase the computational cost
>     required as computers continue to grow faster.  We use the standard
>     value of 8 here, but allow for higher values to be used later.
> 
> diff --git a/docs/UPGRADING-4.2 b/docs/UPGRADING-4.2
> index b7e2015..00b4b74 100644
> --- a/docs/UPGRADING-4.2
> +++ b/docs/UPGRADING-4.2
> @@ -261,6 +261,14 @@ deprecation warnings.  The old names, and their new counterparts, are:
>  Due to many long-standing bugs and limitations, the "Offline Tool" was
>  removed.
>  
> +=item *
> +
> +To increase security againt offline brute-force attacks, RT's default
> +password encryption has been switched to the popular bcrypt() key
> +derivation function.  Passwords cannot be automatically bulk upgraded to
> +the new format, but will be replaced with bcrypt versions upon the first
> +successful login.
> +
>  =back
>  
>  =cut
> diff --git a/lib/RT/User.pm b/lib/RT/User.pm
> index 152981a..3e4c2de 100644
> --- a/lib/RT/User.pm
> +++ b/lib/RT/User.pm
> @@ -79,6 +79,7 @@ sub Table {'Users'}
>  
>  use Digest::SHA;
>  use Digest::MD5;
> +use Crypt::Eksblowfish::Bcrypt qw();
>  use RT::Principals;
>  use RT::ACE;
>  use RT::Interface::Email;
> @@ -870,6 +871,40 @@ sub SetPassword {
>  
>  }
>  
> +sub _GeneratePassword_bcrypt {
> +    my $self = shift;
> +    my ($password, @rest) = @_;
> +
> +    my $salt;
> +    my $rounds;
> +    if (@rest) {
> +        # The first split is the number of rounds
> +        $rounds = $rest[0];
> +
> +        # The salt is the first 22 characters, b64 encoded usign the
> +        # special bcrypt base64.
> +        $salt = Crypt::Eksblowfish::Bcrypt::de_base64( substr($rest[1], 0, 22) );
> +    } else {
> +        # The current standard is 8 rounds
> +        $rounds = 8;
> +
> +        # Generate a random 16-octet base64 salt
> +        $salt = "";
> +        $salt .= pack("C", int rand(256)) for 1..16;
> +    }
> +
> +    my $hash = Crypt::Eksblowfish::Bcrypt::bcrypt_hash({
> +        key_nul => 1,
> +        cost    => $rounds,
> +        salt    => $salt,
> +    }, encode_utf8($password) );
> +
> +    return join("!", "", "bcrypt", sprintf("%02d", $rounds),
> +                Crypt::Eksblowfish::Bcrypt::en_base64( $salt ).
> +                Crypt::Eksblowfish::Bcrypt::en_base64( $hash )
> +              );
> +}
> +
>  sub _GeneratePassword_sha512 {
>      my $self = shift;
>      my ($password, $salt) = @_;
> @@ -893,13 +928,13 @@ Returns a string to store in the database.  This string takes the form:
>  
>     !method!salt!hash
>  
> -By default, the method is currently C<sha512>.
> +By default, the method is currently C<bcrypt>.
>  
>  =cut
>  
>  sub _GeneratePassword {
>      my $self = shift;
> -    return $self->_GeneratePassword_sha512(@_);
> +    return $self->_GeneratePassword_bcrypt(@_);
>  }
>  
>  =head3 HasPassword
> @@ -948,9 +983,11 @@ sub IsPassword {
>      my $stored = $self->__Value('Password');
>      if ($stored =~ /^!/) {
>          # If it's a new-style (>= RT 4.0) password, it starts with a '!'
> -        my (undef, $method, $salt, undef) = split /!/, $stored;
> -        if ($method eq "sha512") {
> -            return $self->_GeneratePassword_sha512($value, $salt) eq $stored;
> +        my (undef, $method, @rest) = split /!/, $stored;
> +        if ($method eq "bcrypt") {
> +            return $self->_GeneratePassword_bcrypt($value, @rest) eq $stored;
> +        } elsif ($method eq "sha512") {
> +            return 0 unless $self->_GeneratePassword_sha512($value, @rest) eq $stored;
>          } else {
>              $RT::Logger->warn("Unknown hash method $method");
>              return 0;
> diff --git a/sbin/rt-test-dependencies.in b/sbin/rt-test-dependencies.in
> index bf9b690..57c2797 100644
> --- a/sbin/rt-test-dependencies.in
> +++ b/sbin/rt-test-dependencies.in
> @@ -179,6 +179,7 @@ CGI::Cookie 1.20
>  CGI::Emulate::PSGI
>  CGI::PSGI 0.12
>  Class::Accessor 0.34
> +Crypt::Eksblowfish
>  CSS::Squish 0.06
>  Date::Extract 0.02
>  Date::Manip
> diff --git a/t/api/password-types.t b/t/api/password-types.t
> index e5155e3..e73bfe6 100644
> --- a/t/api/password-types.t
> +++ b/t/api/password-types.t
> @@ -4,17 +4,22 @@ use warnings;
>  use RT::Test;
>  use Digest::MD5;
>  
> -my $default = "sha512";
> +my $default = "bcrypt";
>  
>  my $root = RT::User->new(RT->SystemUser);
>  $root->Load("root");
>  
> -# Salted SHA-512 (default)
> +# bcrypt (default)
>  my $old = $root->__Value("Password");
>  like($old, qr/^\!$default\!/, "Stored as salted $default");
>  ok($root->IsPassword("password"));
>  is($root->__Value("Password"), $old, "Unchanged after password check");
>  
> +# Salted SHA-512, one round
> +$root->_Set( Field => "Password", Value => RT::User->_GeneratePassword_sha512("other", "salt") );
> +ok($root->IsPassword("other"), "SHA-512 password works");
> +like($root->__Value("Password"), qr/^\!$default\!/, "And is now upgraded to salted $default");
> +
>  # Crypt
>  $root->_Set( Field => "Password", Value => crypt("something", "salt"));
>  ok($root->IsPassword("something"), "crypt()ed password works");
> 
> -----------------------------------------------------------------------
> _______________________________________________
> Rt-commit mailing list
> Rt-commit at lists.bestpractical.com
> http://lists.bestpractical.com/cgi-bin/mailman/listinfo/rt-commit


More information about the rt-devel mailing list