#!/usr/bin/env perl

##
## Author......: See docs/credits.txt
## License.....: MIT
##

use strict;
use warnings;

use Crypt::PBKDF2;
use Crypt::CBC;

sub module_constraints { [[0, 256], [-1, -1], [-1, -1], [-1, -1], [-1, -1]] }

sub module_generate_hash
{
  my $word           = shift;
  my $salt           = shift; # unused
  my $version        = shift // 5;
  my $iter           = shift // 10000;
  my $user_salt      = shift // random_hex_string (128);
  my $ck_salt        = shift // random_hex_string (128);
  my $user_iv        = shift // random_hex_string (32);
  my $masterkey_blob = shift; # use this as hint regular or verify call

  my $kdf = Crypt::PBKDF2->new
  (
    hash_class => 'HMACSHA1',
    iterations => $iter,
    output_len => 32
  );

  my $user_salt_bin = pack ("H*", $user_salt);

  my $key_bin = $kdf->PBKDF2 ($user_salt_bin, $word);

  my $iv_bin = pack ("H*", $user_iv);

  my $cipher = Crypt::CBC->new ({
    key         => $key_bin,
    cipher      => "Crypt::Rijndael",
    iv          => $iv_bin,
    literal_key => 1,
    header      => "none",
    keysize     => 32,
    padding     => "standard",
  });

  my $decrypted_bin = random_bytes (83);

  if (defined $masterkey_blob)
  {
    my $encrypted_bin = pack ("H*", $masterkey_blob);

    my $test_bin = $cipher->decrypt ($encrypted_bin);

    if (length ($test_bin) == 83)
    {
      $decrypted_bin = $test_bin;
    }
  }

  my $encrypted_bin = $cipher->encrypt ($decrypted_bin);

  my $hash = sprintf ("\$ab\$%u*0*%u*%s*%s*%s*%s", $version, $iter, $user_salt, $ck_salt, $user_iv, unpack ("H*", $encrypted_bin));

  return $hash;
}

sub module_verify_hash
{
  my $line = shift;

  my ($hash, $word) = split (':', $line);

  return unless defined $hash;
  return unless defined $word;

  my ($tag, $cipher, $iter, $user_salt, $ck_salt, $user_iv, $masterkey_blob) = split (/\*/, $hash);

  my (undef, $signature, $version) = split (/\$/, $tag);

  return unless $signature eq "ab";
  return unless $cipher    eq "0";

  my $word_packed = pack_if_HEX_notation ($word);

  my $new_hash = module_generate_hash ($word_packed, undef, $version, $iter, $user_salt, $ck_salt, $user_iv, $masterkey_blob);

  return ($new_hash, $word);
}

1;