Beefy Boxes and Bandwidth Generously Provided by pair Networks
Your skill will accomplish
what the force of many cannot
 
PerlMonks  

Re: Replacing crypt() for password login via a digest - looking for stronger alternative

by cavac (Curate)
on Jun 11, 2021 at 21:08 UTC ( #11133799=note: print w/replies, xml ) Need Help??


in reply to Replacing crypt() for password login via a digest - looking for stronger alternative

I'm using Digest::BCrypt on heavily salted passwords for my systems. I have everything in a database, so my code is probably slightly more complex than yours. Take a look at my Password handler:

package PageCamel::Helpers::Passwords; #---AUTOPRAGMASTART--- use 5.030; use strict; use warnings; use diagnostics; use mro 'c3'; use English; use Carp qw[carp croak confess cluck longmess shortmess]; our $VERSION = 3.5; use autodie qw( close ); use Array::Contains; use utf8; use Data::Dumper; use PageCamel::Helpers::UTF; #---AUTOPRAGMAEND--- # PAGECAMEL (C) 2008-2020 Rene Schickbauer # Developed under Artistic license use Digest; use Data::Entropy::Algorithms qw(rand_bits); use MIME::Base64; use PageCamel::Helpers::DateStrings; use Time::HiRes qw[sleep]; use base qw(Exporter); our @EXPORT= qw(update_password verify_password gen_textsalt); ## no c +ritic (Modules::ProhibitAutomaticExportation) sub gen_textsalt { my $saltbase = '0123456789abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMN +OPQRSTUVWXYZ'; my $salt = ''; my $count = int(rand(20))+20; for(1..$count) { my $pos = int(rand(length($saltbase))); $salt .= substr($saltbase, $pos, 1); } return $salt; } sub update_password { my ($dbh, $username, $password) = @_; # While pre- and postsalt does not much for complexity, it helps p +reventing rainbow tables attacks. # I know, the bcrypt salt already does that, in case of a general +bcrypt breach, this should # make it a bit more difficult. my $presalt = gen_textsalt(); my $postsalt = gen_textsalt(); my $bsalt = rand_bits(16*8); # 16 octets (16 bytes at 8 bits) #print length($bsalt) . "\n"; #print $bsalt . "\n"; my $bsalt_b64 = encode_base64($bsalt, ''); #my $cost = getCurrentYear() - 2000 + 3; my $cost = 5; # FIXME: Make SystemSetting my $bcrypt = Digest->new('Bcrypt'); $bcrypt->cost($cost); $bcrypt->salt($bsalt); $bcrypt->add($presalt); $bcrypt->add($password); $bcrypt->add($postsalt); my $pwsalted = $bcrypt->b64digest; my $upsth = $dbh->prepare("UPDATE users SET password_prefix = ?, password_postfix = ?, password_bcrypt_hash = ?, password_bcrypt_salt = ?, password_bcrypt_cost = ?, next_password_change = now() + interval +'12 weeks' WHERE username = ?") or croak($dbh->errstr); if(!$upsth->execute($presalt, $postsalt, $pwsalted, $bsalt_b64, $c +ost, $username)) { return 0; } return 1; } sub verify_password { my ($dbh, $username, $password) = @_; # Pre-initialize for random pw calculations in case no user is fou +nd (there should be no # measurable time difference for unknown users. This will make it +harder to guess is a username # exists) my $presalt = gen_textsalt(); my $postsalt = gen_textsalt(); my $bsalt = rand_bits(16*8); # 16 octets (16 bytes at 8 bits) #my $cost = getCurrentYear() - 2000 + 3; my $cost = 16; # FIXME: Make SystemSetting my $pwhash = ''; my $isLocked = 0; my $selsth = $dbh->prepare("SELECT account_locked, password_prefix, password_postfix, password_bcrypt_hash, password_bcrypt_salt, password_bcrypt_cost FROM users WHERE username = ? AND password_prefix != '' AND password_postfix != '' AND password_bcrypt_hash != '' AND password_bcrypt_salt != '' ") or croak($dbh->errstr); if(!$selsth->execute($username)) { return 0; } my $found = 0; while((my $line = $selsth->fetchrow_arrayref)) { my $bsalt_b64; ($isLocked, $presalt, $postsalt, $pwhash, $bsalt_b64, $cost) = + @{$line}; $bsalt = decode_base64($bsalt_b64); $found = 1; last; } $selsth->finish; my $bcrypt = Digest->new('Bcrypt'); $bcrypt->cost($cost); $bcrypt->salt($bsalt); $bcrypt->add($presalt); $bcrypt->add($password); $bcrypt->add($postsalt); my $pwsalted = $bcrypt->b64digest; # sleep for a random amount of time, up to a second fo further lim +it # bruteforcing and "unknown user" detection my $sleeptime = int(rand(900) + 100) / 1000; sleep($sleeptime); if($isLocked || !$found || $pwsalted ne $pwhash) { return 0; } return 1; } 1; __END__ =head1 NAME PageCamel::Helpers::Passwords - handle passwords in a PageCamel databa +se =head1 SYNOPSIS use PageCamel::Helpers::Passwords; =head1 DESCRIPTION This central module does all the actual password handling for PageCame +l projects. This way, changing the hashing algorithm or adapting its +strengh (vs time) can be done in one central place in the code. =head2 gen_textsalt Randomly generate a salt used for hashing passwords. =head2 update_password Update a password in the database (also generates a new salt). =head2 verify_password Verify correctness of a password. =head1 IMPORTANT NOTE This module is part of the PageCamel framework. Currently, only limite +d support and documentation exists outside my DarkPAN repositories. This source +is currently only provided for your reference and usage in other projects + (just copy&paste what you need, see license terms below). To see PageCamel in action and for news about the project, visit my blog at L<https://cavac.at>. =head1 AUTHOR Rene Schickbauer, E<lt>cavac@cpan.orgE<gt> =head1 COPYRIGHT AND LICENSE Copyright (C) 2008-2020 Rene Schickbauer This library is free software; you can redistribute it and/or modify it under the same terms as Perl itself, either Perl version 5.10.0 or, at your option, any later version of Perl 5 you may have available. =cut

I'm also enforcing quite complex passwords that are very hard to guess.

perl -e 'use Crypt::Digest::SHA256 qw[sha256_hex]; print substr(sha256_hex("the Answer To Life, The Universe And Everything"), 6, 2), "\n";'
  • Comment on Re: Replacing crypt() for password login via a digest - looking for stronger alternative
  • Select or Download Code

Replies are listed 'Best First'.
Re^2: Replacing crypt() for password login via a digest - looking for stronger alternative
by bliako (Monsignor) on Jun 18, 2021 at 08:40 UTC

    May I make a suggestions? in a post in this thread I mention not to underestimate the danger in using not-cryptographically secure RNG (and Perl's isn't).

    Also, for checking password suitability one could run candidate password through the 8billion leaked passwords! Too bad it can't be implemented with an online password checker service!!! Or can it? I surely have a hunch it will be popular. https://www.security.org/how-secure-is-my-password hehe

    Another thought (i.e. just "theory") for selecting a password: how about checking its statistical distribution? Although finding the S.D. is not reliable with just 8-16 chars I find it a good idea as part of a wider suite of tools, even if only for checking if it resembles english or german, etc., language's S.D. Ideally, I guess, a good password must have a uniform S.D. and also conditional probabilities between adjacent characters to follow uniform S.D., e.g. P(pass[i]|pass[i-1]) = 0.5. That can be extended to pass[i]|pass[j] perhaps.

    bw, bliako

Log In?
Username:
Password:

What's my password?
Create A New User
Domain Nodelet?
Node Status?
node history
Node Type: note [id://11133799]
help
Chatterbox?
and the web crawler heard nothing...

How do I use this? | Other CB clients
Other Users?
Others contemplating the Monastery: (2)
As of 2021-10-21 17:42 GMT
Sections?
Information?
Find Nodes?
Leftovers?
    Voting Booth?
    My first memorable Perl project was:







    Results (83 votes). Check out past polls.

    Notices?