Camelia

Encrypting Passwords in Perl 6 using crypt and SHA-512

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.

Note! This site is about Perl 6.
If you are looking for a solution for Perl 5, please check out the Perl 5 tutorial.

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.

Encrypt password

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.

Verify password

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';
}

Register and Authenticate

The algorithm is described in the original article

Generate random salt

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;

Accepting passwords on the command line

This is the same as in the previus article.

Register and Authenticate

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('');
}


The Perl 6 Tricks and Treats newsletter has been around for a while. If you are interested to get special notification when there is new content on this site, it is the best way to keep track:
Email:
Full name:
This is a newsletter temporarily running on my personal site (szabgab.com) using Mailman, till I implement an alternative system in Perl 6.
Gabor Szabo
Written by Gabor Szabo

Published on 2017-04-07



Comments

In the comments, please wrap your code snippets within <pre> </pre> tags and use spaces for indentation.
comments powered by Disqus
Suggest a change
Elapsed time: 2.3196221

Support my Perl 6 book!

I am writing a book on Web Application Development in Perl 6 and raising money via crowdfunding. Please support my effort!

Perl 6 Tricks and Treats newsletter

Register to the free newsletter now, and get updates and news.
Email:
Name: