In an earlier article I wrote about encrypting Passwords in Perl 6 using crypt. Several people pointed out and then I looked it up in the documentation of crypt as well, that if one uses a recent (released after 2007) version of glibc2 then instead of the default DES algorithm, the crypt function can be convinced to use the much stronger SHA-512 algorithm.
The full explanation of crypt can be found at the Description of the Crypt library function.
Bcrypt would still be a better solution, but till we can install that module this improvement is still better than the original version.
In a nutshell we can use a salt that looks likes this:
$6$SALT
Where the SALT can be any string up to 16 significant characters. (After the $6$ prefix.) (Both according to the man page on crypt and based on my experiment of supplying more characters, but getting the same results as with 16-long salt.)
A further improved version looks like this:
$6$rounds=ROUNDS$SALT
Where ROUNDS is a large number. How large?
As large as possible. See the benchmarks.
The enhanced version of the first example looks like this:
examples/libcrypt_encrypt_password_sha_512.pl
use v6; use Crypt::Libcrypt; my $password = 'secret'; my $salt = '$6$abcdefghIJKLMOPQ'; my $encripted = crypt($password, $salt); say $encripted;
# perl6 libcrypt_encrypt_password_sha_512.pl $6$abcdefghIJKLMOPQ$k7/p0miPUkmsxrxWKDuP5Apnb6MZ6.vfG6xulRYIRCnRtDXXgf5rA7M3yb5TeUdg1I8Hxux6BDbsf.ZABF/re.
The result repeats the salt in this format: ($6$SALT$) and then the encrypted string. The encrypted part is alwyas 86 characters long. So the full lenght of the result depends on the lengths of the salt, but the max length is 106 charcters. Knowing this is important for when we will want to store it in a database.
The first version of the verification process is the same, we can use the full encrypted string that contains the salt as prefix instead of the salt.
If, however we would like to extract the salt, we need to take in account that the length of the salt can vary. So we us the index method to locate the $ sign that ends the salt and use that number in the substr function.
examples/libcrypt_verify_password_sha_512.pl
use v6; use Crypt::Libcrypt; my $password = 'secret'; my $encripted = '$6$abcdefghIJKLMOPQ$k7/p0miPUkmsxrxWKDuP5Apnb6MZ6.vfG6xulRYIRCnRtDXXgf5rA7M3yb5TeUdg1I8Hxux6BDbsf.ZABF/re.'; if crypt($password, $encripted) eq $encripted { say 'Verified'; } my $salt = $encripted.substr(0, $encripted.index('$', 3)); say $salt; if crypt($password, $salt) eq $encripted { say 'Verified'; }
The algorithm is described in the original article
This is the same as earlier, except that we need to generate 16 characters and we will have to prefix it by $6$.
examples/random_salt_sha_512.pl
use v6; my @chars = 'a' ... 'z', 'A' ... 'Z', '0' ... '9'; say @chars; my $salt = '$6$' ~ @chars.pick(16).join(''); say $salt;
This is the same as in the previus article.
In the final script nothing needs to change except the generation of the salt.
examples/users_sha_512.pl
use v6; use Terminal::Readsecret; use Crypt::Libcrypt; main(); sub main() { my $database = 'users.csv'; my $r = prompt("Register or Authenicate? [R/A] "); if $r.lc eq 'r' { say "Register"; my $username = prompt("Username:").lc; # TODO: check for validity of username my %users = read_users($database); # say %users; if %users{$username} { say "This user already exists"; exit; } # check if that username already exists my $password = getsecret("password:"); # save username/hashed password %users{$username} = crypt($password, get_salt()); save_users($database, %users); } elsif $r.lc() eq 'a' { say "Authenticate"; my $username = prompt("Username:"); my $password = getsecret("password:"); my %users = read_users($database); # fetch the hashed password of this user # check if that equals to the hashed version of this password if %users{$username} and crypt($password, %users{$username}) eq $%users{$username} { say "Welcome back!"; } else { say "Invalid access"; } } else { say "Bye"; } } sub read_users($database) { # say "Read $database"; my %users; if $database.IO.e { my @lines = $database.IO.lines(); for @lines -> $line { my ($name, $pw) = $line.split(","); %users{$name} = $pw; } } return %users; } sub save_users($database, %users) { # say $database; my $fh = open($database, :w); LEAVE $fh.close; for %users.keys -> $k { $fh.print("$k,%users{$k}\n"); } } sub get_salt() { my @chars = 'a' ... 'z', 'A' ... 'Z', '0' ... '9'; return '$6$' ~ @chars.pick(16).join(''); }
Published on 2017-04-07