Search code examples
postgresqlperlauthenticationshadancer

Dancer2 Auth::Extensible Not Accepting Hashed Password


I've generated a SHA-1 hash using Dancer2::Plugin::Passphrase with the following code:

get '/generate' => {
    my $phrase = passphrase('my_password')->generate({ algorithm => 'SHA-1'});
    return $phrase->rfc2307();
};

The result looks something like this:

{SSHA}+2Dro1/ntPchT93mgvYMKGjdzy+XKXK1agsG3//hKuLNrQAK

and that's what I store in my PostgreSQL database.

I'm using Dancer2::Plugin::Auth::Extensible as my login solution, but I've yet to get it to work with encrypted passwords. I put a test account into my database where username='test' and password='test', and that works fine. But username='test2' and password='{SSHA}+2Dro1/ntPchT93mgvYMKGjdzy+XKXK1agsG3//hKuLNrQAK' doesn't work. The login page just silently fails and reloads.

I turned on DBI_TRACE and don't seen much difference between the two except that the account with the plain text password returns this:

[glm::App:3515] debug @2016-05-10 21:02:23> users accepted user test in /usr/local/share/perl/5.20.2/Dancer2/Core/Route.pm l. 137

and the account with the encrypted password returns this:

[glm::App:3523] core @2016-05-10 21:04:21> looking for get /login in /usr/local/share/perl/5.20.2/Dancer2/Core/App.pm l. 1210
[glm::App:3523] core @2016-05-10 21:04:21> Entering hook core.app.before_request in (eval 62) l. 1
[glm::App:3523] core @2016-05-10 21:04:21> Entering hook core.app.after_request in (eval 62) l. 1
127.0.0.1 - - [10/May/2016:21:04:21 +0100] "POST /login?return_url=%2F     HTTP/1.1" 401 383 "http://localhost:5000/login?return_url=%2F" "Mozilla/5.0 (X11; Ubuntu; Linux x86_64; rv:41.0) Gecko/20100101 Firefox/41.0"

I'm sure I'm missing something, but the CPAN page doesn't detail how to handle encrypted passwords. It just says it will be easy. I guess I'm reading that as "encrypted passwords will be handled automagically." What am I missing?

Config

This is the relevant portion of my config

plugins: 
 Auth::Extensible:
   realms:
      users:
       provider: 'Database'
 Database:
  dsn: 'dbi:Pg:service=test'

App.pm

Below is what I'm doing in the App.pm. You can see that I'm just trying to require a login for the home page. Maybe I need some '/login' code?

package glm::App;

use Dancer2;
use Dancer2::Plugin::Database;
use Dancer2::Plugin::Auth::Extensible;
use Dancer2::Plugin::Passphrase;

use Template;

our $VERSION = '0.1';

get '/' => require_login sub {
    my $sth = database->prepare('SELECT name FROM product', { RaiseError => 1 });
    $sth->execute();

    template 'create_list', {
        'products' => $sth->fetchall_hashref('name'),
    };
};

get '/generate'=> sub {
    my $phrase = passphrase('my_password')->generate({ algorithm => 'SHA-1' });
    return $phrase->rfc2307(); # right now I just manually copy and paste this into the database
};

My database follows the suggested schema for users, passwords, and roles.

Maybe the only other relevant bit of information I can think of is that if I use an encryption scheme not recognized by Digest, I get an error from Digest.pm. That would seem to indicate that it's recognizing the hashed password and trying to decrypt it, but for whatever reason it's just not working. Or it's working and redirecting back to the home page... But why doesn't it do that with 'test,test'?


Solution

  • TL;DR You're using two different methods for hashing, so the generated hashes are incompatible.

    Dancer2::Plugin::Auth::Extensible::Provider::Database uses Crypt::SaltedHash:

    sub encrypt_password {
        my ($self, $password, $algorithm) = @_;
        $algorithm ||= 'SHA-1';
        my $crypt = Crypt::SaltedHash->new(algorithm => $algorithm);
        $crypt->add($password);
        $crypt->generate;
    }
    

    This generates a hash like:

    {SSHA}qTEaPf8KRPt6XBQXIlQhlWstgBz64coW
    

    Compare that to what you got from Dancer2::Plugin::Passphrase:

    {SSHA}+2Dro1/ntPchT93mgvYMKGjdzy+XKXK1agsG3//hKuLNrQAK
    

    Notice that the lengths are different. Dancer2::Plugin::Passphrase uses a 16-byte salt by default, while Crypt::SaltedHash uses a 4-byte salt.


    Although you could tell Dancer2::Plugin::Passphrase to use a 4-byte salt, it's much easier to just use Crypt::SaltedHash everywhere. The Dancer2::Plugin::Auth::Extensible documentation explains how to do this:

    A simple script called generate-crypted-password to generate RFC2307-style hashed passwords is included, or you can use Crypt::SaltedHash yourself to do so, or use the slappasswd utility if you have it installed.

    For example:

    $ generate-crypted-password 
    Enter plain-text password ?> foo
    Result: {SSHA}zdXPS0QqxyKlzXwHxjJ3rsU19Td4ABzW