Skip to content
Snippets Groups Projects
Tools.pm 3.96 KiB
package AccessCheck::Tools;

use Mojo::Base -strict;

use Crypt::Bcrypt;
use Crypt::OpenSSL::Random ();
use Encode;
use English qw(-no_match_vars);
use List::Util qw(shuffle);
use List::MoreUtils qw(pairwise);
use MIME::Base64;
use Template;
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) = @_;

    my @string_chars = split(//, $string);
    my @key_chars    = split(//, $key);

    return encode_base64(otp(\@string_chars, \@key_chars));
}

sub decrypt {
    my ($string, $key) = @_;

    my @string_chars = split(//, decode_base64($string));
    my @key_chars = split(//, $key);

    return otp(\@string_chars, \@key_chars);
}

sub otp {
    my ($string, $key) = @_;

    my @chars =
        pairwise { chr(ord($a) ^ ord($b)) }
        @$string,
        @$key;

    return join('', @chars);
}

# 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);
    }