Sometimes bcrypt()
returns a hash it cannot validate with the same passphrase.
So the following code fails:
bcrypt_check($pass, bcrypt($pass, "2b", 4, urandom(16)))
I've seen this happen in CI runners, and started to dig a bit deeper. Usually after ~10 invocations of bcrypt() or so from process startup.
- Perl and modules from apt repo
- Common KVM processor
root@ubuntu:~# perl bcrypt.pl
Hashing |=================[10000] |^C
root@ubuntu:~# perl bcrypt.pl
Hashing |===================[570] |^C
root@ubuntu:~# perl bcrypt.pl
Hashing |===================[570] |^C
root@ubuntu:~# perl bcrypt.pl
Hashing |====[7] |
Linux ubuntu 5.15.0-84-generic #93-Ubuntu SMP Tue Sep 5 17:16:10 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Perl v5.34.0
Crypt::Bcrypt::VERSION=0.007
Crypt::URandom::VERSION=0.36
Session::Token::VERSION=1.503
FAILED after 8 iterations.
bcrypt("7zm3k2olcfcfzjul6nzmg5ihvd57", "2b", "4", pack("h*", "7a6d05ef8dbb2335de7cabb4f363da6e"));
# Returns: $2b$04$n7XO9rg5KjNrv5nJNxYr3e6ttJLOUoRPLOPa8X3wzXzvvcRhBiP1.
# 2nd time: $2b$04$n7XO9rg5KjNrv5nJNxYr3e6ttJLOUoRPLOPa8X3wzXzvvcRhBiP1.
# 3rd time: $2b$04$n7XO9rg5KjNrv5nJNxYr3e6ttJLOUoRPLOPa8X3wzXzvvcRhBiP1.
Test in other process:
/usr/bin/perl -MCrypt::Bcrypt -E 'say Crypt::Bcrypt::bcrypt("7zm3k2olcfcfzjul6nzmg5ihvd57", "2b", "4", pack("h*", "7a6d05ef8dbb2335de7cabb4f363da6e"));'
# Returns: $2b$04$n7XO9rg5KjNrv5nJNxYr3exspCVOe3vyZpvd5S//s1TsMmNelqULe
- Perl and modules from nixpkgs
$ perl bcrypt.pl
Hashing |=[2] |
Linux nixos 6.6.7 #1-NixOS SMP PREEMPT_DYNAMIC Wed Dec 13 17:45:36 UTC 2023 x86_64 GNU/Linux
Perl v5.38.2
Crypt::Bcrypt::VERSION=0.011
Crypt::URandom::VERSION=0.39
Session::Token::VERSION=1.503
FAILED after 3 iterations.
bcrypt("qr6nbgd6tqiakxeaqj4gf5fze9jv", "2b", "4", pack("h*", "1ad4a9ae450342696417030179823db0"));
# Returns: $2b$04$mS0Y4jOuHHXEaR.OjwhRAuwUqQDyU6S8ZC0qhi27Dctb3DKySeekm
# 2nd time: $2b$04$mS0Y4jOuHHXEaR.OjwhRAuwUqQDyU6S8ZC0qhi27Dctb3DKySeekm
# 3rd time: $2b$04$mS0Y4jOuHHXEaR.OjwhRAuwUqQDyU6S8ZC0qhi27Dctb3DKySeekm
Test in other process:
perl -MCrypt::Bcrypt -E 'say Crypt::Bcrypt::bcrypt("qr6nbgd6tqiakxeaqj4gf5fze9jv", "2b", "4", pack("h*", "1ad4a9ae450342696417030179823db0"));'
# Returns: $2b$04$mS0Y4jOuHHXEaR.OjwhRAuTmnYMFi498885cHE5WvFLnjfcY1hsnm
- Using perl:latest
- Modules from CPAN
root@7ebec81ca322:/foo# perl bcrypt.pl
Hashing |=[2] |
Linux 7ebec81ca322 6.6.7 #1-NixOS SMP PREEMPT_DYNAMIC Wed Dec 13 17:45:36 UTC 2023 x86_64 GNU/Linux
Perl v5.38.2
Crypt::Bcrypt::VERSION=0.011
Crypt::URandom::VERSION=0.39
Session::Token::VERSION=1.503
FAILED after 3 iterations.
bcrypt("xba4u9b9zowzijlrggoxhpmkqgoc", "2b", "4", pack("h*", "ebb57107fa6000e433348298a5e77240"));
# Returns: $2b$04$tjqVaI6E.C2xOwgHUl2l/./I9jueefy5I4qAPaR5MtnwZoY6w7EYC
# 2nd time: $2b$04$tjqVaI6E.C2xOwgHUl2l/./I9jueefy5I4qAPaR5MtnwZoY6w7EYC
# 3rd time: $2b$04$tjqVaI6E.C2xOwgHUl2l/./I9jueefy5I4qAPaR5MtnwZoY6w7EYC
Test in other process:
/usr/local/bin/perl -MCrypt::Bcrypt -E 'say Crypt::Bcrypt::bcrypt("xba4u9b9zowzijlrggoxhpmkqgoc", "2b", "4", pack("h*", "ebb57107fa6000e433348298a5e77240"));'
# Returns: $2b$04$tjqVaI6E.C2xOwgHUl2l/.nkAZVeNi344dIxFVl/xpYwk9OpiCGcC
#!/usr/bin/env perl
use v5.34;
use Crypt::Bcrypt qw(bcrypt bcrypt_check);
use Crypt::URandom qw(urandom);
use Session::Token;
use Smart::Comments;
my $i=0;
while (++$i) { ### Hashing |===[%] |
my $password = Session::Token->new(alphabet => 'abcdefghijklmnopqrstuvwxyz23456789',
length => 28)->get;
my $type = "2b";
my $cost = "4";
my $salt = urandom(16);
my $hash = bcrypt($password, $type, $cost, $salt);
my $salt_hex = unpack("h*", $salt);
if (!bcrypt_check($password, $hash)) {
my $hash2 = bcrypt($password, $type, $cost, $salt);
my $hash3 = bcrypt($password, $type, $cost, $salt);
my $exec_cmd = qq|$^X -MCrypt::Bcrypt -E 'say Crypt::Bcrypt::bcrypt("$password", "$type", "$cost", pack("h*", "$salt_hex"));'|;
my $exec_res = `$exec_cmd`;
my $uname = `uname -a`;
die <<"EOT";
$uname
Perl $^V
Crypt::Bcrypt::VERSION=$Crypt::Bcrypt::VERSION
Crypt::URandom::VERSION=$Crypt::URandom::VERSION
Session::Token::VERSION=$Session::Token::VERSION
FAILED after $i iterations.
bcrypt("$password", "$type", "$cost", pack("h*", "$salt_hex"));
# Returns: $hash
# 2nd time: $hash2
# 3rd time: $hash3
Test in other process:
$exec_cmd
# Returns: $exec_res
EOT
}
}
Library used in Crypt::Bcrypt: https://github.com/openwall/crypt_blowfish
Some observations:
https://github.com/Leont/crypt-bcrypt/blob/master/crypt_blowfish.c#L797-L815