diff --git a/bin/access-check-manager.pl b/bin/access-check-manager.pl index 4c8a22769376bcdf0ba34451cf43e9a8e56967af..e6de646c7d9712ea2c7f800e71c5061caa3834dc 100755 --- a/bin/access-check-manager.pl +++ b/bin/access-check-manager.pl @@ -107,7 +107,7 @@ sub add_account { scope => $configuration->{idp}->{scope}, password => $password, password_crypt => AccessCheck::Tools::encrypt($password, $key), - password_hash => AccessCheck::Tools::sha256_hash($password), + password_hash => AccessCheck::Tools::hash($password), token => $secret, creation_date => DateTime->now(), expiration_date => DateTime->now()->add(days => $validity_period) diff --git a/lib/AccessCheck/App/Step4.pm b/lib/AccessCheck/App/Step4.pm index ce72f758a6e8295d1a10adcfeca1d7ec6d806c98..f3b68a1bd06f3f26bbde8f2a53c4a3f743a74d9e 100644 --- a/lib/AccessCheck/App/Step4.pm +++ b/lib/AccessCheck/App/Step4.pm @@ -79,7 +79,7 @@ sub run { scope => $config->{idp}->{scope}, password => $password, password_crypt => AccessCheck::Tools::encrypt($password, $key), - password_hash => AccessCheck::Tools::sha256_hash($password), + password_hash => AccessCheck::Tools::hash($password), token => $download_token->secret(), creation_date => $creation_date, expiration_date => $account_expiration_date, diff --git a/lib/AccessCheck/Tools.pm b/lib/AccessCheck/Tools.pm index 8ae59a876b34de1ebe0590ae64636cc50a85d237..dc8b3207b40fcc0dac39d292baee95d5d4e70a7e 100644 --- a/lib/AccessCheck/Tools.pm +++ b/lib/AccessCheck/Tools.pm @@ -2,7 +2,8 @@ package AccessCheck::Tools; use Mojo::Base -strict; -use Digest::SHA; +use Crypt::Bcrypt; +use Crypt::OpenSSL::Random (); use Encode; use English qw(-no_match_vars); use List::Util qw(shuffle); @@ -13,6 +14,12 @@ use Template::Constants qw(:chomp); use AccessCheck::Template::Plugin::Quote; +use constant { + SIG_BCRYPT => '2y', + PASSWORD_BCRYPT_DEFAULT_COST => 10, + PASSWORD_BCRYPT_MAX_PASSWORD_LEN => 72, +}; + sub encrypt { my ($string, $key) = @_; @@ -42,11 +49,26 @@ sub otp { return join('', @chars); } -# get SHA256 hash for a string -sub sha256_hash { - my ($s) = @_; +# shamelessly stolen from PHP::Functions::Password +# https://metacpan.org/dist/PHP-Functions-Password/source/lib/PHP/Functions/Password.pm +sub hash { + my ($string) = @_; + + my $salt = Crypt::OpenSSL::Random::random_bytes(16); + my $cost = PASSWORD_BCRYPT_DEFAULT_COST; + + # Treat passwords as strings of bytes + # "\x{100}" becomes "\xc4\x80"; preferred equivalent of Encode::is_utf8($string) && Encode::_utf8_off($password); + utf8::is_utf8($string) && utf8::encode($string); + + # Everything beyond the max password length in bytes for bcrypt is silently ignored. + require bytes; + if (bytes::length($string) > PASSWORD_BCRYPT_MAX_PASSWORD_LEN) { + # $string is already bytes, so the bytes:: prefix is redundant here + $string = substr($string, 0, PASSWORD_BCRYPT_MAX_PASSWORD_LEN); + } - return Digest::SHA::sha256_base64($s); + return Crypt::Bcrypt::bcrypt($string, SIG_BCRYPT, $cost, $salt); } sub generate_password {