#!/usr/bin/env perl

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

##
## Installation script for all perl and python modules:
##
## tools/install_modules.sh
##

##
## If you want to add a new hash mode, follow the STEP comments.
##

use strict;
use warnings;

use Authen::Passphrase::LANManager;
use Authen::Passphrase::MySQL323;
use Authen::Passphrase::NTHash;
use Authen::Passphrase::PHPass;
use Convert::EBCDIC            qw (ascii2ebcdic);
use Crypt::CBC;
use Crypt::DES;
use Crypt::Digest::RIPEMD160   qw (ripemd160_hex);
use Crypt::Digest::Whirlpool   qw (whirlpool_hex);
use Crypt::ECB                 qw (encrypt);
use Crypt::Eksblowfish::Bcrypt qw (bcrypt en_base64);
use Crypt::Mode::ECB;
use Crypt::MySQL               qw (password41);
use Crypt::OpenSSH::ChachaPoly;
use Crypt::PBKDF2;
use Crypt::RC4;
use Crypt::Rijndael;
use Crypt::ScryptKDF           qw (scrypt_hash scrypt_raw scrypt_b64);
use Crypt::Skip32;
use Crypt::Twofish;
use Crypt::UnixCrypt_XS        qw (crypt_rounds fold_password base64_to_int24 block_to_base64 int24_to_base64);
use Digest::MD4                qw (md4 md4_hex);
use Digest::MD5                qw (md5 md5_hex);
use Digest::SHA                qw (sha1 sha256 sha384 sha512 sha1_hex sha224_hex sha256_hex sha384_hex sha512_hex hmac_sha1 hmac_sha256 hmac_sha512);
use Digest::SHA1;
use Digest::SHA3               qw (sha3_224_hex sha3_256_hex sha3_384_hex sha3_512_hex);
use Digest::Keccak             qw (keccak_224_hex keccak_256_hex keccak_384_hex keccak_512_hex);
use Digest::HMAC               qw (hmac hmac_hex);
use Digest::BLAKE2             qw (blake2b_hex);
use Digest::GOST               qw (gost gost_hex);
use Digest::HMAC_MD5           qw (hmac_md5);
use Digest::CRC                qw (crc32);
use Digest::CMAC;
use Digest::SipHash            qw (siphash);
use Digest::Perl::MD5;
use Encode;
use JSON;
use MIME::Base32               qw (encode_base32 decode_base32);
use MIME::Base64               qw (encode_base64 decode_base64 encode_base64url decode_base64url);
use Net::DNS::RR::NSEC3;
use Net::DNS::SEC;
use POSIX                      qw (strftime ceil);
use Text::Iconv;

my $hashcat = "./hashcat";

my $MAX_LEN = 55;

## STEP 1: Add your hash mode to this array.
#
# This array contains all supported hash modes.
#
##

my $MODES =
[
      0,    10,    11,    12,    20,    21,    22,    23,    30,    40,    50,
     60,   100,   101,   110,   111,   112,   120,   121,   122,   125,   130,
    131,   132,   133,   140,   141,   150,   160,   200,   300,   400,   500,
    600,   900,  1000,  1100,  1300,  1400,  1410,  1411,  1420,  1430,  1440,
   1441,  1450,  1460,  1500,  1600,  1700,  1710,  1711,  1720,  1730,  1740,
   1722,  1731,  1750,  1760,  1800,  2100,  2400,  2410,  2500,  2600,  2611,
   2612,  2711,  2811,  3000,  3100,  3200,  3710,  3711,  3300,  3500,  3610,
   3720,  3800,  3910,  4010,  4110,  4210,  4300,  4400,  4500,  4520,  4521,
   4522,  4600,  4700,  4800,  4900,  5100,  5300,  5400,  5500,  5600,  5700,
   5800,  6000,  6100,  6300,  6400,  6500,  6600,  6700,  6800,  6900,  7000,
   7100,  7200,  7300,  7400,  7500,  7700,  7701,  7800,  7801,  7900,  8000,
   8100,  8200,  8300,  8400,  8500,  8600,  8700,  8900,  9100,  9200,  9300,
   9400,  9500,  9600,  9700,  9800,  9900, 10000, 10100, 10200, 10300, 10400,
  10500, 10600, 10700, 10800, 10900, 11000, 11100, 11200, 11300, 11400, 11500,
  11600, 11700, 11750, 11760, 11800, 11850, 11860, 11900, 12000, 12001, 12100,
  12200, 12300, 12400, 12600, 12700, 12800, 12900, 13000, 13100, 13200, 13300,
  13400, 13500, 13600, 13800, 13900, 14000, 14100, 14400, 14700, 14800, 14900,
  15000, 15100, 15200, 15300, 15400, 15500, 15600, 15700, 15900, 16000, 16100,
  16200, 16300, 16400, 16500, 16600, 16700, 16800, 16900, 17300, 17400, 17500,
  17600, 17700, 17800, 17900, 18000, 18100, 18200, 18300, 99999
];

## STEP 2a: If your hash mode does not need a salt, add it to this array.
#
# This array contains all unsalted hash-modes that are handled in the 'default'
# branches in all three single, passthrough and verify test functions. There
# still are some unsalted hash-modes which are handled differently and are not
# listed here; they are caught in separate if conditions accordingly.
#
##

my $COMMON_UNSALTED_MODES =
[
      0,   100,   101,   133,   200,   300,   600,   900,  1000,  1300,  1400,
   1700,  2600,  3500,  4300,  4400,  4500,  4600,  4700,  5100,  5700,  6000,
   6100,  6900,  9900, 10800, 11500, 11700, 11800, 16400, 17300, 17400, 17500,
  17600, 17700, 17800, 17900, 18000, 99999
];

## STEP 2b: If your hash-mode has a salt without any specific syntax,
##          add it to this array. Else look for STEP 2c (several spots).
#
# Same as above, only for salted hashes without specific salt formats.
#
##

my $COMMON_DEFAULT_SALTED_MODES =
[
     10,    20,    23,    30,    40,    50,    60,   110,   120,   130,   140,
    150,   160,  1410,  1420,  1430,  1440,  1450,  1460,  1710,  1720,  1730,
   1740,  1750,  1760,  3610,  3710,  3720,  3910,  4010,  4110,  4210, 11750,
  11760, 11850, 11860, 18100
];

# Arrays for hash modes with maximum password length 15

my $LESS_FIFTEEN = [500, 1600, 1800, 3200, 6300, 7400, 10500, 10700];

# Arrays for hash modes with unusual salts

my $ALLOW_LONG_SALT =
[
   2500,  4520,  4521,  5500,  5600,  7100,  7200,  7300,  9400,  9500,  9600,
   9700,  9800, 10400, 10500, 10600, 10700,  1100, 11000, 11200, 11300, 11400,
  11600, 12600, 13500, 13800, 15000, 16900
];

my $IS_UTF16LE =
[
     30,    40,   130,   131,   132,   133,   140,   141,  1000,  1100,  1430,
   1440,  1441,  1730,  1740,  1731,  5500,  5600,  8000,  9400,  9500,  9600,
   9700,  9800, 11600, 13500, 13800
];

my $LOTUS_MAGIC_TABLE =
[
  0xbd, 0x56, 0xea, 0xf2, 0xa2, 0xf1, 0xac, 0x2a, 0xb0, 0x93, 0xd1, 0x9c,
  0x1b, 0x33, 0xfd, 0xd0, 0x30, 0x04, 0xb6, 0xdc, 0x7d, 0xdf, 0x32, 0x4b,
  0xf7, 0xcb, 0x45, 0x9b, 0x31, 0xbb, 0x21, 0x5a, 0x41, 0x9f, 0xe1, 0xd9,
  0x4a, 0x4d, 0x9e, 0xda, 0xa0, 0x68, 0x2c, 0xc3, 0x27, 0x5f, 0x80, 0x36,
  0x3e, 0xee, 0xfb, 0x95, 0x1a, 0xfe, 0xce, 0xa8, 0x34, 0xa9, 0x13, 0xf0,
  0xa6, 0x3f, 0xd8, 0x0c, 0x78, 0x24, 0xaf, 0x23, 0x52, 0xc1, 0x67, 0x17,
  0xf5, 0x66, 0x90, 0xe7, 0xe8, 0x07, 0xb8, 0x60, 0x48, 0xe6, 0x1e, 0x53,
  0xf3, 0x92, 0xa4, 0x72, 0x8c, 0x08, 0x15, 0x6e, 0x86, 0x00, 0x84, 0xfa,
  0xf4, 0x7f, 0x8a, 0x42, 0x19, 0xf6, 0xdb, 0xcd, 0x14, 0x8d, 0x50, 0x12,
  0xba, 0x3c, 0x06, 0x4e, 0xec, 0xb3, 0x35, 0x11, 0xa1, 0x88, 0x8e, 0x2b,
  0x94, 0x99, 0xb7, 0x71, 0x74, 0xd3, 0xe4, 0xbf, 0x3a, 0xde, 0x96, 0x0e,
  0xbc, 0x0a, 0xed, 0x77, 0xfc, 0x37, 0x6b, 0x03, 0x79, 0x89, 0x62, 0xc6,
  0xd7, 0xc0, 0xd2, 0x7c, 0x6a, 0x8b, 0x22, 0xa3, 0x5b, 0x05, 0x5d, 0x02,
  0x75, 0xd5, 0x61, 0xe3, 0x18, 0x8f, 0x55, 0x51, 0xad, 0x1f, 0x0b, 0x5e,
  0x85, 0xe5, 0xc2, 0x57, 0x63, 0xca, 0x3d, 0x6c, 0xb4, 0xc5, 0xcc, 0x70,
  0xb2, 0x91, 0x59, 0x0d, 0x47, 0x20, 0xc8, 0x4f, 0x58, 0xe0, 0x01, 0xe2,
  0x16, 0x38, 0xc4, 0x6f, 0x3b, 0x0f, 0x65, 0x46, 0xbe, 0x7e, 0x2d, 0x7b,
  0x82, 0xf9, 0x40, 0xb5, 0x1d, 0x73, 0xf8, 0xeb, 0x26, 0xc7, 0x87, 0x97,
  0x25, 0x54, 0xb1, 0x28, 0xaa, 0x98, 0x9d, 0xa5, 0x64, 0x6d, 0x7a, 0xd4,
  0x10, 0x81, 0x44, 0xef, 0x49, 0xd6, 0xae, 0x2e, 0xdd, 0x76, 0x5c, 0x2f,
  0xa7, 0x1c, 0xc9, 0x09, 0x69, 0x9a, 0x83, 0xcf, 0x29, 0x39, 0xb9, 0xe9,
  0x4c, 0xff, 0x43, 0xab
];

my $PDF_PADDING =
[
  0x28, 0xbf, 0x4e, 0x5e, 0x4e, 0x75, 0x8a, 0x41, 0x64, 0x00, 0x4e, 0x56,
  0xff, 0xfa, 0x01, 0x08, 0x2e, 0x2e, 0x00, 0xb6, 0xd0, 0x68, 0x3e, 0x80,
  0x2f, 0x0c, 0xa9, 0xfe, 0x64, 0x53, 0x69, 0x7a
];

my $CISCO_BASE64_MAPPING =
{
  'A', '.', 'B', '/', 'C', '0', 'D', '1', 'E', '2', 'F', '3', 'G', '4', 'H',
  '5', 'I', '6', 'J', '7', 'K', '8', 'L', '9', 'M', 'A', 'N', 'B', 'O', 'C',
  'P', 'D', 'Q', 'E', 'R', 'F', 'S', 'G', 'T', 'H', 'U', 'I', 'V', 'J', 'W',
  'K', 'X', 'L', 'Y', 'M', 'Z', 'N', 'a', 'O', 'b', 'P', 'c', 'Q', 'd', 'R',
  'e', 'S', 'f', 'T', 'g', 'U', 'h', 'V', 'i', 'W', 'j', 'X', 'k', 'Y', 'l',
  'Z', 'm', 'a', 'n', 'b', 'o', 'c', 'p', 'd', 'q', 'e', 'r', 'f', 's', 'g',
  't', 'h', 'u', 'i', 'v', 'j', 'w', 'k', 'x', 'l', 'y', 'm', 'z', 'n', '0',
  'o', '1', 'p', '2', 'q', '3', 'r', '4', 's', '5', 't', '6', 'u', '7', 'v',
  '8', 'w', '9', 'x', '+', 'y', '/', 'z'
};

if (scalar @ARGV < 1)
{
  usage_die ();
}

my $type;
my $mode;
my $len;

$type = shift @ARGV;

if ($type ne "verify")
{
  if (scalar @ARGV > 1)
  {
    $mode = shift @ARGV;
    $len  = shift @ARGV;
  }
  elsif (scalar @ARGV == 1)
  {
    $mode = shift @ARGV;
    $len  = 0;
  }
  else
  {
    $len = 0;
  }

  if ($type eq "single")
  {
    single ($mode);
  }
  elsif ($type eq "passthrough")
  {
    passthrough ($mode);
  }
  else
  {
    usage_die ();
  }
}
else
{
  if (scalar @ARGV != 4)
  {
    usage_die ();
  }

  my $mode      = shift @ARGV;
  my $hash_file = shift @ARGV;
  my $in_file   = shift @ARGV;
  my $out_file  = shift @ARGV;

  my $db;

  open (IN,  "<", $hash_file) or die ("$hash_file: $!\n");

  # clever ? the resulting database could be huge
  # but we need some way to map lines in hashfile w/ cracks
  # maybe rli2 way would be more clever (needs sorted input)

  while (my $line = <IN>)
  {
    $line =~ s/[\n\r]*$//;

    $db->{$line} = undef;
  }

  close (IN);

  verify ($mode, $db, $in_file, $out_file);
}

# Array lookup
sub is_in_array
{
  my $value = shift;
  my $array = shift;

  return grep { $_ eq $value } @{$array};
}

sub verify
{
  my $mode     = shift;
  my $db       = shift;
  my $in_file  = shift;
  my $out_file = shift;

  my $hash_in;
  my $hash_out;
  my $iter;
  my $salt;
  my $word;
  my $param;
  my $param2;
  my $param3;
  my $param4;
  my $param5;
  my $param6;
  my $param7;
  my $param8;
  my $param9;
  my $param10;
  my $param11;

  open (IN,  "<", $in_file)  or die ("$in_file: $!\n");
  open (OUT, ">", $out_file) or die ("$out_file: $!\n");

  my $len;

  my $base64   = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
  my $itoa64_1 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
  my $itoa64_2 = "./ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";

  while (my $line = <IN>)
  {
    chomp ($line);

    $line =~ s/\n$//;
    $line =~ s/\r$//;

    # remember always do "exists ($db->{$hash_in})" checks as soon as possible and don't forget it

    # unsalted
    if (is_in_array ($mode, $COMMON_UNSALTED_MODES)
     || $mode == 2400 || $mode ==  3000
     || $mode == 8600 || $mode == 16000)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $word = substr ($line, $index + 1);
    }
    # hash:salt
    elsif (is_in_array ($mode, $COMMON_DEFAULT_SALTED_MODES)
        || $mode ==    11 || $mode ==    12 || $mode ==    21 || $mode ==    22
        || $mode ==   112 || $mode ==   121 || $mode ==  1100 || $mode ==  2410
        || $mode ==  2611 || $mode ==  2711 || $mode ==  2811 || $mode ==  3100
        || $mode ==  3800 || $mode ==  4520 || $mode ==  4521 || $mode ==  4522
        || $mode ==  4900 || $mode ==  5800 || $mode ==  8400 || $mode == 11000
        || $mode == 12600 || $mode == 13500 || $mode == 13800 || $mode == 13900
        || $mode == 14000 || $mode == 14100 || $mode == 14400 || $mode == 14900
        || $mode == 15000)
    {
      # get hash
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);

      # identify lenghts of both salt and plain

      my $salt_plain = substr ($line, $index1 + 1);

      my $num_cols = () = $salt_plain =~ /:/g;

      my $index2;
      my $matched = 0;
      my $start = 0;

      $word = undef;

      # fuzzy
      foreach (my $i = 0; $i < $num_cols; $i++)
      {
        $index2 = index ($salt_plain, ":", $start);

        next if $index2 < 0;

        $start = $index2 + 1;

        $salt = substr ($salt_plain, 0, $index2);
        $word = substr ($salt_plain, $index2 + 1);

        # can't be true w/ wrong $hash:$salt, otherwise the
        # algo must have many collisions

        if (exists ($db->{$hash_in . ":" . $salt}))
        {
          $hash_in = $hash_in . ":" . $salt;
          $matched = 1;
          last;
        }
      }

      next unless ($matched); # therefore: true == exists ($db->{$hash_in}
      next unless (! defined ($db->{$hash_in}));
    }
    # dcc2
    elsif ($mode == 2100)
    {
      # get hash
      my $index1 = index ($line, "\$DCC2\$");

      next if $index1 != 0;

      # iterations
      my $index2 = index ($line, "#", $index1 + 1);

      next if $index2 < 1;

      $iter = substr ($line, $index1 + 6, $index2 - $index1 - 6);

      # get hash
      $index1 = index ($line, "#");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1 + 1);

      # identify lenghts of both salt and plain

      my $salt_plain = substr ($line, $index2 + 1);

      my $num_cols = () = $salt_plain =~ /:/g;

      my $matched = 0;
      my $start   = 0;
      my $index3  = 0;
      my $raw_hash;

      $word = undef;

      # fuzzy
      foreach (my $i = 0; $i < $num_cols; $i++)
      {
        $index2 = index ($salt_plain, ":", $start);

        next if $index2 < 0;

        $start = $index2 + 1;

        $index3 = rindex ($salt_plain, "#", $index2);

        $raw_hash = substr ($salt_plain, $index3 + 1, $index2 - $index3 - 1);
        $salt = substr ($salt_plain, 0, $index3);
        $word = substr ($salt_plain, $index2 + 1);

        if (exists ($db->{$hash_in . $salt . "#" .$raw_hash}))
        {
          $hash_in = $hash_in . $salt . "#" . $raw_hash;
          $matched = 1;
          last;
        }
      }

      next unless ($matched); # therefore: true == exists ($db->{$hash_in}
      next unless (! defined ($db->{$hash_in}));
    }
    # salt:hash guaranteed only : because of hex salt
    elsif ($mode == 7300)
    {
      # split hash and plain
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $salt = substr ($line, 0, $index1);

      $salt = pack ("H*", $salt);

      my $rest = substr ($line, $index1 + 1);

      my $index2 = index ($rest, ":");

      next if $index2 < 1;

      $hash_in = substr ($rest, 0, $index2);

      $word = substr ($rest, $index2 + 1);

      next unless (exists ($db->{$salt . ":" . $hash_in}) and (! defined ($db->{$hash_in})));
    }
    # 1salthash fixed
    elsif ($mode == 8100)
    {
      # split hash and plain
      $salt = substr ($line, 1, 8);

      my $rest = substr ($line, 1 + 8);

      my $index2 = index ($rest, ":");

      next if $index2 < 1;

      $hash_in = substr ($rest, 0, $index2);

      $word = substr ($rest, $index2 + 1);

      next unless (exists ($db->{"1" . $salt . $hash_in}) and (! defined ($db->{$hash_in})));
    }
    # base64 and salt embedded SSHA1, salt length = total lenght - 20
    elsif ($mode == 111)
    {
      # split hash and plain
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      # remove signature
      my $plain_base64 = substr ($hash_in, 6);

      # base64 decode to extract salt
      my $decoded = decode_base64 ($plain_base64);

      $salt = substr ($decoded, 20);
    }
    # base64 and salt embedded SSHA512, salt length = total length - 64
    elsif ($mode == 1711)
    {
      # split hash and plain
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      # remove signature
      my $plain_base64 = substr ($hash_in, 9);

      # base64 decode to extract salt
      my $decoded = decode_base64 ($plain_base64);

      $salt = substr ($decoded, 64);
    }
    # macOS (first 8 hex chars is salt)
    # ArubaOS (the signature gets added in gen_hash)
    elsif ($mode == 122 || $mode == 1722 || $mode == 125)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = substr ($hash_in, 0, 8);
    }
    # MSSQL (2000, 2005 AND 2012), salt after version number
    elsif ($mode == 131 || $mode == 132 || $mode == 1731)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = substr ($hash_in, 6, 8);
    }
    # Sybase ASE
    elsif ($mode == 8000)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = substr ($hash_in, 6, 16);
    }
    # episerver salts
    elsif ($mode == 141 || $mode == 1441)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 = index ($line, "*", 14);

      #extract salt from base64
      my $plain_base64 = substr ($hash_in, 14, $index2 - 14);

      $salt = decode_base64 ($plain_base64);
    }
    # phpass (first 8 after $P$/$H$ -- or $S$ with drupal7)
    elsif ($mode == 400 || $mode == 7900)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = substr ($hash_in, 4, 8);

      # iterations = 2 ^ cost (where cost == $iter)
      $iter = index ($itoa64_1, substr ($hash_in, 3, 1));
    }
    # $something$[rounds=iter$]salt$     (get last $, then check iter)
    elsif ($mode == 500 || $mode == 1600 || $mode == 1800 || $mode == 3300 || $mode == 7400)
    {
      my $index1 = index ($line, ":", 30);

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $index1 = index ($hash_in,  ",", 1);
      my $index2 = index ($hash_in, "\$", 1);

      if ($index1 != -1)
      {
        if ($index1 < $index2)
        {
          $index2 = $index1;
        }
      }

      $param = substr ($hash_in, $index2, 1);

      $index2++;

      # rounds= if available
      $iter = 0;

      if (substr ($hash_in, $index2, 7) eq "rounds=")
      {
        my $old_index = $index2;

        $index2 = index ($hash_in, "\$", $index2 + 1);

        next if $index2 < 1;

        $iter = substr ($hash_in, $old_index + 7, $index2 - $old_index - 7);

        $index2++;
      }

      # get salt
      my $index3 = rindex ($hash_in, "\$");

      next if $index3 < 1;

      $salt = substr ($hash_in, $index2, $index3 - $index2);
    }
    # descrypt (salt in first 2 char)
    elsif ($mode == 1500)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = substr ($hash_in, 0, 2);
    }
    # bcrypt $something$something$salt.hash
    elsif ($mode == 3200)
    {
      my $index1 = index ($line, ":", 33);

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 =  index ($hash_in, "\$", 4);

      $iter = substr ($hash_in, 4, $index2 - 4);

      my $plain_base64 = substr ($hash_in, $index2 + 1, 22);

      # base64 mapping
      my $encoded = "";

      for (my $i = 0; $i < length ($plain_base64); $i++)
      {
        my $char  = substr ($plain_base64, $i, 1);
        $encoded .= substr ($base64, index ($itoa64_2, $char), 1);
      }

      $salt = decode_base64 ($encoded);
    }
    # md5 (chap)
    elsif ($mode == 4800)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      my $index3 = index ($line, ":", $index2 + 1);

      next if $index3 < 1;

      $salt = substr ($line, $index1 + 1, $index3 - $index1 - 1);

      $word = substr ($line, $index3 + 1);

      $hash_in = substr ($line, 0, $index3);
    }
    # IKE (md5 and sha1)
    elsif ($mode == 5300 || $mode == 5400)
    {
      my $num_cols = () = $line =~ /:/g;

      next unless ($num_cols >= 9);

      my $index1 = -1;
      my $failed =  0;

      for (my $j = 0; $j < 9; $j++)
      {
        $index1 = index ($line, ":", $index1 + 1);

        if ($index1 < 1)
        {
          $failed = 1;
          last;
        }
      }

      next if ($failed);

      $word = substr ($line, $index1 + 1);

      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 = rindex ($line, ":", $index1 - 1);

      $salt = substr ($line, 0, $index2);
    }
    # NetNTLMv1
    elsif ($mode == 5500)
    {
      my $index1 = index ($line, "::");

      next if $index1 < 1;

      my $index2 = index ($line, ":", $index1 + 2);

      next if $index2 < 1;

      $index2 = index ($line, ":", $index2 + 1);

      next if $index2 < 1;

      $salt = substr ($line, 0, $index2);

      $index2 = index ($line, ":", $index2 + 1);

      next if $index2 < 1;

      $salt .= substr ($line, $index2 + 1, 16);

      $index2 = index ($line, ":", $index2 + 1);

      next if $index2 < 1;

      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $word = substr ($line, $index2 + 1);
    }
    # NetNTLMv2
    elsif ($mode == 5600)
    {
      my $index1 = index ($line, "::");

      next if $index1 < 1;

      my $index2 = index ($line, ":", $index1 + 2);

      next if $index2 < 1;

      $index2 = index ($line, ":", $index2 + 1);

      next if $index2 < 1;

      $salt = substr ($line, 0, $index2);

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $salt .= substr ($line, $index1 + 1, $index2 - $index1 - 1);

      $hash_in = substr ($line, 0, $index2);

      # do it later on for this hash mode:
      # next unless ((exists ($db->{$hash_in}) and (! defined ($db->{$hash_in}))) or (exists ($db->{$mod}) and (! defined ($db->{$mod}))));

      $word = substr ($line, $index2 + 1);
    }
    # AIX smd5 something BRACE salt$
    elsif ($mode == 6300)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 =  index ($hash_in, "}");
      my $index3 = rindex ($hash_in, "\$");

      $salt = substr ($hash_in, $index2 + 1, $index3 - $index2 - 1);
    }
    # AIX: something$salt$ (no $ at position 1)
    elsif ($mode == 6400 || $mode == 6500 || $mode == 6700)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 =  index ($hash_in, "}");
      my $index3 =  index ($hash_in, "\$");
      my $index4 = rindex ($hash_in, "\$");

      $salt = substr ($hash_in, $index3 + 1, $index4 - $index3 - 1);

      $iter = substr ($hash_in, $index2 + 1, $index3 - $index2 - 1);
    }
    # 1Password, agilekeychain
    elsif ($mode == 6600)
    {
      my $num_cols = () = $line =~ /:/g;

      next unless ($num_cols > 2);

      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $iter = substr ($line, 0, $index1);

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $salt = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      $salt .= substr ($line, $index2 + 1, $index1 - $index2 - 33);

      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $word = substr ($line, $index1 + 1);
    }
    # 1Password, cloudkeychain
    elsif ($mode == 8200)
    {
      my @datas = split (":", $line);

      next if scalar @datas < 4;

      my $hash = shift @datas;
      $salt    = shift @datas;
      $iter    = shift @datas;
      my $data = shift @datas;

      $hash_in = $hash . ":" . $salt . ":" . $iter . ":" . $data;

      $salt .= $data;

      $word = join (":", @datas);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # lastpass (hash:iter:salt)
    elsif ($mode == 6800)
    {
      my $index1 = index ($line, ":", 34);

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);

      # identify lenghts of both salt and plain

      my $salt_plain = substr ($line, $index1 + 1);

      my $num_cols = () = $salt_plain =~ /:/g;

      my $index2;
      my $matched = 0;
      my $start = 0;

      $word = undef;

      # fuzzy
      foreach (my $i = 0; $i < $num_cols; $i++)
      {
        $index2 = index ($salt_plain, ":", $start);

        next if $index2 < 1;

        $start = $index2 + 1;

        $salt = substr ($salt_plain, 0, $index2);
        $word = substr ($salt_plain, $index2 + 1);

        # can't be true w/ wrong $hash:$salt, otherwise the
        # algo must have many collisions

        if (exists ($db->{$hash_in . ":" . $salt}))
        {
          $hash_in = $hash_in . ":" . $salt;
          $matched = 1;
          last;
        }
      }

      next unless ($matched); # therefore: true == exists ($db->{$hash_in}
      next unless (! defined ($db->{$hash_in}));

      $index1 = index ($hash_in, ":");
      $index2 = index ($hash_in, ":", $index1 + 1);

      $iter = substr ($hash_in, $index1 + 1, $index2 - $index1 - 1);
      $salt = substr ($hash_in, $index2 + 1);
    }
    # Fortigate
    elsif ($mode == 7000)
    {
      my $index1 = index ($line, ":");

      next if $index1 != 47;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (substr ($hash_in, 0, 3) eq "AK1");

      my $decoded = decode_base64 (substr ($hash_in, 3));

      $salt = substr ($decoded, 0, 12);
      $salt = unpack ("H*", $salt);
    }
    # macOS 10.* : $something$iter$salt$
    elsif ($mode == 7100)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 =  index ($hash_in, "\$", 5);

      next if $index2 < 1;

      my $index3 =  index ($hash_in, "\$", $index2 + 1);

      $salt = substr ($hash_in, $index2 + 1, $index3 - $index2 - 1);

      $iter = substr ($hash_in, 4, $index2 - 4);

      next if (int ($iter) < 1);
    }
    # grub: something1.something2.something3.iter.salt.
    elsif ($mode == 7200)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index2 =  index ($hash_in, ".", 19);

      next if $index2 < 1;

      my $index3 =  index ($hash_in, ".", $index2 + 1);

      $salt = substr ($hash_in, $index2 + 1, $index3 - $index2 - 1);

      $iter = substr ($hash_in, 19, $index2 - 19);

      next if (int ($iter) < 1);
    }
    # $something1$something2$something3$something4$salt$
    elsif ($mode == 7500 )
    {
      my $index1 = index ($line, "\$", 11);

      next if $index1 < 1;

      my $index2 = index ($line, "\$", $index1 + 1);

      next if $index2 < 1;

      my $index3 = index ($line, "\$", $index2 + 1);

      next if $index3 < 1;

      $index2 = index ($line, ":", $index3 + 1);

      next if $index2 < 1;

      $hash_in = substr ($line, 0, $index2);
      $word    = substr ($line, $index2 + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt  = substr ($hash_in, 11, $index3 - 10);
      $salt .= substr ($hash_in, $index2 - 32) . "\$\$";
      $salt .= substr ($hash_in, $index3 + 1, $index2 - $index3 - 32 - 1);
    }
    # $salt$$hash
    elsif ($mode == 7700 || $mode == 7800 || $mode == 7701 || $mode == 7801)
    {
      my $index1 = index ($line, ":");

      next if $index1 < 1;

      my @split1 = split (":", $line);

      my @split2 = split ('\$', $split1[0]);

      next unless scalar @split2 == 2;

      $hash_in = $split1[0];

      if (scalar @split1 > 1)
      {
        $word = $split1[1];
      }
      else
      {
        $word = "";
      }

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = $split2[0];
    }
    # DNSSEC
    elsif ($mode == 8300)
    {
      my @datas = split (":", $line);

      next if scalar @datas != 5;

      my $hash;
      my $domain;

      ($hash, $domain, $salt, $iter, $word) = @datas;

      $hash_in = $hash . ":" . $domain . ":" . $salt . ":" . $iter;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      $salt = $domain . ":" . $salt;
    }
    # RACF
    elsif ($mode == 8500)
    {
      my @line_elements = split (":", $line);

      next if scalar @line_elements < 2;

      # get hash and word

      $hash_in = shift @line_elements;

      $word = join (":", @line_elements);

      # get signature

      my @hash_elements = split ('\*', $hash_in);

      next unless ($hash_elements[0] eq '$racf$');

      $salt = $hash_elements[1];

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # DOMINO 6
    elsif ($mode == 8700)
    {
      # split hash and plain
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $plain_base64 = substr ($hash_in, 2, -1);

      ($_, $salt, $param) = domino_decode ($plain_base64);
    }
    # PHPS
    elsif ($mode == 2612)
    {
      next unless (substr ($line, 0, 6) eq '$PHPS$');

      # get hash
      my $index1 = index ($line, "\$", 6);

      next if $index1 < 1;

      $salt = substr ($line, 6, $index1 - 6);

      $salt = pack ("H*", $salt);

      my $index2 = index ($line, "\:", $index1 + 1);

      next if $index2 < 1;

      $word = substr ($line, $index2 + 1);

      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Mediawiki B type
    elsif ($mode == 3711)
    {
      next unless (substr ($line, 0, 3) eq '$B$');

      # get hash
      my $index1 = index ($line, "\$", 3);

      next if $index1 < 1;

      $salt = substr ($line, 3, $index1 - 3);

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $word = substr ($line, $index2 + 1);

      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # scrypt
    elsif ($mode == 8900)
    {
      next unless (substr ($line, 0, 7) eq 'SCRYPT:');

      # get hash
      my $index1 = index ($line, ":", 7);

      next if $index1 < 1;

      # N
      my $N = substr ($line, 7, $index1 - 7);

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      # r
      my $r = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      # p
      my $p = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      $param  = $N;
      $param2 = $r;
      $param3 = $p;

      $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      # salt
      $salt = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      $salt = decode_base64 ($salt);

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      # digest

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # LOTUS 8
    elsif ($mode == 9100)
    {
      # split hash and plain
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $base64_part = substr ($hash_in, 2, -1);

      ($_, $salt, $iter, $param) = domino_85x_decode ($base64_part);

      next if ($iter < 1);
    }
    # Cisco $8$ - PBKDF2-HMAC-SHA256
    elsif ($mode == 9200)
    {
      next unless (substr ($line, 0, 3) eq '$8$');

      # get hash
      my $index1 = index ($line, "\$", 3);

      next if $index1 != 17;

      my $index2 = index ($line, "\$", $index1 + 1);

      # salt
      $salt = substr ($line, 3,  $index1 - 3);

      $index1 = index ($line, ":", $index1 + 1);

      next if $index1 < 1;

      # digest

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Cisco $9$ - scrypt
    elsif ($mode == 9300)
    {
      next unless (substr ($line, 0, 3) eq '$9$');

      # get hash
      my $index1 = index ($line, "\$", 3);

      next if $index1 != 17;

      my $index2 = index ($line, "\$", $index1 + 1);

      # salt
      $salt = substr ($line, 3,  $index1 - 3);

      $index1 = index ($line, ":", $index1 + 1);

      next if $index1 < 1;

      # digest

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Office 2007
    elsif ($mode == 9400)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 8;

      next unless (shift @data eq '$office$');
      next unless (shift @data eq '2007');
      next unless (shift @data eq '20');

      my $aes_key_size = shift @data;

      next unless (($aes_key_size eq '128') || ($aes_key_size eq '256'));
      next unless (shift @data eq '16');

      next unless (length $data[0] == 32);
      next unless (length $data[1] == 32);
      next unless (length $data[2] == 40);

      $salt   = shift @data;
      $param  = shift @data;
      $param2 = $aes_key_size;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Office 2010
    elsif ($mode == 9500)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 8;

      next unless (shift @data eq '$office$');
      next unless (shift @data eq '2010');
      next unless (shift @data eq '100000');
      next unless (shift @data eq '128');
      next unless (shift @data eq '16');

      next unless (length $data[0] == 32);
      next unless (length $data[1] == 32);
      next unless (length $data[2] == 64);

      $salt  = shift @data;
      $param = shift @data;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Office 2013
    elsif ($mode == 9600)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 8;

      next unless (shift @data eq '$office$');
      next unless (shift @data eq '2013');
      next unless (shift @data eq '100000');
      next unless (shift @data eq '256');
      next unless (shift @data eq '16');

      next unless (length $data[0] == 32);
      next unless (length $data[1] == 32);
      next unless (length $data[2] == 64);

      $salt  = shift @data;
      $param = shift @data;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Office Old $1 $2
    elsif ($mode == 9700)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 4;

      my $signature = shift @data;

      next unless (($signature eq '$oldoffice$0') || ($signature eq '$oldoffice$1'));

      next unless (length $data[0] == 32);
      next unless (length $data[1] == 32);
      next unless (length $data[2] == 32);

      $salt  = shift @data;
      $param = shift @data;
      $param2 = substr ($signature, 11, 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Office Old $3 $4
    elsif ($mode == 9800)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 4;

      my $signature = shift @data;

      next unless (($signature eq '$oldoffice$3') || ($signature eq '$oldoffice$4'));

      next unless (length $data[0] == 32);
      next unless (length $data[1] == 32);
      next unless (length $data[2] == 40);

      $salt  = shift @data;
      $param = shift @data;
      $param2 = substr ($signature, 11, 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Django (PBKDF2-SHA256)
    elsif ($mode == 10000)
    {
      next unless (substr ($line, 0, 14) eq 'pbkdf2_sha256$');

      # get hash
      my $index1 = index ($line, "\$", 14);

      next if $index1 < 1;

      my $index2 = index ($line, "\$", $index1 + 1);

      # iter

      $iter = substr ($line, 14,  $index1 - 14);


      # salt
      $salt = substr ($line, $index1 + 1,  $index2 - $index1 - 1);

      # digest

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # SipHash
    elsif ($mode == 10100)
    {
      my $hash;

      ($hash, undef, undef, $salt, $word) = split ":", $line;

      next unless defined $hash;
      next unless defined $salt;
      next unless defined $word;

      next unless (length $hash == 16);
      next unless (length $salt == 32);

      my $hash_in = sprintf ("%s:2:4:%s", $hash, $salt);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Cram MD5
    elsif ($mode == 10200)
    {
      next unless (substr ($line, 0, 10) eq '$cram_md5$');

      # get hash
      my $index1 = index ($line, "\$", 10);

      next if $index1 < 1;

      # challenge

      my $challengeb64 = substr ($line, 10,  $index1 - 10);
      $salt = decode_base64 ($challengeb64);

      # response

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      my $responseb64 = substr ($line, $index1 + 1, $index2 - $index1 - 1);
      my $response = decode_base64 ($responseb64);

      $param = substr ($response, 0, length ($response) - 32 - 1); # -1 is for space

      $word = substr ($line, $index2 + 1);
      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # SAP CODVN H (PWDSALTEDHASH) iSSHA-1
    elsif ($mode == 10300)
    {
      next unless (substr ($line, 0, 10) eq '{x-issha, ');

      # get iterations

      my $index1 = index ($line, "}", 10);

      next if $index1 < 1;

      $iter = substr ($line, 10, $index1 - 10);

      $iter = int ($iter);

      # base64 substring

      my $base64_encoded = substr ($line, $index1 + 1);
      my $base64_decoded = decode_base64 ($base64_encoded);

      $salt = substr ($base64_decoded, 20);

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $word = substr ($line, $index2 + 1);
      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PDF 1.1 - 1.3 (Acrobat 2 - 4)
    elsif ($mode == 10400)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 11;

      next unless (shift @data eq '$pdf$1');
      next unless (shift @data eq '2');
      next unless (shift @data eq '40');
      my $P      = shift @data;
      next unless (shift @data eq '0');
      next unless (shift @data eq '16');
      my $id     = shift @data;
      next unless (shift @data eq '32');
      my $u      = shift @data;
      next unless (shift @data eq '32');
      my $o      = shift @data;

      $salt   = $id;
      $param  = $u;
      $param2 = $o;
      $param3 = $P;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PDF 1.4 - 1.6 (Acrobat 5 - 8)
    elsif ($mode == 10500)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data == 11;

      my $V      = shift @data; $V = substr ($V, 5, 1);
      my $R      = shift @data;
      next unless (shift @data eq '128');
      my $P      = shift @data;
      my $enc    = shift @data;
      next unless (shift @data eq '16');
      my $id     = shift @data;
      next unless (shift @data eq '32');
      my $u      = shift @data;
      next unless (shift @data eq '32');
      my $o      = shift @data;

      $salt   = $id;
      $param  = $u;
      $param2 = $o;
      $param3 = $P;
      $param4 = $V;
      $param5 = $R;
      $param6 = $enc;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PDF 1.7 Level 3 (Acrobat 9)
    elsif ($mode == 10600)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data >= 11;

      next unless (shift @data eq '$pdf$5');
      next unless (shift @data eq '5');
      next unless (shift @data eq '256');
      next unless (shift @data eq '-1028');
      next unless (shift @data eq '1');
      next unless (shift @data eq '16');
      my $id     = shift @data;
      my $rest   = join "*", @data;

      $salt   = $id;
      $param  = $rest;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PDF 1.7 Level 8 (Acrobat 10 - 11)
    elsif ($mode == 10700)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\*/, $hash_in;

      next unless scalar @data >= 11;

      next unless (shift @data eq '$pdf$5');
      next unless (shift @data eq '6');
      next unless (shift @data eq '256');
      next unless (shift @data eq '-1028');
      next unless (shift @data eq '1');
      next unless (shift @data eq '16');
      my $id     = shift @data;
      my $rest   = join "*", @data;

      $salt   = $id;
      $param  = $rest;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PBKDF2-HMAC-SHA256
    elsif ($mode == 10900)
    {
      next unless (substr ($line, 0, 7) eq 'sha256:');

      # iterations
      my $index1 = index ($line, ":", 7);

      next if $index1 < 1;

      $iter = substr ($line, 7, $index1 - 7);

      # salt

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $salt = substr ($line, $index1 + 1,  $index2 - $index1 - 1);

      $salt = decode_base64 ($salt);

      # end of digest

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      # additional param = output len of pbkdf2

      my $digest64_encoded = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      my $digest = decode_base64 ($digest64_encoded);

      $param = length ($digest);

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PostgreSQL MD5 Authentication
    elsif ($mode == 11100)
    {
      next unless (substr ($line, 0, 10) eq '$postgres$');

      my $index1 = index ($line, "*", 10);

      next if $index1 < 1;

      # the user name

      $param = substr ($line, 10, $index1 - 10);

      # get the 4 byte salt

      my $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 1;

      $salt = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      # word / hash

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # MySQL MD5 Authentication
    elsif ($mode == 11200)
    {
      next unless (substr ($line, 0, 9) eq '$mysqlna$');

      my $index1 = index ($line, "*", 9);

      next if $index1 < 1;

      # salt

      $salt = substr ($line, 9, $index1 - 9);

      # word / hash

      $index1 = index ($line, ":", $index1 + 1);

      next if $index1 < 1;

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # WPA-EAPOL-PBKDF2
    elsif ($mode == 2500)
    {
      print "ERROR: verify currently not supported for WPA-EAPOL-PBKDF2 (because of hashcat's output format)\n";

      exit (1);
    }
    # Bitcoin/Litecoin wallet.dat
    elsif ($mode == 11300)
    {
      print "ERROR: verify currently not supported for Bitcoin/Litecoin wallet.dat because of unknown crypt data\n";

      exit (1);
    }
    # SIP digest authentication (MD5)
    elsif ($mode == 11400)
    {
      next unless (substr ($line, 0, 6) eq '$sip$*');

      # URI_server:

      my $index1 = index ($line, "*", 6);

      next if $index1 < 0;

      $param10 = substr ($line, 6, $index1 - 6);

      next if (length ($param10) > 32);

      # URI_client:

      my $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 0;

      $param11 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next if (length ($param11) > 32);

      # user:

      $index1 = index ($line, "*", $index2 + 1);

      next if $index1 < 0;

      $param = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      next if (length ($param) > 12);

      # realm:

      $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 0;

      $param2 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next if (length ($param2) > 20);

      # method:

      $index1 = index ($line, "*", $index2 + 1);

      next if $index1 < 0;

      $param6 = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      next if (length ($param6) > 24);

      # URI_prefix:

      $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 0;

      $param7 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next if (length ($param7) > 10);

      # URI_resource:

      $index1 = index ($line, "*", $index2 + 1);

      next if $index1 < 0;

      $param8 = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      next if (length ($param8) > 32);

      # URI_suffix:

      $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 0;

      $param9 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next if (length ($param9) > 32);

      # nonce:

      $index1 = index ($line, "*", $index2 + 1);

      next if $index1 < 0;

      $salt = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      next if (length ($salt) > 34);

      # nonce_client:

      $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 0;

      $param4 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next if (length ($param4) > 12);

      # nonce_count:

      $index1 = index ($line, "*", $index2 + 1);

      next if $index1 < 0;

      $param3 = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      next if (length ($param3) > 10);

      # qop:

      $index2 = index ($line, "*", $index1 + 1);

      next if $index2 < 0;

      $param5 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next if (length ($param5) > 8);

      # directive:

      $index1 = index ($line, "*", $index2 + 1);

      next if $index1 < 0;

      my $directive = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      next unless ($directive eq "MD5");

      # hash_buf:

      $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 0;

      my $hex_digest = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      next unless (length ($hex_digest) == 32);

      $word = substr ($line, $index2 + 1);
      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # 7-Zip
    elsif ($mode == 11600)
    {
      next unless (substr ($line, 0, 4) eq '$7z$');

      # p

      my $index1 = index ($line, '$', 4);

      next if $index1 < 0;

      my $p = substr ($line, 4, $index1 - 4);

      next unless ($p eq "0");

      # num cycle power

      my $index2 = index ($line, '$', $index1 + 1);

      next if $index2 < 0;

      $iter = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      # seven zip salt length

      $index1 = index ($line, '$', $index2 + 1);

      next if $index1 < 0;

      $param = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      # seven zip salt

      $index2 = index ($line, '$', $index1 + 1);

      next if $index2 < 0;

      $param2 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      # salt len

      $index1 = index ($line, '$', $index2 + 1);

      next if $index1 < 0;

      $param3 = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      # salt

      $index2 = index ($line, '$', $index1 + 1);

      next if $index2 < 0;

      $salt = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      $salt = pack ("H*", $salt);

      # crc / hash

      $index1 = index ($line, '$', $index2 + 1);

      next if $index1 < 0;

      my $crc = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      # ignore this crc, we don't need to pass it to gen_hash ()

      # data len

      $index2 = index ($line, '$', $index1 + 1);

      next if $index2 < 0;

      $param4 = substr ($line, $index1 + 1, $index2 - $index1 - 1);

      # unpack size

      $index1 = index ($line, '$', $index2 + 1);

      next if $index1 < 0;

      $param5 = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      # data

      $index2 = index ($line, ':', $index1 + 1);

      next if $index2 < 0;

      $param6 = substr ($line, $index1 + 1, $index2 - $index1 - 1);
      $param6 = pack ("H*", $param6);

      $word = substr ($line, $index2 + 1);
      $hash_in = substr ($line, 0, $index2);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PBKDF2-HMAC-MD5
    elsif ($mode == 11900)
    {
      next unless (substr ($line, 0, 4) eq 'md5:');

      # iterations
      my $index1 = index ($line, ":", 4);

      next if $index1 < 1;

      $iter = substr ($line, 4, $index1 - 4);

      # salt

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $salt = substr ($line, $index1 + 1,  $index2 - $index1 - 1);

      $salt = decode_base64 ($salt);

      # end of digest

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      # additional param = output len of pbkdf2

      my $digest64_encoded = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      my $digest = decode_base64 ($digest64_encoded);

      $param = length ($digest);

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PBKDF2-HMAC-SHA1
    elsif ($mode == 12000)
    {
      next unless (substr ($line, 0, 5) eq 'sha1:');

      # iterations
      my $index1 = index ($line, ":", 5);

      next if $index1 < 1;

      $iter = substr ($line, 5, $index1 - 5);

      # salt

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $salt = substr ($line, $index1 + 1,  $index2 - $index1 - 1);

      $salt = decode_base64 ($salt);

      # end of digest

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      # additional param = output len of pbkdf2

      my $digest64_encoded = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      my $digest = decode_base64 ($digest64_encoded);

      $param = length ($digest);

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # PBKDF2-HMAC-SHA512
    elsif ($mode == 12100)
    {
      next unless (substr ($line, 0, 7) eq 'sha512:');

      # iterations
      my $index1 = index ($line, ":", 7);

      next if $index1 < 1;

      $iter = substr ($line, 7, $index1 - 7);

      # salt

      my $index2 = index ($line, ":", $index1 + 1);

      next if $index2 < 1;

      $salt = substr ($line, $index1 + 1,  $index2 - $index1 - 1);

      $salt = decode_base64 ($salt);

      # end of digest

      $index1 = index ($line, ":", $index2 + 1);

      next if $index1 < 1;

      # additional param = output len of pbkdf2

      my $digest64_encoded = substr ($line, $index2 + 1, $index1 - $index2 - 1);

      my $digest = decode_base64 ($digest64_encoded);

      $param = length ($digest);

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # ecryptfs
    elsif ($mode == 12200)
    {
      next unless (substr ($line, 0, 12) eq '$ecryptfs$0$');

      # check if default salt

      $param = 1;

      $param = 0 if (substr ($line, 12, 2) eq '1$');

      # salt

      $salt = "";

      my $index1 = 12;

      if ($param == 0) # we need to extract the salt
      {
        $index1 = index ($line, '$', $index1);

        next if $index1 < 1;

        my $index2 = index ($line, '$', $index1 + 1);

        next if $index2 < 1;

        $salt = substr ($line, $index1 + 1, $index2 - $index1 - 1);

        $index1 = $index2;
      }

      $index1 = index ($line, ':', $index1 + 1);

      next if $index1 < 1;

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Oracle T: Type (Oracle 12+)
    elsif ($mode == 12300)
    {
      my $index1 = index ($line, ':');

      next if ($index1 != 160);

      # salt

      $salt = substr ($line, 128, 32);

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # BSDi Crypt, Extended DES
    elsif ($mode == 12400)
    {
      next unless (substr ($line, 0, 1) eq '_');

      my $index1 = index ($line, ':', 20);

      next if ($index1 != 20);

      # iter

      $iter = substr ($line, 1, 4);

      $iter = base64_to_int24 ($iter);

      # salt

      $salt = substr ($line, 5, 4);

      # word / hash

      $word = substr ($line, $index1 + 1);
      $hash_in = substr ($line, 0, $index1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Blockchain, My Wallet
    elsif ($mode == 12700)
    {
      my $index1 = index ($line, ':');

      next if ($index1 < 0);

      $hash_in = substr ($line, 0, $index1);
      $word = substr ($line, $index1 + 1);

      my (undef, $signature, $data_len, $data_buf) = split '\$', $hash_in;

      next unless ($signature eq "blockchain");

      next unless (($data_len * 2) == length $data_buf);

      $salt  = substr ($data_buf, 0, 32);
      $param = substr ($data_buf, 32);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 12800)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split /\,/, $hash_in;

      next unless scalar @data == 4;

      next unless (shift @data eq 'v1;PPH1_MD4');

      $salt = shift @data;
      $iter = shift @data;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 12900)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      next unless length $hash_in == 160;

      $param = substr ($hash_in, 0, 64);
      $salt  = substr ($hash_in, 128, 32);
      $iter  = 4096;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 13000)
    {
      my $hash_line;

      ($hash_line, $word) = split ":", $line;

      next unless defined $hash_line;
      next unless defined $word;

      my @data = split ('\$', $hash_line);

      next unless scalar @data == 8;

      shift @data;

      my $signature    = shift @data;
      my $salt_len     = shift @data;
      my $salt_buf     = shift @data;
      my $iterations   = shift @data;
      my $iv           = shift @data;
      my $pswcheck_len = shift @data;
      my $pswcheck     = shift @data;

      next unless ($signature eq "rar5");
      next unless ($salt_len == 16);
      next unless ($pswcheck_len == 8);

      $salt    = $salt_buf;
      $iter    = $iterations;
      $hash_in = $pswcheck;
      $param   = $iv;

      next unless (exists ($db->{$hash_line}) and (! defined ($db->{$hash_line})));
    }
    elsif ($mode == 13100)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 8;

      shift @data;

      my $signature = shift @data;
      my $algorithm = shift @data;
      my $user      = shift @data;
      $user         = substr ($user, 1);
      my $realm     = shift @data;
      my $spn       = shift @data;
      $spn          = substr ($spn, 0, length ($spn) - 1);
      my $checksum  = shift @data;
      my $edata2    = shift @data;

      next unless ($signature eq "krb5tgs");
      next unless (length ($checksum) == 32);
      next unless (length ($edata2) >= 64);

      $salt = $user . '$' . $realm . '$' . $spn . '$';

      $param  = $checksum;
      $param2 = $edata2;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 13200)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\*', $hash_in);

      next unless scalar @data == 5;

      my $signature = shift @data;
      my $version   = shift @data;
      my $iteration = shift @data;
      my $mysalt    = shift @data;
      my $digest    = shift @data;

      next unless ($signature eq '$axcrypt$');
      next unless (length ($mysalt) == 32);
      next unless (length ($digest) == 48);

      $salt  = $iteration . '*' . $mysalt;
      $param = $digest;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 13300)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 3;

      shift @data;

      my $signature = shift @data;
      my $digest    = shift @data;

      $param = length ($digest);

      next unless ($signature eq 'axcrypt_sha1');
      next unless (($param == 32) || ($param == 40));

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 13400)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\*', $hash_in);

      next unless (scalar @data == 9
                  || scalar @data == 11
                  || scalar @data == 12
                  || scalar @data == 14);

      my $signature = shift @data;
      next unless ($signature eq '$keepass$');

      my $version = shift @data;
      next unless ($version == 1 || $version == 2);

      my $iteration          = shift @data;

      my $algorithm          = shift @data;

      my $final_random_seed  = shift @data;

      if ($version == 1)
      {
        next unless (length ($final_random_seed) == 32);
      }
      elsif ($version == 2)
      {
        next unless (length ($final_random_seed) == 64);
      }

      my $transf_random_seed = shift @data;
      next unless (length ($transf_random_seed) == 64);

      my $enc_iv = shift @data;
      next unless (length ($enc_iv) == 32);

      if ($version == 1)
      {
        my $contents_hash  = shift @data;
        next unless (length ($contents_hash) == 64);

        my $inline_flags   = shift @data;
        next unless ($inline_flags == 1);

        my $contents_len   = shift @data;

        my $contents       = shift @data;
        next unless (length ($contents) == $contents_len * 2);
      }
      elsif ($version == 2)
      {
        my $expected_bytes = shift @data;
        next unless (length ($expected_bytes) == 64);

        my $contents_hash  = shift @data;
        next unless (length ($contents_hash) == 64);
      }

      if (scalar @data == 12 || scalar @data == 14)
      {
        my $inline_flags = shift @data;
        next unless ($inline_flags == 1);

        my $keyfile_len  = shift @data;
        next unless ($keyfile_len == 64);

        my $keyfile     = shift @data;
        next unless (length ($keyfile) == $keyfile_len);
      }

      $salt = substr ($hash_in, length ("*keepass*") + 1);
      $param = 1; # distinguish between encrypting vs decrypting

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 13600)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\*', $hash_in);

      next unless scalar @data == 10;

      my $tag_start     = shift @data;
      my $type          = shift @data;
      my $mode          = shift @data;
      my $magic         = shift @data;
      my $salt          = shift @data;
      my $verify_bytes  = shift @data;
      my $length        = shift @data;
      my $data          = shift @data;
      my $auth          = shift @data;
      my $tag_end       = shift @data;

      next unless ($tag_start eq '$zip2$');
      next unless ($tag_end   eq '$/zip2$');

      $param  = $type;
      $param2 = $mode;
      $param3 = $magic;
      $param4 = $salt;
      $param5 = $length;
      $param6 = $data;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # itunes backup 9/10
    elsif (($mode == 14700) || ($mode == 14800))
    {
      ($hash_in, $word) = split ":", $line;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      my $index1 = index ($hash_in, '*');

      next unless ($index1 == 15);

      # signature

      my $signature = substr ($hash_in, 0, $index1);

      next unless ($signature eq '$itunes_backup$');

      my $index2 = index ($hash_in, '*', $index1 + 1);

      next unless ($index2 >= 0);

      # version

      my $version = substr ($hash_in, $index1 + 1, $index2 - $index1 - 1);

      if ($mode == 14700)
      {
        next unless ($version eq "9");
      }
      else
      {
        next unless ($version eq "10");
      }

      $index1 = index ($hash_in, '*', $index2 + 1);

      next unless ($index1 >= 0);

      # wpky

      my $wpky = substr ($hash_in, $index2 + 1, $index1 - $index2 - 1);

      next unless (length ($wpky) == 80);

      $wpky = pack ("H*", $wpky);

      $param = $wpky;

      $index2 = index ($hash_in, '*', $index1 + 1);

      next unless ($index2 >= 0);

      # iterations

      $iter = substr ($hash_in, $index1 + 1, $index2 - $index1 - 1);
      $iter = int ($iter);

      next unless ($iter > 0);

      $index1 = index ($hash_in, '*', $index2 + 1);

      next unless ($index1 >= 0);

      # salt

      $salt = substr ($hash_in, $index2 + 1, $index1 - $index2 - 1);

      next unless (length ($salt) == 40);

      # dpic and dpsl

      if ($mode == 14700)
      {
        $index2 = index ($hash_in, '**', $index1 + 1);

        next unless ($index2 != $index1 + 1);
      }
      else
      {
        $index2 = index ($hash_in, '*', $index1 + 1);

        next unless ($index2 >= 0);

        # dpic

        my $dpic = substr ($hash_in, $index1 + 1, $index2 - $index1 - 1);

        $dpic = int ($dpic);

        next unless ($dpic > 0);

        $param2 = $dpic;

        # dpsl

        my $dpsl = substr ($hash_in, $index2 + 1);

        next unless (length ($dpsl) == 40);

        $dpsl = pack ("H*", $dpsl);

        $param3 = $dpsl;
      }
    }
    # base64 and salt embedded SSHA256, salt length = total length - 32
    elsif ($mode == 1411)
    {
      # split hash and plain
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));

      # remove signature
      my $plain_base64 = substr ($hash_in, 9);

      # base64 decode to extract salt
      my $decoded = decode_base64 ($plain_base64);

      $salt = substr ($decoded, 32);
    }
    # Atlassian (PBKDF2-HMAC-SHA1)
    elsif ($mode == 12001)
    {
      my $index = index ($line, ":");

      next if $index < 1;

      $hash_in = substr ($line, 0, $index);
      $word    = substr ($line, $index + 1);

      next unless (substr ($hash_in, 0, 9) eq '{PKCS5S2}');

      # base64 buf

      my $base64_buf = substr ($hash_in, 9);
      my $base64_buf_decoded = decode_base64 ($base64_buf);

      next if (length ($base64_buf_decoded) != (16 + 32));

      $salt = substr ($base64_buf_decoded, 0, 16);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 15100)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 5;

      shift @data;

      my $signature = shift @data;

      next unless ($signature eq 'sha1');

      $iter  = shift @data;
      $salt  = shift @data;
      $param = shift @data;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 15200)
    {
      my $index1 = index ($line, ':');

      next if ($index1 < 0);

      $hash_in = substr ($line, 0, $index1);
      $word = substr ($line, $index1 + 1);

      my (undef, $signature, $version, $iter_count, $data_len, $data_buf) = split '\$', $hash_in;

      next unless ($signature eq "blockchain");

      next unless ($version eq "v2");

      next unless (($data_len * 2) == length $data_buf);

      $iter  = $iter_count;
      $salt  = substr ($data_buf, 0, 32);
      $param = substr ($data_buf, 32);

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 15300 || $mode == 15900)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @tmp_data  = split ('\$', $hash_in);

      my $signature = $tmp_data[1];

      next unless ($signature eq 'DPAPImk');

      my @data = split ('\*', $tmp_data[2]);

      next unless (scalar @data == 9);

      my $version = shift @data;

      next unless ($version == 1 || $version == 2);

      my $context          = shift @data;

      my $SID              = shift @data;

      my $cipher_algorithm = shift @data;

      my $hash_algorithm   = shift @data;

      my $iteration        = shift @data;

      my $iv               = shift @data;

      my $cipher_len       = shift @data;

      my $cipher           = shift @data;

      next unless (length ($cipher) == $cipher_len);

      if ($version == 1)
      {
        next unless ($cipher_len == 208);
      }
      elsif ($version == 2)
      {
        next unless ($cipher_len == 288);
      }

      $salt   = substr ($hash_in, length ('$DPAPImk$'));

      $param = $cipher;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # chacha
    elsif ($mode == 15400)
    {
      my $index1 = index ($line, ':');

      next if ($index1 < 0);

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next if (length ($hash_in) < 11);

      next unless (substr ($hash_in, 0, 11) eq "\$chacha20\$\*");

      my @data = split ('\*', $hash_in);

      next unless (scalar (@data) == 6);

      $param  = $data[1]; # counter
      $param2 = $data[2]; # offset
      $param3 = $data[3]; # iv

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # jksprivk
    elsif ($mode == 15500)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\*', $hash_in);

      next unless scalar @data == 7;

      my $signature = shift @data;

      next unless ($signature eq '$jksprivk$');

      my $checksum  = shift @data;
      my $iv        = shift @data;
      my $enc_key   = shift @data;
      my $DER1      = shift @data;
      my $DER2      = shift @data;
      my $alias     = shift @data;

      $param  = $iv;
      $param2 = $enc_key;
      $param3 = $alias;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Ethereum - PBKDF2
    elsif ($mode == 15600)
    {
      my $index1 = index ($line, ':');

      next if ($index1 < 0);

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next if (length ($hash_in) < 12);

      next unless (substr ($hash_in, 0, 12) eq "\$ethereum\$p\*");

      my @data = split ('\*', $hash_in);

      next unless (scalar (@data) == 5);

      $iter = $data[1];

      $salt = pack ("H*", $data[2]);

      $param = pack ("H*", $data[3]); # ciphertext

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Ethereum - Scrypt
    elsif ($mode == 15700)
    {
      my $index1 = index ($line, ':');

      next if ($index1 < 0);

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next if (length ($hash_in) < 12);

      next unless (substr ($hash_in, 0, 12) eq "\$ethereum\$s\*");

      my @data = split ('\*', $hash_in);

      next unless (scalar (@data) == 7);

      $param  = $data[1];              # scrypt_N
      $param2 = $data[2];              # scrypt_r
      $param3 = $data[3];              # scrypt_p

      $salt   = pack ("H*", $data[4]);

      $param4 = pack ("H*", $data[5]); # ciphertext

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # tacacs-plus
    elsif ($mode == 16100)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 6;

      shift @data;

      my $signature = shift @data;

      next unless ($signature eq "tacacs-plus");

      my $auth_version = shift @data;

      next unless ($auth_version eq "0");

      my $session_id      = shift @data;
      my $encrypted_data  = shift @data;
      my $sequence        = shift @data;

      $param  = $session_id;
      $param2 = $encrypted_data;
      $param3 = $sequence;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # apple secure notes
    elsif ($mode == 16200)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\*', $hash_in);

      next unless scalar @data == 5;

      my $signature = shift @data;

      next unless ($signature eq '$ASN$');

      my ($Z_PK, $ZCRYPTOITERATIONCOUNT, $ZCRYPTOSALT, $ZCRYPTOWRAPPEDKEY) = @data;

      $salt = $ZCRYPTOSALT;
      $iter = $ZCRYPTOITERATIONCOUNT;

      $param  = $Z_PK;
      $param2 = $ZCRYPTOWRAPPEDKEY;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Ethereum Pre-Sale - PBKDF2
    elsif ($mode == 16300)
    {
      my $index1 = index ($line, ':');

      next if ($index1 < 0);

      $hash_in = substr ($line, 0, $index1);
      $word    = substr ($line, $index1 + 1);

      next if (length ($hash_in) < 12);

      next unless (substr ($hash_in, 0, 12) eq "\$ethereum\$w\*");

      my @data = split ('\*', $hash_in);

      next unless (scalar (@data) == 4);

      $param  = pack ("H*", $data[1]); # encseed

      $salt = $data[2];                # ethaddr

      $param2 = pack ("H*", $data[3]); # bpk (the iv + keccak digest)

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # JWT
    elsif ($mode == 16500)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split (/\./, $hash_in);

      next unless scalar @data == 3;

      my ($header, $payload, $signature) = @data;

      $salt = $header . "." . $payload;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Electrum Wallet (Salt-Type 1-3)
    elsif ($mode == 16600)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split (/\*/, $hash_in);

      next unless scalar @data == 3;

      my ($mode, $iv, $encrypted) = @data;

      my (undef, $signature, $salt_type) = split ('\$', $mode);

      next unless ($signature eq "electrum");

      $param  = $salt_type;
      $param2 = $iv;
      $param3 = $encrypted;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # FileVault 2
    elsif ($mode == 16700)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 7;

      shift @data;

      my $signature = shift @data;

      next unless ($signature eq 'fvde');

      my $Z_PK = shift @data;

      next unless ($Z_PK eq '1');

      my $salt_length = shift @data;

      next unless ($salt_length eq '16');

      my ($ZCRYPTOSALT, $ZCRYPTOITERATIONCOUNT, $ZCRYPTOWRAPPEDKEY) = @data;

      $salt = $ZCRYPTOSALT;
      $iter = $ZCRYPTOITERATIONCOUNT;

      $param  = $Z_PK;
      $param2 = $ZCRYPTOWRAPPEDKEY;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # WPA-PMKID-PMKDF2
    elsif ($mode == 16800)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split (/\*/, $hash_in);

      next unless scalar @data == 4;

      my ($pmkid, $macap, $macsta, $essid) = @data;

      $param  = $macap;
      $param2 = $macsta;
      $param3 = $essid;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # Ansible Vault
    elsif ($mode == 16900)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\*', $hash_in);

      next unless scalar @data == 5;

      my ($signature_tmp, $cipher, $salt, $ciphertext, $hmac) = @data;

      my ($signature, undef) = split ('\$', $signature_tmp);

      next unless ($signature eq "ansible");

      $param = $ciphertext;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    elsif ($mode == 18200)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 8;

      shift @data;

      my $signature            = shift @data;
      my $algorithm            = shift @data;
      my $user_principal_name  = shift @data;
      my $checksum             = shift @data;
      my $edata2               = shift @data;

      next unless ($signature eq "krb5asrep");
      next unless (length ($checksum) == 32);
      next unless (length ($edata2) >= 64);

      $salt = $user_principal_name;

      $param  = $checksum;
      $param2 = $edata2;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    # FileVault 2
    elsif ($mode == 18300)
    {
      ($hash_in, $word) = split ":", $line;

      next unless defined $hash_in;
      next unless defined $word;

      my @data = split ('\$', $hash_in);

      next unless scalar @data == 7;

      shift @data;

      my $signature = shift @data;

      next unless ($signature eq 'fvde');

      my $Z_PK = shift @data;

      next unless ($Z_PK eq '2');

      my $salt_length = shift @data;

      next unless ($salt_length eq '16');

      my ($ZCRYPTOSALT, $ZCRYPTOITERATIONCOUNT, $ZCRYPTOWRAPPEDKEY) = @data;

      $salt = $ZCRYPTOSALT;
      $iter = $ZCRYPTOITERATIONCOUNT;

      $param  = $Z_PK;
      $param2 = $ZCRYPTOWRAPPEDKEY;

      next unless (exists ($db->{$hash_in}) and (! defined ($db->{$hash_in})));
    }
    ## STEP 2c: Add your custom salt branch here
    else
    {
      print "ERROR: hash mode is not supported\n";

      exit (1);
    }

    if ($word =~ m/^\$HEX\[[0-9a-fA-F]*\]$/)
    {
      $word = pack ("H*", substr ($word, 5, -1));
    }

    # finally generate the hash

    # special case:
    if ($mode == 6800)
    {
      # check both variations
      $hash_out = gen_hash ($mode, $word, $salt, $iter, 1);

      $len = length $hash_out; # == length $alternative

      if (substr ($line, 0, $len) ne $hash_out)
      {
        my $alternative = gen_hash ($mode, $word, $salt, $iter, 2);

        return unless (substr ($line, 0, $len) eq $alternative);
      }
    }
    elsif ($mode == 8700)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 8900)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 9100)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 3300)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 5100)
    {
      # check 3 variants (start, middle, end)

      my $idx = 0;

      $hash_out = gen_hash ($mode, $word, $salt, $iter, $idx++);

      $len = length $hash_out; # == length $alternative

      if (substr ($line, 0, $len) ne $hash_out)
      {
        my $alternative = gen_hash ($mode, $word, $salt, $iter, $idx++);

        if (substr ($line, 0, $len) ne $alternative)
        {
          my $alternative = gen_hash ($mode, $word, $salt, $iter, $idx++);

          return unless (substr ($line, 0, $len) eq $alternative);
        }
      }
    }
    elsif ($mode == 9400)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 50000, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 9500)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 100000, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 9600)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 100000, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 9700)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 9800)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 10200)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 10400)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 10500)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2, $param3, $param4, $param5, $param6);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 10600)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 10700)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 10900)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 11100)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 11400)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2, $param3, $param4, $param5, $param6, $param7, $param8, $param9, $param10, $param11);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 11600)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2, $param3, $param4, $param5, $param6);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 11900)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 12000)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 12100)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 12200)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 12700)
    {
      # this is very special, we can't call gen_hash () because the param part is not always the same
      # we only know that it should contain the letters "guid" at the beginning of the decryted string

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

      my $salt_bin = pack ("H*", $salt);

      my $key = $pbkdf2->PBKDF2 ($salt_bin, $word);

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

      my $param_bin = pack ("H*", $param);

      my $decrypted = $cipher->decrypt ($param_bin);

      my $decrypted_part = substr ($decrypted, 1, 16);

      return unless ($decrypted_part =~ /"guid"/);

      $hash_out = $hash_in;
    }
    elsif ($mode == 12900)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 13000)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 13100)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 13200)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 13300)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 13400)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 13600)
    {
      $hash_out = gen_hash ($mode, $word, undef, undef, $param, $param2, $param3, $param4, $param5, $param6);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 14700)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 14800)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 15100)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 15200)
    {
      # this is very special, we can't call gen_hash () because the param part is not always the same
      # we only know that it should contain the letters "guid" at the beginning of the decryted string

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

      my $salt_bin = pack ("H*", $salt);

      my $key = $pbkdf2->PBKDF2 ($salt_bin, $word);

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

      my $param_bin = pack ("H*", $param);

      my $decrypted = $cipher->decrypt ($param_bin);

      my $decrypted_part = substr ($decrypted, 1, 16);

      return unless ($decrypted_part =~ /"guid"/);

      $hash_out = $hash_in;
    }
    elsif ($mode == 15300 || $mode == 15900)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 15400)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 15500)
    {
      $hash_out = gen_hash ($mode, $word, undef, undef, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 15600)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 15700)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2, $param3, $param4);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16100)
    {
      $hash_out = gen_hash ($mode, $word, undef, 0, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16200)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16300)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16600)
    {
      $hash_out = gen_hash ($mode, $word, undef, 0, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16700)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16800)
    {
      $hash_out = gen_hash ($mode, $word, undef, 0, $param, $param2, $param3);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 16900)
    {
      $hash_out = gen_hash ($mode, $word, $salt, 0, $param);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 18200)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    elsif ($mode == 18300)
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter, $param, $param2);

      $len = length $hash_out;

      return unless (substr ($line, 0, $len) eq $hash_out);
    }
    else
    {
      $hash_out = gen_hash ($mode, $word, $salt, $iter);

      $len = length $hash_out;

      # special cases:
      if ($mode == 400)
      {
        # allow $P$ and $H$ for -m 400
        next unless (substr ($line, 3, $len - 3) eq substr ($hash_out, 3));
      }
      elsif ($mode == 5600)
      {
        # hashcat outputs the user name always upper-case, we need
        next unless (substr ($line, 0, $len) eq $hash_out);

        my $found = 0;

        my $hash_out_lower = lc ($hash_out);

        for my $key (keys %{$db})
        {
          if (lc ($key) eq $hash_out_lower)
          {
            $found = 1;

            last;
          }
        }

        next unless $found;
      }
      else
      {
        next unless (substr ($line, 0, $len) eq $hash_out);
      }
    }

    # do not forget "exists ($db->$hash_out)" should be done above!
    $db->{$hash_out} = $word;
    print OUT $line . "\n";
  }

  close (IN);
  close (OUT);
}

sub passthrough
{
  my $mode = shift || 0;

  while (my $word_buf = <>)
  {
    chomp ($word_buf);

    next if length ($word_buf) > 256;

    ##
    ## gen salt
    ##

    my @salt_arr;

    for (my $i = 0; $i < 256; $i++)
    {
      my $c = get_random_chr (0x30, 0x39);

      push (@salt_arr, $c);
    }

    my $salt_buf = join ("", @salt_arr);

    ##
    ## gen hash
    ##

    my $tmp_hash;

    # unsalted
    if (is_in_array ($mode, $COMMON_UNSALTED_MODES)
     || $mode == 2400 || $mode == 13300)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, "");
    }
    elsif (is_in_array ($mode, $COMMON_DEFAULT_SALTED_MODES)
        || $mode ==  1411 || $mode ==  1711 || $mode ==  3711 || $mode ==  3800
        || $mode ==  4900 || $mode ==  8900 || $mode == 10000 || $mode == 10200
        || $mode == 10900 || $mode == 11900 || $mode == 12000 || $mode == 12100)
    {
      my $salt_len = get_random_num (1, 15);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 11 || $mode == 12)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 21)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 2));
    }
    elsif ($mode == 22)
    {
      my $salt_len = get_random_num (1, 11);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode ==  111 || $mode ==   122 || $mode ==  131 || $mode ==  132
        || $mode ==  400 || $mode ==   500 || $mode == 1600 || $mode == 1722
        || $mode == 1731 || $mode ==  1800 || $mode == 6300 || $mode == 7900
        || $mode == 8100 || $mode == 11100)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 8));
    }
    elsif ($mode == 112)
    {
      next if length ($word_buf) > 30;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 20));
    }
    elsif ($mode == 121)
    {
      my $salt_len = get_random_num (1, 9);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 125)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 8));
    }
    elsif ($mode == 141 || $mode == 1441)
    {
      my $salt_len = get_random_num (1, 15);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 1100)
    {
      my $salt_len = get_random_num (1, 19);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 1500)
    {
      next if length ($word_buf) > 8;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 2));
    }
    elsif ($mode == 2100)
    {
      next if length ($word_buf) > 13;

      my $salt_len = get_random_num (1, 19);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 2410)
    {
      my $salt_len = get_random_num (1, 4);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 2500)
    {
      next if length ($word_buf) < 8;

      my $salt_len = get_random_num (0, 32);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 2611)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 3));
    }
    elsif ($mode == 2612)
    {
      my $salt_len = get_random_num (1, 22);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 2711)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 30));
    }
    elsif ($mode == 2811)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 5));
    }
    elsif ($mode == 3000)
    {
      next if length ($word_buf) > 7;

      $tmp_hash = gen_hash ($mode, $word_buf, "");
    }
    elsif ($mode == 3100)
    {
      next if length ($word_buf) > 30;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 10));
    }
    elsif ($mode == 3200 || $mode == 5800 || $mode == 6400 || $mode == 6500 || $mode == 6700 || $mode == 7400 || $mode == 3300 || $mode == 8000 || $mode == 9100 || $mode == 12001 || $mode == 12200 || $mode == 15600)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 16));
    }
    elsif ($mode == 3800 || $mode == 4900)
    {
      my $salt_len = get_random_num (1, 11);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 4520)
    {
      my $salt_len = get_random_num (1, 50);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 4521 || $mode == 15700)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 4522)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 12));
    }
    elsif ($mode == 4800)
    {
      $salt_buf = get_random_md5chap_salt (substr ($salt_buf, 0, 16));

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 5300 || $mode == 5400)
    {
      $salt_buf = get_random_ike_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 5500)
    {
      my $user_len   = get_random_num (0, 15);
      my $domain_len = get_random_num (0, 15);

      $salt_buf = get_random_netntlmv1_salt ($user_len, $domain_len);

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 5600)
    {
      my $user_len   = get_random_num (0, 15);
      my $domain_len = get_random_num (0, 15);

      $salt_buf = get_random_netntlmv2_salt ($user_len, $domain_len);

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 6600)
    {
      $salt_buf = get_random_agilekeychain_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 6800)
    {
      my $email_len = get_random_num (1, 15);

      my $email = "";

      for (my $i = 0; $i < $email_len; $i++)
      {
        $email .= get_random_chr (0x61, 0x7a);
      }

      $email .= '@trash-mail.com';

      $tmp_hash = gen_hash ($mode, $word_buf, $email);
    }
    elsif ($mode == 7000)
    {
      next if length ($word_buf) > 19;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 24));
    }
    elsif ($mode == 7100)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 64));
    }
    elsif ($mode == 7200)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 128));
    }
    elsif ($mode == 7300)
    {
      my $salt_len = get_random_num (32, 256);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 7500)
    {
      $salt_buf = get_random_kerberos5_salt (substr ($salt_buf, 0, 16));

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 7700 || $mode == 7701)
    {
      next if length ($word_buf) > 8;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 12));
    }
    elsif ($mode == 7800 || $mode == 7801)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 12));
    }
    elsif ($mode == 8200)
    {
      $salt_buf = get_random_cloudkeychain_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 8300)
    {
      $salt_buf = get_random_dnssec_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 8400 || $mode == 11200 || $mode == 16300)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 40));
    }
    elsif ($mode == 8500)
    {
      next if length ($word_buf) > 8;

      my $salt_len = get_random_num (1, 9);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 8600)
    {
      next if length ($word_buf) > 16;

      $tmp_hash = gen_hash ($mode, $word_buf, "");
    }
    elsif ($mode == 8700)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 5));
    }
    elsif ($mode == 9200 || $mode == 9300)
    {
      my $salt_len = 14;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 9400 || $mode == 9500 || $mode == 9600)
    {
      next if length ($word_buf) > 19;

      my $salt_len = 32;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 9700 || $mode == 9800)
    {
      next if length ($word_buf) > 15;

      my $salt_len = 32;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 10100)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 10300)
    {
      my $salt_len = get_random_num (4, 15);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 10400)
    {
      next if length ($word_buf) > 31;

      my $salt_len = 32;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 10500)
    {
      next if length ($word_buf) > 15;

      my $salt_len = 32;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 10600)
    {
      next if length ($word_buf) > 31;

      my $salt_len = 32;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 10700)
    {
      next if length ($word_buf) > 15;

      my $salt_len = 32;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 11000)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 56));
    }
    elsif ($mode == 11300)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 16));
    }
    elsif ($mode == 11400)
    {
      next if length ($word_buf) > 24;

      my $salt_len = get_random_num (1, 15);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 11600)
    {
      my $salt_len = get_random_num (0, 16);

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, $salt_len));
    }
    elsif ($mode == 12300)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 12400)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 4));
    }
    elsif ($mode == 12600 || $mode == 15000)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 64));
    }
    elsif ($mode == 12700)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 12800)
    {
      next if length ($word_buf) > 24;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 20));
    }
    elsif ($mode == 12900)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 13000)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 13100)
    {
      $salt_buf = get_random_kerberos5_tgs_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 13200)
    {
      $salt_buf = get_random_axcrypt_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 13400)
    {
      $salt_buf = get_random_keepass_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 13500)
    {
      $salt_buf = get_pstoken_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 13600)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 13800)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 256));
    }
    elsif ($mode == 13900)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 9));
    }
    elsif ($mode == 14000)
    {
      next if length ($word_buf) != 8;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 16));
    }
    elsif ($mode == 14100)
    {
      next if length ($word_buf) != 24;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 16));
    }
    elsif ($mode == 14400)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 20));
    }
    elsif (($mode == 14700) || ($mode == 14800))
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 40));
    }
    elsif ($mode == 14900)
    {
      next if length ($word_buf) != 10;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 8));
    }
    elsif ($mode == 15100)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 8));
    }
    elsif ($mode == 15200)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 15300 || $mode == 15900)
    {
      my $version = 2;

      if ($mode == 15300)
      {
        $version = 1;
      }

      $salt_buf = get_random_dpapimk_salt ($version);

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 15400)
    {
      next if length ($word_buf) != 32;

      $tmp_hash = gen_hash ($mode, $word_buf, "");
    }
    elsif ($mode == 15500)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 40));
    }
    elsif ($mode == 16000)
    {
      next if length ($word_buf) > 8;

      $tmp_hash = gen_hash ($mode, $word_buf, "");
    }
    elsif ($mode == 16100)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, undef);
    }
    elsif ($mode == 16200)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 16500)
    {
      $salt_buf = get_random_jwt_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 16600)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 16700)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 16800)
    {
      next if length ($word_buf) < 8;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    elsif ($mode == 16900)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 64));
    }
    elsif ($mode == 18200)
    {
      $salt_buf = get_random_kerberos5_as_rep_salt ();

      $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);
    }
    elsif ($mode == 18300)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 32));
    }
    ## STEP 2c: Add your custom salt branch here
    else
    {
      print "ERROR: Unsupported hash type\n";

      exit (1);
    }

    print $tmp_hash, "\n";
  }
}

sub single
{
  my $mode = shift;

  if (defined $mode)
  {
    @{$MODES} = ($mode);
  }

  for (my $j = 0; $j < scalar @{$MODES}; $j++)
  {
    my $mode = $MODES->[$j];

    if (is_in_array ($mode, $COMMON_UNSALTED_MODES)
     || $mode == 5300 || $mode == 5400 || $mode ==  6600
     || $mode == 8200 || $mode == 8300 || $mode == 13300)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif (is_in_array ($mode, $COMMON_DEFAULT_SALTED_MODES)
        || $mode ==   121 || $mode ==  1411 || $mode ==  1711 || $mode ==  3711
        || $mode ==  8900 || $mode == 10000 || $mode == 10200 || $mode == 10900
        || $mode == 11900 || $mode == 12000 || $mode == 12100 || $mode == 16500)
    {
      my $salt_len = get_random_num (1, 15);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 11 || $mode == 12)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 21 || $mode == 22)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 2);
        }
        else
        {
          rnd ($mode, $i, 2);
        }
      }
    }
    elsif ($mode ==  111 || $mode ==   122 || $mode ==  125 || $mode ==  131
        || $mode ==  132 || $mode ==   400 || $mode ==  500 || $mode == 1600
        || $mode == 1722 || $mode ==  1731 || $mode == 6300 || $mode == 7900
        || $mode == 8100 || $mode == 11100)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 8);
        }
        else
        {
          rnd ($mode, $i, 8);
        }
      }
    }
    elsif ($mode == 112)
    {
      for (my $i = 1; $i < 31; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 20);
        }
        else
        {
          rnd ($mode, $i, 20);
        }
      }
    }
    elsif ($mode ==   141 || $mode ==  3300 || $mode ==  1441 || $mode == 1800
        || $mode ==  3200 || $mode ==  4800 || $mode ==  6400 || $mode == 6500
        || $mode ==  6700 || $mode ==  7400 || $mode ==  8000 || $mode == 9100
        || $mode == 12001 || $mode == 12200 || $mode == 15600)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 1100)
    {
      my $salt_len = get_random_num (1, 19);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 1500)
    {
      for (my $i = 1; $i < 9; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 2);
        }
        else
        {
          rnd ($mode, $i, 2);
        }
      }
    }
    elsif ($mode == 2100)
    {
      my $salt_len = get_random_num (1, 19);

      for (my $i = 1; $i < 13; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 2400)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif ($mode == 2410)
    {
      my $salt_len = get_random_num (3, 4);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 2500)
    {
      my $salt_len = get_random_num (0, 32);

      for (my $i = 8; $i < 16; $i++)
      {
        if ($len != 0)
        {
          if ($len < 8)
          {
            $len += 7;
          }

          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 2611)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 3);
        }
        else
        {
          rnd ($mode, $i, 3);
        }
      }
    }
    elsif ($mode == 2612)
    {
      my $salt_len = get_random_num (1, 22);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 2711)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 30);
        }
        else
        {
          rnd ($mode, $i, 30);
        }
      }
    }
    elsif ($mode == 2811)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 5);
        }
        else
        {
          rnd ($mode, $i, 5);
        }
      }
    }
    elsif ($mode == 3000)
    {
      for (my $i = 1; $i < 8; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif ($mode == 3100)
    {
      for (my $i = 1; $i < 31; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 10);
        }
        else
        {
          rnd ($mode, $i, 10);
        }
      }
    }
    elsif ($mode == 3800 || $mode == 4900)
    {
      my $salt_len = get_random_num (1, 11);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 4520)
    {
      my $salt_len = get_random_num (1, 50);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 4521 || $mode == 15700)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 4522)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 12);
        }
        else
        {
          rnd ($mode, $i, 12);
        }
      }
    }
    elsif ($mode == 5500 || $mode == 5600)
    {
      my $salt_len;

      for (my $i = 1; $i < 27; $i++)
      {
        $salt_len = get_random_num (1, 15);

        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 5800)
    {
      for (my $i = 1; $i < 14; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 6800)
    {
      my $salt_len = get_random_num (8, 25);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 7000)
    {
      for (my $i = 1; $i < 19; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 24);
        }
        else
        {
          rnd ($mode, $i, 24);
        }
      }
    }
    elsif ($mode == 7100)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 64);
        }
        else
        {
          rnd ($mode, $i, 64);
        }
      }
    }
    elsif ($mode == 7200)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 128);
        }
        else
        {
          rnd ($mode, $i, 128);
        }
      }
    }
    elsif ($mode == 7300)
    {
      my $salt_len = get_random_num (32, 255);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 7500)
    {
      for (my $i = 1; $i < 27; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 7700 || $mode == 7701)
    {
      my $salt_len = get_random_num (1, 12);

      for (my $i = 1; $i < 9; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 7800 || $mode == 7801)
    {
      my $salt_len = get_random_num (1, 12);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 8400 || $mode == 11200 || $mode == 14700 || $mode == 14800 || $mode == 16300)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 40);
        }
        else
        {
          rnd ($mode, $i, 40);
        }
      }
    }
    elsif ($mode == 8500)
    {
      my $salt_len = get_random_num (1, 8);

      for (my $i = 1; $i < 9; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 8600)
    {
      for (my $i = 1; $i < 17; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif ($mode == 8700)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 5);
        }
        else
        {
          rnd ($mode, $i, 5);
        }
      }
    }
    elsif ($mode == 9200 || $mode == 9300)
    {
      my $salt_len = 14;

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 9400 || $mode == 9500 || $mode == 9600)
    {
      my $salt_len = 32;

      for (my $i = 1; $i < 20; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 9700 || $mode == 9800)
    {
      my $salt_len = 32;

      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 10100)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 10300)
    {
      my $salt_len = get_random_num (4, 15);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 10400 || $mode == 10600)
    {
      my $salt_len = 32;

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 10500 || $mode == 10700)
    {
      my $salt_len = 32;

      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 11000)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 56);
        }
        else
        {
          rnd ($mode, $i, 56);
        }
      }
    }
    elsif ($mode == 11300)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 11400)
    {
      for (my $i = 1; $i < 24; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 11600)
    {
      my $salt_len = get_random_num (0, 16);

      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 12300)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 12400)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 4);
        }
        else
        {
          rnd ($mode, $i, 4);
        }
      }
    }
    elsif ($mode == 12600 || $mode == 15000)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 64);
        }
        else
        {
          rnd ($mode, $i, 64);
        }
      }
    }
    elsif ($mode == 12700)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 12800)
    {
      for (my $i = 1; $i < 25; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 20);
        }
        else
        {
          rnd ($mode, $i, 20);
        }
      }
    }
    elsif ($mode == 12900)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 13000)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 13100)
    {
      for (my $i = 1; $i < 27; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 13200)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 13400)
    {
      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 13500)
    {
      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 13600)
    {
      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 13800)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 256);
        }
        else
        {
          rnd ($mode, $i, 256);
        }
      }
    }
    elsif ($mode == 13900)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 9);
        }
        else
        {
          rnd ($mode, $i, 9);
        }
      }
    }
    elsif ($mode == 14000)
    {
      rnd ($mode,  8, 16);
    }
    elsif ($mode == 14100)
    {
      rnd ($mode, 24, 16);
    }
    elsif ($mode == 14400)
    {
      for (my $i = 1; $i < 24; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 20);
        }
        else
        {
          rnd ($mode, $i, 20);
        }
      }
    }
    elsif ($mode == 14900)
    {
      rnd ($mode, 10, 8);
    }
    elsif ($mode == 15100)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 8);
        }
        else
        {
          rnd ($mode, $i, 8);
        }
      }
    }
    elsif ($mode == 15200)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 15300 || $mode == 15900)
    {
      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 15400)
    {
      rnd ($mode, 32, 0);
    }
    elsif ($mode == 15500)
    {
      for (my $i = 1; $i < 16; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 40);
        }
        else
        {
          rnd ($mode, $i, 40);
        }
      }
    }
    elsif ($mode == 16000)
    {
      for (my $i = 1; $i < 9; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif ($mode == 16100)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif ($mode == 16200)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 16600)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 16700)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    elsif ($mode == 16800)
    {
      my $salt_len = get_random_num (0, 32);

      for (my $i = 8; $i < 16; $i++)
      {
        if ($len != 0)
        {
          if ($len < 8)
          {
            $len += 7;
          }

          rnd ($mode, $len, $salt_len);
        }
        else
        {
          rnd ($mode, $i, $salt_len);
        }
      }
    }
    elsif ($mode == 16900)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 64);
        }
        else
        {
          rnd ($mode, $i, 64);
        }
      }
    }
    elsif ($mode == 18200)
    {
      for (my $i = 1; $i < 27; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    elsif ($mode == 18300)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 32);
        }
        else
        {
          rnd ($mode, $i, 32);
        }
      }
    }
    ## STEP 2c: Add your custom salt branch here
  }
}

exit;

## STEP 3: Implement hash generation for your hash mode here.
#
# For an example of how to use python, see mode 11700.
# For an example of how to use PHP, see mode 11900.
#
# Don't forget to add the modules you depend on to the
# installation script.
#
##
sub gen_hash
{
  my $mode = shift;

  my $word_buf = shift;

  my $salt_buf = shift;

  my $iter = shift;

  my $additional_param = shift;

  my $additional_param2 = shift;

  my $additional_param3 = shift;

  my $additional_param4 = shift;

  my $additional_param5 = shift;

  my $additional_param6 = shift;

  my $additional_param7 = shift;

  my $additional_param8 = shift;

  my $additional_param9 = shift;

  my $additional_param10 = shift;

  my $additional_param11 = shift;

  ##
  ## gen hash
  ##

  my $tmp_hash;

  my $hash_buf;

  if ($mode == 0)
  {
    $hash_buf = md5_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 10)
  {
    $hash_buf = md5_hex ($word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 11)
  {
    $hash_buf = md5_hex ($word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 12)
  {
    $hash_buf = md5_hex ($word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 20)
  {
    $hash_buf = md5_hex ($salt_buf . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 21)
  {
    $hash_buf = md5_hex ($salt_buf . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 22)
  {
    my $itoa64 = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789+/";
    my $salt_suffix = "Administration Tools";

    my $pass = sprintf ("%s:%s:%s", $salt_buf, $salt_suffix, $word_buf);

    $hash_buf = md5 ($pass);

    my $res = "";

    for (my $pos = 0; $pos < 16; $pos += 2)
    {
      my $octet1 = ord (substr ($hash_buf, $pos + 0, 1));
      my $octet2 = ord (substr ($hash_buf, $pos + 1, 1));

      my $num = ($octet1 <<8 & 0xff00) | ($octet2 & 0xff);

      my $idx1 = $num >> 12 & 0x0f;
      my $idx2 = $num >>  6 & 0x3f;
      my $idx3 = $num       & 0x3f;

      $res = $res . substr ($itoa64, $idx1, 1) . substr ($itoa64, $idx2, 1) . substr ($itoa64, $idx3, 1);
    }

    my $obfuscate_str = "nrcstn";
    my @obfuscate_pos = (0, 6, 12, 17, 23, 29);

    foreach my $pos (keys @obfuscate_pos)
    {
      my $idx = $obfuscate_pos[$pos];
      my $before = substr ($res, 0, $idx);
      my $char   = substr ($obfuscate_str, $pos, 1);
      my $after  = substr ($res, $idx);

      $res = sprintf ("%s%s%s", $before, $char, $after);
    }

    $tmp_hash = sprintf ("%s:%s", $res, $salt_buf);
  }
  elsif ($mode == 23)
  {
    $hash_buf = md5_hex ($salt_buf . "\nskyper\n" . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 30)
  {
    $hash_buf = md5_hex (encode ("UTF-16LE", $word_buf) . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 40)
  {
    $hash_buf = md5_hex ($salt_buf . encode ("UTF-16LE", $word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 50)
  {
    $hash_buf = hmac_hex ($salt_buf, $word_buf, \&md5, 64);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 60)
  {
    $hash_buf = hmac_hex ($word_buf, $salt_buf, \&md5, 64);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 100)
  {
    $hash_buf = sha1_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 101)
  {
    $hash_buf = sha1 ($word_buf);

    my $base64_buf = encode_base64 ($hash_buf, "");

    $tmp_hash = sprintf ("{SHA}%s", $base64_buf);
  }
  elsif ($mode == 110)
  {
    $hash_buf = sha1_hex ($word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 111)
  {
    $hash_buf = sha1 ($word_buf . $salt_buf);

    my $base64_buf = encode_base64 ($hash_buf . $salt_buf, "");

    $tmp_hash = sprintf ("{SSHA}%s", $base64_buf);
  }
  elsif ($mode == 112)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha1_hex ($word_buf . $salt_buf_bin);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 120)
  {
    $hash_buf = sha1_hex ($salt_buf . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 121)
  {
    $hash_buf = sha1_hex (lc ($salt_buf) . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 122)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha1_hex ($salt_buf_bin . $word_buf);

    $tmp_hash = sprintf ("%s%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 125)
  {
    my $signature = "01";

    my $salt_buf_bin = pack ("H*", $salt_buf . $signature);

    $hash_buf = sha1_hex ($salt_buf_bin . $word_buf);

    $tmp_hash = sprintf ("%s%s%s", $salt_buf, $signature, $hash_buf);
  }
  elsif ($mode == 130)
  {
    $hash_buf = sha1_hex (encode ("UTF-16LE", $word_buf) . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 131)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha1_hex (encode ("UTF-16LE", uc ($word_buf)) . $salt_buf_bin);

    $tmp_hash = sprintf ("0x0100%s%s%s", $salt_buf, "0" x 40, $hash_buf);
  }
  elsif ($mode == 132)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha1_hex (encode ("UTF-16LE", $word_buf) . $salt_buf_bin);

    $tmp_hash = sprintf ("0x0100%s%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 133)
  {
    $hash_buf = sha1 (encode ("UTF-16LE", $word_buf));

    $hash_buf = encode_base64 ($hash_buf, "");

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 140)
  {
    $hash_buf = sha1_hex ($salt_buf . encode ("UTF-16LE", $word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 141)
  {
    $hash_buf = sha1 ($salt_buf . encode ("UTF-16LE", $word_buf));

    my $base64_salt_buf = encode_base64 ($salt_buf, "");
    my $base64_hash_buf = encode_base64 ($hash_buf, "");

    $base64_hash_buf = substr ($base64_hash_buf, 0, 27);

    $tmp_hash = sprintf ("\$episerver\$*0*%s*%s", $base64_salt_buf, $base64_hash_buf);
  }
  elsif ($mode == 150)
  {
    $hash_buf = hmac_hex ($salt_buf, $word_buf, \&sha1, 64);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 160)
  {
    $hash_buf = hmac_hex ($word_buf, $salt_buf, \&sha1, 64);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 200)
  {
    my $ppr = Authen::Passphrase::MySQL323->new (passphrase => $word_buf);

    $hash_buf = $ppr->hash_hex;

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 300)
  {
    $hash_buf = substr (password41 ($word_buf), 1);

    $hash_buf = lc ($hash_buf); # useful for 'not matched' check only

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 400)
  {
    my $cost = 11;

    if (length ($iter))
    {
      $cost = $iter;
    }

    my $ppr = Authen::Passphrase::PHPass->new
    (
      cost => $cost,
      salt => $salt_buf,
      passphrase => $word_buf,
    );

    $hash_buf = $ppr->as_rfc2307;

    $tmp_hash = sprintf ("%s", substr ($hash_buf, 7));
  }
  elsif ($mode == 500)
  {
    my $iterations = 1000;

    if (defined ($iter))
    {
      if ($iter > 0)
      {
        $iterations = int ($iter);
      }
    }

    $hash_buf = md5_crypt ('$1$', $iterations, $word_buf, $salt_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 600)
  {
    $hash_buf = lc blake2b_hex ($word_buf);
    $tmp_hash = sprintf ("\$BLAKE2\$" . $hash_buf);
  }
  elsif ($mode == 900)
  {
    $hash_buf = md4_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1000)
  {
    $hash_buf = md4_hex (encode ("UTF-16LE", $word_buf));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1100)
  {
    $hash_buf = md4_hex (md4 (encode ("UTF-16LE", $word_buf)) . encode ("UTF-16LE", lc ($salt_buf)));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1300)
  {
    $hash_buf = sha224_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1400)
  {
    $hash_buf = sha256_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1410)
  {
    $hash_buf = sha256_hex ($word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1411)
  {
    $hash_buf = sha256_hex ($word_buf . $salt_buf);

    my $base64_buf = encode_base64 (pack ("H*", $hash_buf) . $salt_buf, "");

    $tmp_hash = sprintf ("{SSHA256}%s", $base64_buf);
  }
  elsif ($mode == 1420)
  {
    $hash_buf = sha256_hex ($salt_buf . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1430)
  {
    $hash_buf = sha256_hex (encode ("UTF-16LE", $word_buf) . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1440)
  {
    $hash_buf = sha256_hex ($salt_buf . encode ("UTF-16LE", $word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1441)
  {
    $hash_buf = sha256 ($salt_buf . encode ("UTF-16LE", $word_buf));

    my $base64_salt_buf = encode_base64 ($salt_buf, "");
    my $base64_hash_buf = encode_base64 ($hash_buf, "");

    $base64_hash_buf = substr ($base64_hash_buf, 0, 43);

    $tmp_hash = sprintf ("\$episerver\$*1*%s*%s", $base64_salt_buf, $base64_hash_buf);
  }
  elsif ($mode == 1450)
  {
    $hash_buf = hmac_hex ($salt_buf, $word_buf, \&sha256, 64);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1460)
  {
    $hash_buf = hmac_hex ($word_buf, $salt_buf, \&sha256, 64);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1500)
  {
    $hash_buf = crypt ($word_buf, $salt_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1600)
  {
    my $iterations = 1000;

    if (defined ($iter))
    {
      if ($iter > 0)
      {
        $iterations = int ($iter);
      }
    }

    $hash_buf = md5_crypt ('$apr1$', $iterations, $word_buf, $salt_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1700)
  {
    $hash_buf = sha512_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 1710 || $mode == 15000)
  {
    $hash_buf = sha512_hex ($word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1711)
  {
    $hash_buf = sha512_hex ($word_buf . $salt_buf);

    my $base64_buf = encode_base64 (pack ("H*", $hash_buf) . $salt_buf, "");

    $tmp_hash = sprintf ("{SSHA512}%s", $base64_buf);
  }
  elsif ($mode == 1720)
  {
    $hash_buf = sha512_hex ($salt_buf . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1730)
  {
    $hash_buf = sha512_hex (encode ("UTF-16LE", $word_buf) . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1740)
  {
    $hash_buf = sha512_hex ($salt_buf . encode ("UTF-16LE", $word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1722)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha512_hex ($salt_buf_bin . $word_buf);

    $tmp_hash = sprintf ("%s%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 1731)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha512_hex (encode ("UTF-16LE", $word_buf) . $salt_buf_bin);

    $tmp_hash = sprintf ("0x0200%s%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 1750)
  {
    $hash_buf = hmac_hex ($salt_buf, $word_buf, \&sha512, 128);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1760)
  {
    $hash_buf = hmac_hex ($word_buf, $salt_buf, \&sha512, 128);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 1800)
  {
    my $iterations = 5000;

    if (defined ($iter))
    {
      if ($iter > 0)
      {
        $iterations = int ($iter);
      }
    }

    $hash_buf = sha512_crypt ($iterations, $word_buf, $salt_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 2100)
  {
    my $iterations = 10240;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $salt = encode ("UTF-16LE", lc ($salt_buf));

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hash_class => 'HMACSHA1',
      iterations => $iterations,
      output_len => 16,
      salt_len   => length ($salt),
    );

    $hash_buf = unpack ("H*", $pbkdf2->PBKDF2 ($salt, md4 (md4 (encode ("UTF-16LE", $word_buf)) . $salt)));

    $tmp_hash = sprintf ("\$DCC2\$%i#%s#%s", $iterations, $salt_buf, $hash_buf);
  }
  elsif ($mode == 2400)
  {
    my $word_len = length ($word_buf);

    my $pad_len = ceil ($word_len / 16) * 16;

    my $hash_buf = Digest::MD5::md5 ($word_buf . "\0" x ($pad_len - $word_len));

    $tmp_hash = sprintf ("%s", pseudo_base64 ($hash_buf));
  }
  elsif ($mode == 2410)
  {
    my $word_salt_buf = $word_buf . $salt_buf;

    my $word_salt_len = length ($word_salt_buf);

    my $pad_len = ceil ($word_salt_len / 16) * 16;

    my $hash_buf = Digest::MD5::md5 ($word_buf . $salt_buf . "\0" x ($pad_len - $word_salt_len));

    $tmp_hash = sprintf ("%s:%s", pseudo_base64 ($hash_buf), $salt_buf);
  }
  elsif ($mode == 2500)
  {
    my ($bssid, $stmac, $snonce, $anonce, $eapol, $keyver, $eapol_len, $essid_len);

    if (! defined ($additional_param))
    {
      # random stuff

      $bssid  = randbytes (6);
      $stmac  = randbytes (6);
      $snonce = randbytes (32);
      $anonce = randbytes (32);

      $keyver = get_random_num (1, 4); # 1, 2 or 3

      # eapol:
      # should be "validly" generated, but in theory could be anything for us also:
      # $eapol = "\x00" x 121; # works too, but let's generate it correctly

      $eapol = gen_random_wpa_eapol ($keyver, $snonce);
    }
    else
    {
      $bssid  = $additional_param;
      $stmac  = $additional_param2;
      $snonce = $additional_param3;
      $anonce = $additional_param4;
      $keyver = $additional_param5;
      $eapol  = $additional_param6;
    }

    $eapol_len = length ($eapol);

    # constants

    my $iterations = 4096;

    #
    # START
    #

    # generate the Pairwise Master Key (PMK)

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

    my $pmk = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

    # Pairwise Transient Key (PTK) transformation

    my $ptk = wpa_prf_512 ($keyver, $pmk, $stmac, $bssid, $snonce, $anonce);

    # generate the Message Integrity Code (MIC)

    my $mic = "";

    if ($keyver == 1) # WPA1 => MD5
    {
      $mic = hmac ($eapol, $ptk, \&md5);
    }
    elsif ($keyver == 2) # WPA2 => SHA1
    {
      $mic = hmac ($eapol, $ptk, \&sha1);
    }
    elsif ($keyver == 3) # WPA2 => SHA256 + AES-CMAC
    {
      my $omac1 = Digest::CMAC->new ($ptk, 'Crypt::Rijndael');

      $omac1->add ($eapol);

      $mic = $omac1->digest;
    }

    $mic = substr ($mic, 0, 16);

    #
    # format the binary output
    #

    my $HCCAPX_VERSION = 4;

    # signature
    $hash_buf = "HCPX";

    # format version
    $hash_buf .= pack ("L<", $HCCAPX_VERSION);

    # authenticated
    $hash_buf .= pack ("C", 0);

    # essid length
    $essid_len = length ($salt_buf);
    $hash_buf .= pack ("C", $essid_len);

    # essid (NULL-padded up to the first 32 bytes)
    $hash_buf .= $salt_buf;
    $hash_buf .= "\x00" x (32 - $essid_len);

    # key version
    $hash_buf .= pack ("C", $keyver);

    # key mic
    $hash_buf .= $mic;

    # access point MAC
    $hash_buf .= $bssid;

    # access point nonce
    $hash_buf .= $snonce;

    # client MAC
    $hash_buf .= $stmac;

    # client nonce
    $hash_buf .= $anonce;

    # eapol length
    $hash_buf .= pack ("S<", $eapol_len);

    # eapol
    $hash_buf .= $eapol;
    $hash_buf .= "\x00" x (256 - $eapol_len);

    # base64 encode the output
    $tmp_hash = encode_base64 ($hash_buf, "");
  }
  elsif ($mode == 2600)
  {
    $hash_buf = md5_hex (md5_hex ($word_buf));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 2611)
  {
    $hash_buf = md5_hex (md5_hex ($word_buf) . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 2612)
  {
    my $salt_buf_hex = unpack ("H*", $salt_buf);

    $hash_buf = md5_hex (md5_hex ($word_buf) . $salt_buf);

    $tmp_hash = sprintf ("\$PHPS\$%s\$%s", $salt_buf_hex, $hash_buf);
  }
  elsif ($mode == 2711)
  {
    $hash_buf = md5_hex (md5_hex ($word_buf) . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 2811)
  {
    $hash_buf = md5_hex (md5_hex ($salt_buf) . md5_hex ($word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 3000)
  {
    my $ppr = Authen::Passphrase::LANManager->new ("passphrase" => $word_buf);

    $hash_buf = $ppr->hash_hex;

    $tmp_hash = sprintf ("%s", substr ($hash_buf, 0, 16));
  }
  elsif ($mode == 3100)
  {
    $hash_buf = oracle_hash ($salt_buf, $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 3200)
  {
    my $cost = "05";

    if (length ($iter))
    {
      $cost = $iter;
    }

    $tmp_hash = bcrypt ($word_buf, sprintf ('$2a$%s$%s$', $cost, en_base64 ($salt_buf)));
  }
  elsif ($mode == 3300)
  {
    my $iterations = 904;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $variant = "\$";

    if (defined ($additional_param))
    {
      $variant = $additional_param;
    }

    my $prefix = sprintf ("\$md5%srounds=%i\$%s", $variant, $iterations, $salt_buf);

    $iterations += 4096;

    $hash_buf = sun_md5 ($word_buf, $prefix, $iterations);

    $tmp_hash = sprintf ("%s\$%s", $prefix, $hash_buf);
  }
  elsif ($mode == 3500)
  {
    $hash_buf = md5_hex (md5_hex (md5_hex ($word_buf)));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 3610)
  {
    $hash_buf = md5_hex (md5_hex ($salt_buf) . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 3710)
  {
    $hash_buf = md5_hex ($salt_buf . md5_hex ($word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 3711)
  {
    $hash_buf = md5_hex ($salt_buf . "-" . md5_hex ($word_buf));

    $tmp_hash = sprintf ("\$B\$%s\$%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 3720)
  {
    $hash_buf = md5_hex ($word_buf . md5_hex ($salt_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 3800)
  {
    $hash_buf = md5_hex ($salt_buf . $word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 3910)
  {
    $hash_buf = md5_hex (md5_hex ($word_buf) . md5_hex ($salt_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 4010)
  {
    $hash_buf = md5_hex ($salt_buf . md5_hex ($salt_buf . $word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 4110)
  {
    $hash_buf = md5_hex ($salt_buf . md5_hex ($word_buf . $salt_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 4210)
  {
    $hash_buf = md5_hex ($salt_buf . "\x00" . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 4300)
  {
    $hash_buf = md5_hex (uc (md5_hex ($word_buf)));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 4400)
  {
    $hash_buf = md5_hex (sha1_hex ($word_buf));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 4500)
  {
    $hash_buf = sha1_hex (sha1_hex ($word_buf));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif (($mode == 4520) || ($mode == 4521) || ($mode == 4522))
  {
    $hash_buf = sha1_hex ($salt_buf . sha1_hex ($word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 4600)
  {
    $hash_buf = sha1_hex (sha1_hex (sha1_hex ($word_buf)));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 4700)
  {
    $hash_buf = sha1_hex (md5_hex ($word_buf));

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 4800)
  {
    my $index = rindex ($salt_buf, ":");

    my $salt  = substr ($salt_buf, 0, $index);
    my $salt_bin  = pack ("H*", $salt);
    my $chap_sign = substr ($salt_buf, $index + 1);
    my $chap_sign_bin = pack ("H*", $chap_sign);

    $hash_buf = md5_hex ($chap_sign_bin . $word_buf . $salt_bin);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 4900)
  {
    $hash_buf = sha1_hex ($salt_buf . $word_buf . $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 5100)
  {
    my $pos;

    if (! defined ($additional_param))
    {
      $pos = 0;
    }
    else
    {
      $pos = $additional_param * 8 unless ($additional_param > 2);
    }

    $hash_buf = md5_hex ($word_buf);

    $tmp_hash = sprintf ("%s", substr ($hash_buf, $pos, 16));
  }
  elsif ($mode == 5300)
  {
    my @salt_arr = split (":", $salt_buf);

    my $msg_buf = pack ("H*", $salt_arr[0] . $salt_arr[1] . $salt_arr[2] . $salt_arr[3] . $salt_arr[4] . $salt_arr[5]);
    my $nr_buf  = pack ("H*", $salt_arr[6] . $salt_arr[7]);

    my $hash_buf = hmac  ($nr_buf , $word_buf, \&md5, 64);
    $hash_buf = hmac_hex ($msg_buf, $hash_buf, \&md5, 64);

    $tmp_hash = sprintf ("%s:%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 5400)
  {
    my @salt_arr = split (":", $salt_buf);

    my $msg_buf = pack ("H*", $salt_arr[0] . $salt_arr[1] . $salt_arr[2] . $salt_arr[3] . $salt_arr[4] . $salt_arr[5]);
    my $nr_buf  = pack ("H*", $salt_arr[6] . $salt_arr[7]);

    my $hash_buf = hmac  ($nr_buf , $word_buf, \&sha1, 64);
    $hash_buf = hmac_hex ($msg_buf, $hash_buf, \&sha1, 64);

    $tmp_hash = sprintf ("%s:%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 5500)
  {
    my $index1 = index  ($salt_buf, "::");
    my $user   = substr ($salt_buf, 0, $index1);

    my $index2 = index  ($salt_buf, ":", $index1 + 2);
    my $domain = substr ($salt_buf, $index1 + 2, $index2 - $index1 - 2);

    my $len = length (substr ($salt_buf, $index2 + 1));

    my $c_challenge_hex;

    if ($len > 32)
    {
      $c_challenge_hex = substr ($salt_buf, $index2 +  1, 48);
      $index2 += 32;
    }
    else
    {
      $c_challenge_hex  = substr ($salt_buf, $index2 +  1, 16);
      $c_challenge_hex .= 00 x 32;
    }

    my $c_challenge     = pack   ("H*", substr ($c_challenge_hex, 0, 16));
    my $s_challenge_hex = substr ($salt_buf, $index2 + 17, 16);
    my $s_challenge     = pack   ("H*", $s_challenge_hex);

    my $challenge = substr (md5 ($s_challenge . $c_challenge), 0, 8);

    my $ntresp;

    my $nthash = Authen::Passphrase::NTHash->new (passphrase => $word_buf)->hash . "\x00" x 5;

    $ntresp .= Crypt::ECB::encrypt (setup_des_key (substr ($nthash,  0, 7)), "DES", $challenge, "none");
    $ntresp .= Crypt::ECB::encrypt (setup_des_key (substr ($nthash,  7, 7)), "DES", $challenge, "none");
    $ntresp .= Crypt::ECB::encrypt (setup_des_key (substr ($nthash, 14, 7)), "DES", $challenge, "none");

    $tmp_hash = sprintf ("%s::%s:%s:%s:%s", $user, $domain, $c_challenge_hex, unpack ("H*", $ntresp), $s_challenge_hex);
  }
  elsif ($mode == 5600)
  {
    my $index1 = index  ($salt_buf, "::");
    my $user   = substr ($salt_buf, 0, $index1);

    my $index2 = index  ($salt_buf, ":", $index1 + 2);
    my $domain = substr ($salt_buf, $index1 + 2, $index2 - $index1 - 2);

    my $s_challenge_hex = substr ($salt_buf, $index2 + 1, 16);
    my $s_challenge     = pack   ("H*", $s_challenge_hex);

    my $temp_hex = substr ($salt_buf, $index2 + 17);
    my $temp     = pack   ("H*", $temp_hex);

    my $nthash   = Authen::Passphrase::NTHash->new (passphrase => $word_buf)->hash;
    my $identity = Encode::encode ("UTF-16LE", uc ($user) . $domain);

    $hash_buf = hmac_hex ($s_challenge . $temp, hmac ($identity, $nthash, \&md5, 64), \&md5, 64);

    $tmp_hash = sprintf ("%s::%s:%s:%s:%s", $user, $domain, $s_challenge_hex, $hash_buf, $temp_hex);
  }
  elsif ($mode == 5700)
  {
    $hash_buf = sha256 ($word_buf);

    my $base64_buf = encode_base64 ($hash_buf, "");

    $tmp_hash = "";

    for (my $i = 0; $i < 43; $i++)
    {
      $tmp_hash .= $CISCO_BASE64_MAPPING->{substr ($base64_buf, $i, 1)};
    }
  }
  elsif ($mode == 5800)
  {
    $hash_buf = androidpin_hash ($word_buf, $salt_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 6000)
  {
    $hash_buf = ripemd160_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 6100)
  {
    $hash_buf = whirlpool_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 6300)
  {
    my $iterations = 1000; # hard coded by the AIX format

    $hash_buf = md5_crypt ('', $iterations, $word_buf, $salt_buf);

    $tmp_hash = sprintf ("{smd5}%s", $hash_buf);
  }
  elsif ($mode == 6400)
  {
    my $iterations = 64;

    if (length ($iter))
    {
      $iterations = 1 << int ($iter);
    }

    $hash_buf = aix_ssha256_pbkdf2 ($word_buf, $salt_buf, $iterations);

    $tmp_hash = sprintf ("{ssha256}%02i\$%s\$%s", log ($iterations) / log (2), $salt_buf, $hash_buf);
  }
  elsif ($mode == 6500)
  {
    my $iterations = 64;

    if (length ($iter))
    {
      $iterations = 1 << int ($iter);
    }

    $hash_buf = aix_ssha512_pbkdf2 ($word_buf, $salt_buf, $iterations);

    $tmp_hash = sprintf ("{ssha512}%02i\$%s\$%s", log ($iterations) / log (2), $salt_buf, $hash_buf);
  }
  elsif ($mode == 6600)
  {
    my $iterations = 1000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $salt_hex = substr ($salt_buf, 0, 16);
    my $salt     = pack   ("H*", $salt_hex);

    my $prefix   = substr ($salt_buf, 16, 2016);

    my $iv_hex   = substr ($salt_buf, 2032);
    my $iv       = pack ("H*", $iv_hex);

    my $data = pack ("H*", "10101010101010101010101010101010");

    my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1');

    my $pbkdf2 = Crypt::PBKDF2->new (
      hasher       => $hasher,
      iterations   => $iterations,
      output_len   => 16
    );

    my $key = $pbkdf2->PBKDF2 ($salt, $word_buf);

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

    my $encrypted = unpack ("H*", $cipher->encrypt ($data));

    $hash_buf  = substr ($encrypted, 0, 32);

    $tmp_hash = sprintf ("%i:%s:%s%s%s", $iterations, $salt_hex, $prefix, $iv_hex, $hash_buf);
  }
  elsif ($mode == 6700)
  {
    my $iterations = 64;

    if (length ($iter))
    {
      $iterations = 1 << int ($iter);
    }

    $hash_buf = aix_ssha1_pbkdf2 ($word_buf, $salt_buf, $iterations);

    $tmp_hash = sprintf ("{ssha1}%02i\$%s\$%s", log ($iterations) / log (2), $salt_buf, $hash_buf);
  }
  elsif ($mode == 6800)
  {
    my $variant = $additional_param;

    if (! defined ($variant))
    {
      $variant = int (rand (2));
    }

    my $iterations = 500;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $iv = "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";

    my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256);

    my $pbkdf2 = Crypt::PBKDF2->new (
      hasher       => $hasher,
      iterations   => $iterations,
      output_len   => 32
    );

    my $key = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

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

    if ($variant == 1)
    {
      my $encrypt = $cipher->encrypt (substr ($salt_buf, 0, 16));

      $hash_buf = substr (unpack ("H*", $encrypt), 0, 32);
    }
    else
    {
      my $verifier = "lastpass rocks\x02\x02";

      $hash_buf = unpack ("H*", substr ($cipher->encrypt ($verifier), 0, 16));
    }

    $tmp_hash = sprintf ("%s:%i:%s", $hash_buf, $iterations, $salt_buf);
  }
  elsif ($mode == 6900)
  {
    $hash_buf = gost_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 7000)
  {
    my $FORTIGATE_SIGNATURE = "AK1";
    my $FORTIGATE_MAGIC     = pack ("H*", "a388ba2e424cb04a537930c13107cc3fa1329029a9815b70");

    my $salt_bin = pack ("H*", $salt_buf);

    my $hash = sha1 ($salt_bin . $word_buf . $FORTIGATE_MAGIC);

    $hash = encode_base64 ($salt_bin . $hash, "");

    $tmp_hash = sprintf ("%s%s", $FORTIGATE_SIGNATURE, $hash);
  }
  elsif ($mode == 7100)
  {
    my $iterations = 1024;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512),
      iterations => $iterations
    );

    $hash_buf = unpack ("H*", $pbkdf2->PBKDF2 (pack ("H*", $salt_buf), $word_buf));

    $tmp_hash = sprintf ("\$ml\$%i\$%s\$%0128s", $iterations, $salt_buf, $hash_buf);
  }
  elsif ($mode == 7200)
  {
    my $iterations = 1024;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $pbkdf2 = Crypt::PBKDF2->new (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512),
      iterations => $iterations
    );

    $hash_buf = unpack ("H*", $pbkdf2->PBKDF2 (pack ("H*", $salt_buf), $word_buf));

    $tmp_hash = sprintf ("grub.pbkdf2.sha512.%i.%s.%0128s", $iterations, $salt_buf, $hash_buf);
  }
  elsif ($mode == 7300)
  {
    $hash_buf = hmac_hex ($salt_buf, $word_buf, \&sha1);

    $tmp_hash = sprintf ("%s:%s", unpack ("H*", $salt_buf), $hash_buf);
  }
  elsif ($mode == 7400)
  {
    my $iterations = 5000;

    if (defined ($iter))
    {
      if ($iter > 0)
      {
        $iterations = int ($iter);
      }
    }

    $hash_buf = sha256_crypt ($iterations, $word_buf, $salt_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 7500)
  {
    my @salt_arr = split ("\\\$", $salt_buf);

    my $user = $salt_arr[0];

    my $realm = $salt_arr[1];

    my $salt = $salt_arr[2];

    my $hmac_salt = $salt_arr[3];
    my $hmac_salt_bin = pack ("H*", $hmac_salt);

    my $clear_data = $salt_arr[4];

    my $k = md4 (encode ("UTF-16LE", $word_buf));

    my $k1 = hmac_md5 ("\x01\x00\x00\x00", $k);

    my $k3 = hmac_md5 ($hmac_salt_bin, $k1);

    if (length ($clear_data) > 1)
    {
      my $clear_data_bin = pack ("H*", $clear_data);

      $hash_buf = RC4 ($k3, $clear_data_bin);
    }
    else
    {
      my $hash = $salt_arr[5];

      my $hash_bin = pack ("H*", $hash);

      my $clear_data = RC4 ($k3, $hash_bin);

      my $timestamp = substr ($clear_data, 14, 14);

      my $is_numeric = 1;

      if ($timestamp !~ /^[[:digit:]]{14}$/)
      {
        $is_numeric = 0;
      }

      if (! $is_numeric)
      {
        $hash_buf = "\x00" x 36;

        if ($hash_buf eq $hash_bin)
        {
          $hash_buf = "\x01" x 36;
        }
      }
      else
      {
        $hash_buf = $hash_bin;
      }
    }

    $tmp_hash = sprintf ("\$krb5pa\$23\$%s\$%s\$%s\$%s%s", $user, $realm, $salt, unpack ("H*", $hash_buf), $hmac_salt);
  }
  elsif ($mode == 7700 || $mode == 7701)
  {
    $word_buf = uc $word_buf;
    $salt_buf = uc $salt_buf;

    my $word_buf_t = sapb_transcode ($word_buf);
    my $salt_buf_t = sapb_transcode ($salt_buf);

    my $digest1 = md5 ($word_buf_t . $salt_buf_t);

    my $data = sapb_waldorf ($digest1, $word_buf_t, $salt_buf_t);

    my $digest2 = md5 ($data);

    my ($a, $b, $c, $d) = unpack ("N4", $digest2);

    $a ^= $c;
    $b ^= $d;

    if ($mode == 7700)
    {
      $tmp_hash = sprintf ("%s\$%08X%08X", $salt_buf, $a, $b);
    }
    else
    {
      $tmp_hash = sprintf ("%s\$%08X%08X", $salt_buf, $a, 0);
    }
  }
  elsif ($mode == 7800 || $mode == 7801)
  {
    my $theMagicArray_s =
      "\x91\xac\x51\x14\x9f\x67\x54\x43\x24\xe7\x3b\xe0\x28\x74\x7b\xc2" .
      "\x86\x33\x13\xeb\x5a\x4f\xcb\x5c\x08\x0a\x73\x37\x0e\x5d\x1c\x2f" .
      "\x33\x8f\xe6\xe5\xf8\x9b\xae\xdd\x16\xf2\x4b\x8d\x2c\xe1\xd4\xdc" .
      "\xb0\xcb\xdf\x9d\xd4\x70\x6d\x17\xf9\x4d\x42\x3f\x9b\x1b\x11\x94" .
      "\x9f\x5b\xc1\x9b\x06\x05\x9d\x03\x9d\x5e\x13\x8a\x1e\x9a\x6a\xe8" .
      "\xd9\x7c\x14\x17\x58\xc7\x2a\xf6\xa1\x99\x63\x0a\xd7\xfd\x70\xc3" .
      "\xf6\x5e\x74\x13\x03\xc9\x0b\x04\x26\x98\xf7\x26\x8a\x92\x93\x25" .
      "\xb0\xa2\x0d\x23\xed\x63\x79\x6d\x13\x32\xfa\x3c\x35\x02\x9a\xa3" .
      "\xb3\xdd\x8e\x0a\x24\xbf\x51\xc3\x7c\xcd\x55\x9f\x37\xaf\x94\x4c" .
      "\x29\x08\x52\x82\xb2\x3b\x4e\x37\x9f\x17\x07\x91\x11\x3b\xfd\xcd";

    $salt_buf = uc $salt_buf;

    my $digest = sha1 ($word_buf . $salt_buf);

    my ($a, $b, $c, $d, $e) = unpack ("I*", $digest);

    my $lengthMagicArray = 0x20;
    my $offsetMagicArray = 0;

    $lengthMagicArray += (($a >>  0) & 0xff) % 6;
    $lengthMagicArray += (($a >>  8) & 0xff) % 6;
    $lengthMagicArray += (($a >> 16) & 0xff) % 6;
    $lengthMagicArray += (($a >> 24) & 0xff) % 6;
    $lengthMagicArray += (($b >>  0) & 0xff) % 6;
    $lengthMagicArray += (($b >>  8) & 0xff) % 6;
    $lengthMagicArray += (($b >> 16) & 0xff) % 6;
    $lengthMagicArray += (($b >> 24) & 0xff) % 6;
    $lengthMagicArray += (($c >>  0) & 0xff) % 6;
    $lengthMagicArray += (($c >>  8) & 0xff) % 6;
    $offsetMagicArray += (($c >> 16) & 0xff) % 8;
    $offsetMagicArray += (($c >> 24) & 0xff) % 8;
    $offsetMagicArray += (($d >>  0) & 0xff) % 8;
    $offsetMagicArray += (($d >>  8) & 0xff) % 8;
    $offsetMagicArray += (($d >> 16) & 0xff) % 8;
    $offsetMagicArray += (($d >> 24) & 0xff) % 8;
    $offsetMagicArray += (($e >>  0) & 0xff) % 8;
    $offsetMagicArray += (($e >>  8) & 0xff) % 8;
    $offsetMagicArray += (($e >> 16) & 0xff) % 8;
    $offsetMagicArray += (($e >> 24) & 0xff) % 8;

    my $hash_buf = sha1_hex ($word_buf . substr ($theMagicArray_s, $offsetMagicArray, $lengthMagicArray) . $salt_buf);

    if ($mode == 7800)
    {
      $tmp_hash = sprintf ("%s\$%s", $salt_buf, uc $hash_buf);
    }
    else
    {
      $tmp_hash = sprintf("%s\$%.20s%020X", $salt_buf, uc $hash_buf, 0);
    }
  }
  elsif ($mode == 7900)
  {
    my $cost = 14;

    if (length ($iter))
    {
      $cost = $iter;
    }

    my $phpass_it = 1 << $cost;

    $hash_buf = sha512 ($salt_buf . $word_buf);

    for (my $i = 0; $i < $phpass_it; $i++)
    {
      $hash_buf = sha512 ($hash_buf . $word_buf);
    }

    my $base64_buf = substr (Authen::Passphrase::PHPass::_en_base64 ($hash_buf), 0, 43);

    my $base64_digits = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

    my $cost_str = substr ($base64_digits , $cost, 1);

    $tmp_hash = sprintf ('$S$%s%s%s', $cost_str, $salt_buf, $base64_buf);
  }
  elsif ($mode == 8000)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $word_buf_utf = encode ("UTF-16BE", $word_buf);

    $hash_buf = sha256_hex ($word_buf_utf . "\x00" x (510 - (length ($word_buf) * 2)) . $salt_buf_bin);

    $tmp_hash = sprintf ("0xc007%s%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 8100)
  {
    $hash_buf = sha1_hex ($salt_buf . $word_buf . "\x00");

    $tmp_hash = sprintf ("1%s%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 8200)
  {
    my $iterations = 40000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $salt_hex = substr ($salt_buf, 0, 32);
    my $salt     = pack   ("H*", $salt_hex);

    my $data_hex = substr ($salt_buf, 32);
    my $data     = pack   ("H*", $data_hex);

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512),
      iterations => int $iterations
    );

    my $key = $pbkdf2->PBKDF2 ($salt, $word_buf);

    $hash_buf = hmac_hex ($data, substr ($key, 32, 32), \&sha256, 64);

    $tmp_hash = sprintf ("%s:%s:%d:%s", $hash_buf, $salt_hex, $iterations, $data_hex);
  }
  elsif ($mode == 8300)
  {
    my ($domain, $salt_hex) = split (":", $salt_buf);

    my $hashalg = Net::DNS::SEC->digtype ("SHA1");

    my $salt = pack ("H*", $salt_hex);

    my $iterations = 1;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $name = lc ($word_buf . $domain);

    my $hash_buf = Net::DNS::RR::NSEC3::name2hash ($hashalg, $name, $iterations, $salt);

    $tmp_hash = sprintf ("%s:%s:%s:%d", $hash_buf, $domain, $salt_hex, $iterations);
  }
  elsif ($mode == 8400 || $mode == 13900)
  {
    $hash_buf = sha1_hex ($salt_buf . sha1_hex ($salt_buf . sha1_hex ($word_buf)));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 8500)
  {
    $hash_buf = racf_hash (uc $salt_buf, $word_buf);

    $tmp_hash = sprintf ('$racf$*%s*%s', uc $salt_buf, uc $hash_buf);
  }
  elsif ($mode == 8600)
  {
    my @saved_key = map { ord $_; } split "", $word_buf;

    my $len = scalar @saved_key;

    my @state = domino_big_md (\@saved_key, $len);

    $tmp_hash = sprintf ('%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x%02x',
      $state[ 0],
      $state[ 1],
      $state[ 2],
      $state[ 3],
      $state[ 4],
      $state[ 5],
      $state[ 6],
      $state[ 7],
      $state[ 8],
      $state[ 9],
      $state[10],
      $state[11],
      $state[12],
      $state[13],
      $state[14],
      $state[15],
    );
  }
  elsif ($mode == 8700)
  {
    my $domino_char = undef;

    if (defined ($additional_param))
    {
      $domino_char = $additional_param;
    }

    my @saved_key = map { ord $_; } split "", $word_buf;

    my $len = scalar @saved_key;

    my @state = domino_big_md (\@saved_key, $len);

    my $str = "(" . unpack ("H*", join ("", (map { chr $_; } @state))) . ")";

    @saved_key = map { ord $_; } split "", $salt_buf . uc $str;

    @state = domino_big_md (\@saved_key, 34);

    $hash_buf = join ("", (map { chr $_; } @state));

    $tmp_hash = sprintf ('(G%s)', domino_encode ($salt_buf . $hash_buf, $domino_char));
  }
  elsif ($mode == 8900)
  {
    my $N = 1024;
    my $r = 1;
    my $p = 1;

    if (defined ($additional_param))
    {
      $N = $additional_param;
      $r = $additional_param2;
      $p = $additional_param3;
    }

    $hash_buf = scrypt_hash ($word_buf, $salt_buf, $N, $r, $p, 32);

    $tmp_hash = sprintf ('%s', $hash_buf);
  }
  elsif ($mode == 9100)
  {
    my $iterations = 5000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $domino_char = undef;

    # domino 5 hash - SEC_pwddigest_V1 - -m 8600

    my @saved_key = map { ord $_; } split "", $word_buf;

    my $len = scalar @saved_key;

    my @state = domino_big_md (\@saved_key, $len);


    # domino 6 hash - SEC_pwddigest_V2 - -m 8700

    my $salt_part = substr ($salt_buf, 0, 5);

    my $str = "(" . unpack ("H*", join ("", (map { chr $_; } @state))) . ")";

    @saved_key = map { ord $_; } split "", $salt_part . uc $str;

    @state = domino_big_md (\@saved_key, 34);

    $hash_buf = join ("", (map { chr $_; } @state));

    $tmp_hash = sprintf ('(G%s)', domino_encode ($salt_part . $hash_buf, $domino_char));


    # domino 8(.5.x) hash - SEC_pwddigest_V3 - -m 9100

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hash_class => 'HMACSHA1',
      iterations => $iterations,
      output_len =>  8,
      salt_len   => 16,
    );

    my $chars = "02";

    if (defined ($additional_param))
    {
      $chars = $additional_param;
    }

    my $digest_new = $pbkdf2->PBKDF2 ($salt_buf, $tmp_hash);

    for (my $i = length ($iterations); $i < 10; $i++)
    {
      $iterations = "0" . $iterations;
    }

    $tmp_hash = sprintf ('(H%s)', domino_85x_encode ($salt_buf . $iterations . $chars . $digest_new, $domino_char));
  }
  elsif ($mode == 9200)
  {
    my $iterations = 20000;

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations
    );

    $hash_buf = encode_base64 ($pbkdf2->PBKDF2 ($salt_buf, $word_buf), "");

    $tmp_hash = "";

    for (my $i = 0; $i < 43; $i++)
    {
      $tmp_hash .= $CISCO_BASE64_MAPPING->{substr ($hash_buf, $i, 1)};
    }

    $tmp_hash = sprintf ("\$8\$%s\$%s", $salt_buf, $tmp_hash);
  }
  elsif ($mode == 9300)
  {
    my $N = 16384;
    my $r = 1;
    my $p = 1;

    $hash_buf = scrypt_b64 ($word_buf, $salt_buf, $N, $r, $p, 32);

    $tmp_hash = "";

    for (my $i = 0; $i < 43; $i++)
    {
      $tmp_hash .= $CISCO_BASE64_MAPPING->{substr ($hash_buf, $i, 1)};
    }

    $tmp_hash = sprintf ('$9$%s$%s', $salt_buf, $tmp_hash);
  }
  elsif ($mode == 9400)
  {
    my $iterations = 50000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $aes_key_size = 128; # or 256

    if (defined ($additional_param2))
    {
      $aes_key_size = $additional_param2;
    }

    $salt_buf = pack ("H*", $salt_buf);

    my $tmp = sha1 ($salt_buf . encode ("UTF-16LE", $word_buf));

    for (my $i = 0; $i < $iterations; $i++)
    {
      my $num32 = pack ("L", $i);

      $tmp = sha1 ($num32 . $tmp);
    }

    my $zero32    = pack ("L", 0x00);

    my $derivation_array1 = pack ("C", 0x36) x 64;
    my $derivation_array2 = pack ("C", 0x5C) x 64;

    $tmp = sha1 ($tmp . $zero32);

    my $tmp2 = sha1 ($derivation_array1 ^ $tmp);
    my $tmp3 = sha1 ($derivation_array2 ^ $tmp);

    my $key = substr ($tmp2 . $tmp3, 0, $aes_key_size / 8);

    my $m = Crypt::Mode::ECB->new ('AES', 0);

    my $encdata;

    if (defined $additional_param)
    {
      $encdata = $m->decrypt (pack ("H*", $additional_param), $key);
    }
    else
    {
      $encdata = "A" x 16; ## can be anything
    }

    my $data1_buf = $encdata;
    my $data2_buf = sha1 (substr ($data1_buf, 0, 16));

    $data1_buf = substr ($data1_buf . ("\x00" x 16), 0, 16);
    $data2_buf = substr ($data2_buf . ("\x00" x 16), 0, 32);

    my $encrypted1 = unpack ("H*", $m->encrypt ($data1_buf, $key));
    my $encrypted2 = unpack ("H*", $m->encrypt ($data2_buf, $key));

    $encrypted1 = substr ($encrypted1, 0, 32);
    $encrypted2 = substr ($encrypted2, 0, 40);

    $tmp_hash = sprintf ("\$office\$*%d*%d*%d*%d*%s*%s*%s", 2007, 20, $aes_key_size, 16, unpack ("H*", $salt_buf), $encrypted1, $encrypted2);
  }
  elsif ($mode == 9500)
  {
    my $iterations = 100000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    $salt_buf = pack ("H*", $salt_buf);

    my $tmp = sha1 ($salt_buf . encode ("UTF-16LE", $word_buf));

    for (my $i = 0; $i < $iterations; $i++)
    {
      my $num32 = pack ("L", $i);

      $tmp = sha1 ($num32 . $tmp);
    }

    my $encryptedVerifierHashInputBlockKey = "\xfe\xa7\xd2\x76\x3b\x4b\x9e\x79";
    my $encryptedVerifierHashValueBlockKey = "\xd7\xaa\x0f\x6d\x30\x61\x34\x4e";

    my $final1 = sha1 ($tmp . $encryptedVerifierHashInputBlockKey);
    my $final2 = sha1 ($tmp . $encryptedVerifierHashValueBlockKey);

    my $key1 = substr ($final1, 0, 16);
    my $key2 = substr ($final2, 0, 16);

    my $cipher1 = Crypt::CBC->new ({
      key         => $key1,
      cipher      => "Crypt::Rijndael",
      iv          => $salt_buf,
      literal_key => 1,
      header      => "none",
      keysize     => 16,
      padding     => "null",
    });

    my $cipher2 = Crypt::CBC->new ({
      key         => $key2,
      cipher      => "Crypt::Rijndael",
      iv          => $salt_buf,
      literal_key => 1,
      header      => "none",
      keysize     => 16,
      padding     => "null",
    });

    my $encdata;

    if (defined $additional_param)
    {
      $encdata = $cipher1->decrypt (pack ("H*", $additional_param));
    }
    else
    {
      $encdata = "A" x 16; ## can be anything
    }

    my $data1_buf = $encdata;
    my $data2_buf = sha1 (substr ($data1_buf, 0, 16));

    my $encrypted1 = unpack ("H*", $cipher1->encrypt ($data1_buf));
    my $encrypted2 = unpack ("H*", $cipher2->encrypt ($data2_buf));

    $encrypted2 = substr ($encrypted2, 0, 64);

    $tmp_hash = sprintf ("\$office\$*%d*%d*%d*%d*%s*%s*%s", 2010, 100000, 128, 16, unpack ("H*", $salt_buf), $encrypted1, $encrypted2);
  }
  elsif ($mode == 9600)
  {
    my $iterations = 100000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    $salt_buf = pack ("H*", $salt_buf);

    my $tmp = sha512 ($salt_buf . encode ("UTF-16LE", $word_buf));

    for (my $i = 0; $i < $iterations; $i++)
    {
      my $num32 = pack ("L", $i);

      $tmp = sha512 ($num32 . $tmp);
    }

    my $encryptedVerifierHashInputBlockKey = "\xfe\xa7\xd2\x76\x3b\x4b\x9e\x79";
    my $encryptedVerifierHashValueBlockKey = "\xd7\xaa\x0f\x6d\x30\x61\x34\x4e";

    my $final1 = sha512 ($tmp . $encryptedVerifierHashInputBlockKey);
    my $final2 = sha512 ($tmp . $encryptedVerifierHashValueBlockKey);

    my $key1 = substr ($final1, 0, 32);
    my $key2 = substr ($final2, 0, 32);

    my $cipher1 = Crypt::CBC->new ({
      key         => $key1,
      cipher      => "Crypt::Rijndael",
      iv          => $salt_buf,
      literal_key => 1,
      header      => "none",
      keysize     => 32,
      padding     => "null",
    });

    my $cipher2 = Crypt::CBC->new ({
      key         => $key2,
      cipher      => "Crypt::Rijndael",
      iv          => $salt_buf,
      literal_key => 1,
      header      => "none",
      keysize     => 32,
      padding     => "null",
    });

    my $encdata;

    if (defined $additional_param)
    {
      $encdata = $cipher1->decrypt (pack ("H*", $additional_param));
    }
    else
    {
      $encdata = "A" x 16; ## can be anything
    }

    my $data1_buf = $encdata;
    my $data2_buf = sha512 (substr ($data1_buf, 0, 16));

    my $encrypted1 = unpack ("H*", $cipher1->encrypt ($data1_buf));
    my $encrypted2 = unpack ("H*", $cipher2->encrypt ($data2_buf));

    $encrypted2 = substr ($encrypted2, 0, 64);

    $tmp_hash = sprintf ("\$office\$*%d*%d*%d*%d*%s*%s*%s", 2013, 100000, 256, 16, unpack ("H*", $salt_buf), $encrypted1, $encrypted2);
  }
  elsif ($mode == 9700)
  {
    $salt_buf = pack ("H*", $salt_buf);

    my $tmp = md5 (encode ("UTF-16LE", $word_buf));

    $tmp = substr ($tmp, 0, 5);

    my $data;

    for (my $i = 0; $i < 16; $i++)
    {
      $data .= $tmp;
      $data .= $salt_buf;
    }

    $tmp = md5 ($data);

    $tmp = substr ($tmp, 0, 5);

    my $version;

    if (defined $additional_param2)
    {
      $version = $additional_param2;
    }
    else
    {
      $version = (unpack ("L", $tmp) & 1) ? 0 : 1;
    }

    my $rc4_key = md5 ($tmp . "\x00\x00\x00\x00");

    my $m = Crypt::RC4->new (substr ($rc4_key, 0, 16));

    my $encdata;

    if (defined $additional_param)
    {
      $encdata = $m->RC4 (pack ("H*", $additional_param));
    }
    else
    {
      $encdata = "A" x 16; ## can be anything
    }

    my $data1_buf = $encdata;
    my $data2_buf = md5 (substr ($data1_buf, 0, 16));

    $m = Crypt::RC4->new (substr ($rc4_key, 0, 16));

    my $encrypted1 = $m->RC4 ($data1_buf);
    my $encrypted2 = $m->RC4 ($data2_buf);

    $tmp_hash = sprintf ("\$oldoffice\$%d*%s*%s*%s", $version, unpack ("H*", $salt_buf), unpack ("H*", $encrypted1), unpack ("H*", $encrypted2));
  }
  elsif ($mode == 9800)
  {
    $salt_buf = pack ("H*", $salt_buf);

    my $tmp = sha1 ($salt_buf. encode ("UTF-16LE", $word_buf));

    my $version;

    if (defined $additional_param2)
    {
      $version = $additional_param2;
    }
    else
    {
      $version = (unpack ("L", $tmp) & 1) ? 3 : 4;
    }

    my $rc4_key = sha1 ($tmp . "\x00\x00\x00\x00");

    if ($version == 3)
    {
      $rc4_key = substr ($rc4_key, 0, 5) . "\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00";
    }

    my $m = Crypt::RC4->new (substr ($rc4_key, 0, 16));

    my $encdata;

    if (defined $additional_param)
    {
      $encdata = $m->RC4 (pack ("H*", $additional_param));
    }
    else
    {
      $encdata = "A" x 16; ## can be anything
    }

    my $data1_buf = $encdata;
    my $data2_buf = sha1 (substr ($data1_buf, 0, 16));

    $m = Crypt::RC4->new (substr ($rc4_key, 0, 16));

    my $encrypted1 = $m->RC4 ($data1_buf);
    my $encrypted2 = $m->RC4 ($data2_buf);

    $tmp_hash = sprintf ("\$oldoffice\$%d*%s*%s*%s", $version, unpack ("H*", $salt_buf), unpack ("H*", $encrypted1), unpack ("H*", $encrypted2));
  }
  elsif ($mode == 9900)
  {
    $tmp_hash = sprintf ("%s", md5_hex ($word_buf . "\0" x (100 - length ($word_buf))));
  }
  elsif ($mode == 10000)
  {
    my $iterations = 10000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations
    );

    $hash_buf = encode_base64 ($pbkdf2->PBKDF2 ($salt_buf, $word_buf), "");

    $tmp_hash = sprintf ("pbkdf2_sha256\$%i\$%s\$%s", $iterations, $salt_buf, $hash_buf);
  }
  elsif ($mode == 10100)
  {
    my $seed = pack ("H*", $salt_buf);

    my ($hi, $lo) = siphash ($word_buf, $seed);

    my $hi_s = sprintf ("%08x", $hi);
    my $lo_s = sprintf ("%08x", $lo);

    $hi_s =~ s/^(..)(..)(..)(..)$/$4$3$2$1/;
    $lo_s =~ s/^(..)(..)(..)(..)$/$4$3$2$1/;

    $tmp_hash = sprintf ("%s%s:2:4:%s", $hi_s, $lo_s, $salt_buf);
  }
  elsif ($mode == 10200)
  {
    my $challengeb64 = encode_base64 ($salt_buf, "");

    my $username;

    if (defined $additional_param)
    {
      $username = $additional_param;
    }
    else
    {
      $username = "user";
    }

    $hash_buf = hmac_hex ($salt_buf, $word_buf, \&md5);

    my $responseb64 = encode_base64 ($username . " " . $hash_buf, "");

    $tmp_hash = sprintf ('$cram_md5$%s$%s', $challengeb64, $responseb64);
  }
  elsif ($mode == 10300)
  {
    my $iterations = 1024;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $hash_buf = $salt_buf;

    for (my $pos = 0; $pos < $iterations; $pos++)
    {
      $hash_buf = sha1 ($word_buf . $hash_buf);
    }

    $hash_buf = encode_base64 ($hash_buf . $salt_buf, "");

    $tmp_hash = sprintf ("{x-issha, %i}%s", $iterations, $hash_buf);
  }
  elsif ($mode == 10400)
  {
    my $id  = $salt_buf;
    my $u   = $additional_param;
    my $o   = $additional_param2;
    my $P   = $additional_param3;

    if (defined $u == 0)
    {
      $u = "0" x 64;
    }

    if (defined $o == 0)
    {
      $o = "0" x 64;
    }

    if (defined $P == 0)
    {
      $P = -1;
    }

    my $padding;

    for (my $i = 0; $i < 32; $i++)
    {
      $padding .= pack ("C", $PDF_PADDING->[$i]);
    }

    my $res = pdf_compute_encryption_key ($word_buf, $padding, $id, $u, $o, $P, 1, 2, 0);

    my $m = Crypt::RC4->new (substr ($res, 0, 5));

    $u = $m->RC4 ($padding);

    $tmp_hash = sprintf ('$pdf$%d*%d*40*%d*%d*16*%s*32*%s*32*%s', 1, 2, $P, 0, $id, unpack ("H*", $u), $o);
  }
  elsif ($mode == 10500)
  {
    my $id  = $salt_buf;
    my $u   = $additional_param;
    my $o   = $additional_param2;
    my $P   = $additional_param3;
    my $V   = $additional_param4;
    my $R   = $additional_param5;
    my $enc = $additional_param6;

    if (defined $u == 0)
    {
      $u = "0" x 64;
    }

    my $u_save = $u;

    if (defined $o == 0)
    {
      $o = "0" x 64;
    }

    if (defined $R == 0)
    {
      $R = get_random_num (3, 5);
    }

    if (defined $V == 0)
    {
      $V = ($R == 3) ? 2 : 4;
    }

    if (defined $P == 0)
    {
      $P = ($R == 3) ? -4 : -1028;
    }

    if (defined $enc == 0)
    {
      $enc = ($R == 3) ? 1 : get_random_num (0, 2);
    }

    my $padding;

    for (my $i = 0; $i < 32; $i++)
    {
      $padding .= pack ("C", $PDF_PADDING->[$i]);
    }

    my $res = pdf_compute_encryption_key ($word_buf, $padding, $id, $u, $o, $P, $V, $R, $enc);

    my $digest = md5 ($padding . pack ("H*", $id));

    my $m = Crypt::RC4->new ($res);

    $u = $m->RC4 ($digest);

    my @ress = split "", $res;

    for (my $x = 1; $x <= 19; $x++)
    {
      my @xor;

      for (my $i = 0; $i < 16; $i++)
      {
        $xor[$i] = chr (ord ($ress[$i]) ^ $x);
      }

      my $s = join ("", @xor);

      my $m2 = Crypt::RC4->new ($s);

      $u = $m2->RC4 ($u);
    }

    $u .= substr (pack ("H*", $u_save), 16, 16);

    $tmp_hash = sprintf ('$pdf$%d*%d*128*%d*%d*16*%s*32*%s*32*%s', $V, $R, $P, $enc, $id, unpack ("H*", $u), $o);
  }
  elsif ($mode == 10600)
  {
    my $id   = $salt_buf;
    my $rest = $additional_param;

    if (defined $id == 0)
    {
      $id = "0" x 32;
    }

    if (defined $rest == 0)
    {
      $rest = "127*";
      $rest .= "0" x 64;
      $rest .= $id;
      $rest .= "0" x 158;
      $rest .= "*127*00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000*32*0000000000000000000000000000000000000000000000000000000000000000*32*0000000000000000000000000000000000000000000000000000000000000000";
    }

    my @data = split /\*/, $rest;

    my $u = pack ("H*", $data[1]);

    my $h = sha256 ($word_buf . substr ($u, 32, 8));

    $data[1] = unpack ("H*", $h . substr ($u, 32));

    $rest = join ("*", @data);

    $tmp_hash = sprintf ('$pdf$5*5*256*-1028*1*16*%s*%s', $id, $rest);
  }
  elsif ($mode == 10700)
  {
    my $id   = $salt_buf;
    my $rest = $additional_param;

    if (defined $id == 0)
    {
      $id = "0" x 32;
    }

    if (defined $rest == 0)
    {
      $rest = "127*";
      $rest .= "0" x 64;
      $rest .= $id;
      $rest .= "0" x 158;
      $rest .= "*127*00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000*32*0000000000000000000000000000000000000000000000000000000000000000*32*0000000000000000000000000000000000000000000000000000000000000000";
    }

    my @datax = split /\*/, $rest;

    my $u = pack ("H*", $datax[1]);

    my $block = sha256 ($word_buf . substr ($u, 32, 8));

    my $block_size = 32;

    my $data = 0x00 x 64;

    my $data_len = 1;

    my $data63 = 0;

    for (my $i = 0; $i < 64 || $i < $data63 + 32; $i++)
    {
      $data = $word_buf . $block;

      $data_len = length ($data);

      for (my $k = 1; $k < 64; $k++)
      {
        $data .= $word_buf . $block;
      }

      my $aes = Crypt::CBC->new ({
        key         => substr ($block,  0, 16),
        cipher      => "Crypt::Rijndael",
        iv          => substr ($block, 16, 16),
        literal_key => 1,
        header      => "none",
        keysize     => 16,
        padding     => "null",
      });

      my $data = $aes->encrypt ($data);

      my $sum = 0;

      for (my $j = 0; $j < 16; $j++)
      {
        $sum += ord (substr ($data, $j, 1));
      }

      $block_size = 32 + ($sum % 3) * 16;

      if ($block_size == 32)
      {
        $block = sha256 (substr ($data, 0, $data_len * 64));
      }
      elsif ($block_size == 48)
      {
        $block = sha384 (substr ($data, 0, $data_len * 64));
      }
      elsif ($block_size == 64)
      {
        $block = sha512 (substr ($data, 0, $data_len * 64));
      }

      $data63 = ord (substr ($data, $data_len * 64 - 1, 1));
    }

    $datax[1] = unpack ("H*", substr ($block, 0, 32) . substr ($u, 32));

    $rest = join ("*", @datax);

    $tmp_hash = sprintf ('$pdf$5*6*256*-1028*1*16*%s*%s', $id, $rest);
  }
  elsif ($mode == 10800)
  {
    $hash_buf = sha384_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 10900)
  {
    my $iterations = 1000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $out_len = 24;

    if (defined $additional_param)
    {
      $out_len = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => $out_len
    );

    $hash_buf = encode_base64 ($pbkdf2->PBKDF2 ($salt_buf, $word_buf), "");

    my $base64_salt_buf = encode_base64 ($salt_buf, "");

    $tmp_hash = sprintf ("sha256:%i:%s:%s", $iterations, $base64_salt_buf, $hash_buf);
  }
  elsif ($mode == 16900)
  {
    my $iterations = 10000;

    my $salt_hex = substr ($salt_buf, 0, 64);
    my $salt     = pack   ("H*", $salt_hex);

    my $ciphertext = randbytes(32);

    if (defined $additional_param)
    {
      my $ciphertext_hex = $additional_param;
      $ciphertext       = pack ("H*", $ciphertext_hex);
    }

    # actually 80 but the last 16 bytes are the IV which we don't need
    my $out_len = 64;

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => $out_len
    );

    my $derived_key = $pbkdf2->PBKDF2 ($salt, $word_buf);

    $hash_buf = hmac_hex ($ciphertext, substr ($derived_key, 32, 32), \&sha256);

    $tmp_hash = sprintf ('$ansible$0*0*%s*%s*%s', unpack ("H*", $salt), unpack ("H*", $ciphertext), $hash_buf);
  }
  elsif ($mode == 11000)
  {
    $hash_buf = md5_hex ($salt_buf . $word_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 11100)
  {
    my $user = "postgres";

    if (defined $additional_param)
    {
      $user = $additional_param;
    }

    $hash_buf = md5_hex (md5_hex ($word_buf . $user) . pack ("H*", $salt_buf));

    $tmp_hash = sprintf ("\$postgres\$%s*%s*%s", $user, $salt_buf, $hash_buf);
  }
  elsif ($mode == 11200)
  {
    my $sha1_pass   = sha1 ($word_buf);
    my $double_sha1 = sha1 ($sha1_pass);

    my $xor_part1 = $sha1_pass;
    my $xor_part2 = sha1 (pack ("H*", $salt_buf) . $double_sha1);

    my $hash_buf = "";

    for (my $i = 0; $i < 20; $i++)
    {
      my $first_byte  = substr ($xor_part1, $i, 1);
      my $second_byte = substr ($xor_part2, $i, 1);

      my $xor_result = $first_byte ^ $second_byte;

      $hash_buf .= unpack ("H*", $xor_result);
    }

    $tmp_hash = sprintf ("\$mysqlna\$%s*%s", $salt_buf, $hash_buf);
  }
  elsif ($mode == 11300)
  {
    my $ckey_buf = get_random_string (96);

    if (length ($additional_param))
    {
      $ckey_buf = $additional_param;
    }

    my $public_key_buf = get_random_string (66);

    if (length ($additional_param2))
    {
      $public_key_buf = $additional_param2;
    }

    my $salt_iter = get_random_num (150000, 250000);

    if (length ($iter))
    {
      $salt_iter = int ($iter);
    }

    my $hash_buf = sha512 ($word_buf . pack ("H*", $salt_buf));

    for (my $i = 1; $i < $salt_iter; $i++)
    {
      $hash_buf = sha512 ($hash_buf);
    }

    my $data = get_random_string (32);

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

    my $cry_master_buf = (unpack ("H*", $aes->encrypt ($data)));

    $tmp_hash = sprintf ('$bitcoin$%d$%s$%d$%s$%d$%d$%s$%d$%s',
      length ($cry_master_buf),
      $cry_master_buf,
      length ($salt_buf),
      $salt_buf,
      $salt_iter,
      length ($ckey_buf),
      $ckey_buf,
      length ($public_key_buf),
      $public_key_buf);
  }
  elsif ($mode == 11400)
  {
    my ($directive, $URI_server, $URI_client, $user, $realm, $nonce, $nonce_count, $nonce_client, $qop, $method, $URI, $URI_prefix, $URI_resource, $URI_suffix);

    $directive = "MD5"; # only directive currently supported

    if (defined ($additional_param))
    {
      $user   = $additional_param;
      $realm  = $additional_param2;
      $nonce  = $salt_buf;
      $nonce_count  = $additional_param3;
      $nonce_client = $additional_param4;
      $qop = $additional_param5;
      $method = $additional_param6;

      $URI_prefix   = $additional_param7;
      $URI_resource = $additional_param8;
      $URI_suffix   = $additional_param9;

      # not needed information

      $URI_server = $additional_param10;
      $URI_client = $additional_param11;
    }
    else
    {
      $user   = get_random_string (get_random_num (0, 12 + 1));

      # special limit: (user_len + 1 + realm_len + 1 + word_buf_len) < 56
      my $realm_max_len = 55 - length ($user) - 1 - length ($word_buf) - 1;

      if ($realm_max_len < 1) # should never happen
      {
        $realm_max_len = 1;
      }

      $realm_max_len = min (20, $realm_max_len);

      $realm  = get_random_string (get_random_num (0, $realm_max_len + 1));

      $nonce  = $salt_buf;

      if (get_random_num (0, 1 + 1) == 1)
      {
        $qop = "auth";

        $nonce_count  = get_random_string (get_random_num (0, 10 + 1));
        $nonce_client = get_random_string (get_random_num (0, 12 + 1));
      }
      else
      {
        $qop = "";

        $nonce_count  = "";
        $nonce_client = "";
      }

      $method = get_random_string (get_random_num (0, 24 + 1));

      $URI_prefix   = get_random_string (get_random_num (0, 10 + 1));
      $URI_resource = get_random_string (get_random_num (1, 32 + 1));
      $URI_suffix   = get_random_string (get_random_num (0, 32 + 1));

      # not needed information

      $URI_server = get_random_string (get_random_num (0, 32 + 1));
      $URI_client = $URI_resource; # simplification
    }

    # start

    $URI = "";

    if (length ($URI_prefix) > 0)
    {
      $URI = $URI_prefix . ":";
    }

    $URI .= $URI_resource;

    if (length ($URI_suffix) > 0)
    {
      $URI .= ":" . $URI_suffix;
    }

    my $HA2 = md5_hex ($method . ":" . $URI);

    my $HA1 = md5_hex ($user . ":" . $realm . ":" . $word_buf);

    my $tmp_buf;

    if (($qop eq "auth") || ($qop eq "auth-int"))
    {
      $tmp_buf = $nonce . ":" . $nonce_count . ":" . $nonce_client . ":" . $qop;
    }
    else
    {
      $tmp_buf = $nonce;
    }

    my $hash_buf = md5_hex ($HA1 . ":" . $tmp_buf . ":" . $HA2);

    $tmp_hash = sprintf ("\$sip\$*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s*%s", $URI_server, $URI_resource, $user, $realm, $method, $URI_prefix, $URI_resource, $URI_suffix, $nonce, $nonce_client, $nonce_count, $qop, $directive, $hash_buf);
  }
  elsif ($mode == 11500)
  {
    $hash_buf = crc32 ($word_buf);

    $tmp_hash = sprintf ("%08x:00000000", $hash_buf);
  }
  elsif ($mode == 11600)
  {
    my ($p, $num_cycle_power, $seven_zip_salt_len, $seven_zip_salt_buf, $salt_len, $data_len, $unpack_size, $data_buf);

    $p = 0; # is fixed

    my $validation_only = 0;

    $validation_only = 1 if (defined ($additional_param));

    if ($validation_only == 1)
    {
      $num_cycle_power = int ($iter);
      $seven_zip_salt_len = $additional_param;
      $seven_zip_salt_buf = $additional_param2;
      $salt_len = $additional_param3;
      # $salt_buf set in parser
      # $hash_buf (resulting crc)
      $data_len = $additional_param4;
      $unpack_size = $additional_param5;
      $data_buf = $additional_param6;
    }
    else
    {
      $num_cycle_power = 14; # by default it is 19
      $seven_zip_salt_len = 0;
      $seven_zip_salt_buf = "";
      $salt_len = length ($salt_buf);
      # $salt_buf set automatically
      # $hash_buf (resulting crc)
      # $data_len will be set when encrypting
      $unpack_size = get_random_num (1, 32 + 1);
      $data_buf = get_random_string ($unpack_size);
    }

    #
    # 2 ^ NumCyclesPower "iterations" of SHA256 (only one final SHA256)
    #

    $word_buf = encode ("UTF-16LE", $word_buf);

    my $rounds = 1 << $num_cycle_power;

    my $pass_buf = "";

    for (my $i = 0; $i < $rounds; $i++)
    {
      my $num_buf = "";

      $num_buf .= pack ("V", $i);
      $num_buf .= "\x00" x 4;

      # this would be better but only works on 64-bit systems:
      # $num_buf = pack ("q", $i);

      $pass_buf .= sprintf ("%s%s", $word_buf, $num_buf);
    }

    my $key = sha256 ($pass_buf);

    # the salt_buf is our IV for AES CBC
    # pad the salt_buf

    my $salt_buf_len = length ($salt_buf);
    my $salt_padding_len = 0;

    if ($salt_buf_len < 16)
    {
      $salt_padding_len = 16 - $salt_buf_len;
    }

    $salt_buf .= "\x00" x $salt_padding_len;

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

    if ($validation_only == 1)
    {
      # decrypt

      my $decrypted_data = $aes->decrypt ($data_buf);

      $decrypted_data = substr ($decrypted_data, 0, $unpack_size);

      $hash_buf = crc32 ($decrypted_data);
    }
    else
    {
      # encrypt

      $hash_buf = crc32 ($data_buf);

      $data_buf = $aes->encrypt ($data_buf);

      $data_len = length ($data_buf);
    }

    $tmp_hash = sprintf ("\$7z\$%i\$%i\$%i\$%s\$%i\$%08s\$%u\$%u\$%u\$%s", $p, $num_cycle_power, $seven_zip_salt_len, $seven_zip_salt_buf, $salt_len, unpack ("H*", $salt_buf), $hash_buf, $data_len, $unpack_size, unpack ("H*", $data_buf));
  }
  elsif ($mode == 11700)
  {
    # PyGOST outputs digests in little-endian order, while the kernels
    # expect them in big-endian; hence the digest[::-1] mirroring.
    # Using sys.stdout.write instead of print to disable \n character.
    my $python_code = <<"END_CODE";

import binascii
import sys
from pygost import gost34112012256
digest = gost34112012256.new(b"$word_buf").digest()
sys.stdout.write(binascii.hexlify(digest[::-1]))

END_CODE

    $tmp_hash = `python2 -c '$python_code'`;
  }
  elsif ($mode == 11750)
  {
    my $python_code = <<"END_CODE";

import binascii
import hmac
import sys
from pygost import gost34112012256
key    = b"$word_buf"
msg    = b"$salt_buf"
digest = hmac.new(key, msg, gost34112012256).digest()
sys.stdout.write(binascii.hexlify(digest[::-1]))

END_CODE

    $hash_buf = `python2 -c '$python_code'`;
    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 11760)
  {
    my $python_code = <<"END_CODE";

import binascii
import hmac
import sys
from pygost import gost34112012256
key    = b"$salt_buf"
msg    = b"$word_buf"
digest = hmac.new(key, msg, gost34112012256).digest()
sys.stdout.write(binascii.hexlify(digest[::-1]))

END_CODE

    $hash_buf = `python2 -c '$python_code'`;
    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 11800)
  {
    my $python_code = <<"END_CODE";

import binascii
import sys
from pygost import gost34112012512
digest = gost34112012512.new(b"$word_buf").digest()
sys.stdout.write(binascii.hexlify(digest[::-1]))

END_CODE

    $tmp_hash = `python2 -c '$python_code'`;
  }
  elsif ($mode == 11850)
  {
    my $python_code = <<"END_CODE";

import binascii
import hmac
import sys
from pygost import gost34112012512
key    = b"$word_buf"
msg    = b"$salt_buf"
digest = hmac.new(key, msg, gost34112012512).digest()
sys.stdout.write(binascii.hexlify(digest[::-1]))

END_CODE

    $hash_buf = `python2 -c '$python_code'`;
    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 11860)
  {
    my $python_code = <<"END_CODE";

import binascii
import hmac
import sys
from pygost import gost34112012512
key    = b"$salt_buf"
msg    = b"$word_buf"
digest = hmac.new(key, msg, gost34112012512).digest()
sys.stdout.write(binascii.hexlify(digest[::-1]))

END_CODE

    $hash_buf = `python2 -c '$python_code'`;
    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 11900)
  {
    my $iterations = 1000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $out_len = 32;

    if (defined $additional_param)
    {
      $out_len = $additional_param;
    }

    #
    # call PHP here - WTF
    #

    # sanitize $word_buf and $salt_buf:

    my $word_buf_base64 = encode_base64 ($word_buf, "");
    my $salt_buf_base64 = encode_base64 ($salt_buf, "");

    # sanitize lenghs

    $out_len = int ($out_len);

    # output is in hex encoding, otherwise it could be screwed (but shouldn't)

    my $php_code = <<'END_CODE';

    function pbkdf2 ($algorithm, $password, $salt, $count, $key_length, $raw_output = false)
    {
        $algorithm = strtolower ($algorithm);

        if (! in_array ($algorithm, hash_algos (), true))
        {
          trigger_error ("PBKDF2 ERROR: Invalid hash algorithm.", E_USER_ERROR);
        }

        if ($count <= 0 || $key_length <= 0)
        {
          trigger_error ("PBKDF2 ERROR: Invalid parameters.", E_USER_ERROR);
        }

        if (function_exists ("hash_pbkdf2"))
        {
          if (!$raw_output)
          {
              $key_length = $key_length * 2;
          }

          return hash_pbkdf2 ($algorithm, $password, $salt, $count, $key_length, $raw_output);
        }

        $hash_length = strlen (hash ($algorithm, "", true));
        $block_count = ceil ($key_length / $hash_length);

        $output = "";

        for ($i = 1; $i <= $block_count; $i++)
        {
          $last = $salt . pack ("N", $i);

          $last = $xorsum = hash_hmac ($algorithm, $last, $password, true);

          for ($j = 1; $j < $count; $j++)
          {
            $xorsum ^= ($last = hash_hmac ($algorithm, $last, $password, true));
          }

          $output .= $xorsum;
        }

        if ($raw_output)
        {
          return substr ($output, 0, $key_length);
        }
        else
        {
          return bin2hex (substr ($output, 0, $key_length));
        }
    }

    print pbkdf2 ("md5", base64_decode ("$word_buf_base64"), base64_decode ("$salt_buf_base64"), $iterations, $out_len, False);

END_CODE

    # replace with these command line arguments

    $php_code =~ s/\$word_buf_base64/$word_buf_base64/;
    $php_code =~ s/\$salt_buf_base64/$salt_buf_base64/;
    $php_code =~ s/\$iterations/$iterations/;
    $php_code =~ s/\$out_len/$out_len/;

    my $php_output = `php -r '$php_code'`;

    $hash_buf = pack ("H*", $php_output);

    $hash_buf = encode_base64 ($hash_buf, "");

    my $base64_salt_buf = encode_base64 ($salt_buf, "");

    $tmp_hash = sprintf ("md5:%i:%s:%s", $iterations, $base64_salt_buf, $hash_buf);
  }
  elsif ($mode == 12000)
  {
    my $iterations = 1000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $out_len = 16;

    if (defined $additional_param)
    {
      $out_len = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
      iterations => $iterations,
      output_len => $out_len
    );

    $hash_buf = encode_base64 ($pbkdf2->PBKDF2 ($salt_buf, $word_buf), "");

    my $base64_salt_buf = encode_base64 ($salt_buf, "");

    $tmp_hash = sprintf ("sha1:%i:%s:%s", $iterations, $base64_salt_buf, $hash_buf);
  }
  elsif ($mode == 12001)
  {
    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
      iterations => 10000,
      output_len => 32
    );

    my $base64_buf = encode_base64 ($salt_buf . $pbkdf2->PBKDF2 ($salt_buf, $word_buf), "");

    $tmp_hash = sprintf ("{PKCS5S2}%s", $base64_buf);
  }
  elsif ($mode == 12100)
  {
    my $iterations = 1000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $out_len = 16;

    if (defined $additional_param)
    {
      $out_len = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512),
      iterations => $iterations,
      output_len => $out_len
    );

    $hash_buf = encode_base64 ($pbkdf2->PBKDF2 ($salt_buf, $word_buf), "");

    my $base64_salt_buf = encode_base64 ($salt_buf, "");

    $tmp_hash = sprintf ("sha512:%i:%s:%s", $iterations, $base64_salt_buf, $hash_buf);
  }
  elsif ($mode == 12200)
  {
    my $iterations = 65536;

    my $default_salt = 0;

    if (defined $additional_param)
    {
      $default_salt = int ($additional_param);
    }

    if ($default_salt == 1)
    {
      $salt_buf = "0011223344556677";
    }

    $hash_buf = sha512 (pack ("H*", $salt_buf) . $word_buf);

    for (my $i = 0; $i < $iterations; $i++)
    {
      $hash_buf = sha512 ($hash_buf);
    }

    $hash_buf = unpack ("H*", $hash_buf);
    $hash_buf = substr ($hash_buf, 0, 16);

    if ($default_salt == 0)
    {
      $tmp_hash = sprintf ("\$ecryptfs\$0\$1\$%s\$%s", $salt_buf, $hash_buf);
    }
    else
    {
      $tmp_hash = sprintf ("\$ecryptfs\$0\$%s", $hash_buf);
    }
  }
  elsif ($mode == 12300)
  {
    my $iterations = 4096;

    my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512);

    my $pbkdf2 = Crypt::PBKDF2->new (
      hasher       => $hasher,
      iterations   => $iterations,
      output_len   => 64
    );

    my $salt_bin = pack ("H*", $salt_buf);

    my $key = $pbkdf2->PBKDF2 ($salt_bin. "AUTH_PBKDF2_SPEEDY_KEY", $word_buf);

    $hash_buf = sha512_hex ($key . $salt_bin);

    $tmp_hash = sprintf ("%s%s", uc ($hash_buf), uc ($salt_buf));
  }
  elsif ($mode == 12400)
  {
    my $iterations;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }
    else
    {
      $iterations = get_random_num (1, 5001 + 1);
    }

    my $key_value  = fold_password ($word_buf);

    my $data = "\x00\x00\x00\x00\x00\x00\x00\x00";
    my $salt_value = base64_to_int24 ($salt_buf);

    $hash_buf = crypt_rounds ($key_value, $iterations, $salt_value, $data);

    $tmp_hash = sprintf ("_%s%s%s", int24_to_base64 ($iterations), $salt_buf, block_to_base64 ($hash_buf));
  }
  elsif ($mode == 12600)
  {
    $hash_buf = sha1_hex ($word_buf);

    $hash_buf = sha256_hex ($salt_buf . uc $hash_buf);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 12700)
  {
    my $iterations = 10;

    my $data = qq|{
  "guid" : "00000000-0000-0000-0000-000000000000",
  "sharedKey" : "00000000-0000-0000-0000-000000000000",
  "options" : {"pbkdf2_iterations":10,"fee_policy":0,"html5_notifications":false,"logout_time":600000,"tx_display":0,"always_keep_local_backup":false}|;

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1');

    my $pbkdf2 = Crypt::PBKDF2->new (
      hasher       => $hasher,
      iterations   => $iterations,
      output_len   => 32
    );

    my $key = $pbkdf2->PBKDF2 ($salt_buf_bin, $word_buf);

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

    my $encrypted = unpack ("H*", $cipher->encrypt ($data));

    $tmp_hash = sprintf ("\$blockchain\$%s\$%s", length ($salt_buf . $encrypted) / 2, $salt_buf . $encrypted);
  }
  elsif ($mode == 12800)
  {
    my $iterations = 100;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $nt = md4_hex (encode ("UTF-16LE", $word_buf));

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => 32
    );

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $hash = $pbkdf2->PBKDF2 ($salt_buf_bin, uc (encode ("UTF-16LE", $nt)));

    $tmp_hash = sprintf ("v1;PPH1_MD4,%s,%d,%s", $salt_buf, $iterations, unpack ("H*", $hash));
  }
  elsif ($mode == 12900)
  {
    my $iterations = 4096;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $salt2 = $salt_buf . $salt_buf;

    if (defined $additional_param)
    {
      $salt2 = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => 32
    );

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $hash = $pbkdf2->PBKDF2 ($salt_buf_bin, $word_buf);

    my $salt2_bin = pack ("H*", $salt2);

    my $hash_hmac = hmac_hex ($salt2_bin, $hash, \&sha256, 64);

    $tmp_hash = sprintf ("%s%s%s", $salt2, $hash_hmac, $salt_buf);
  }
  elsif ($mode == 13000)
  {
    my $iterations = 15;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $iv = "0" x 32;

    if (defined $additional_param)
    {
      $iv = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => (1 << $iterations) + 32,
      output_len => 32
    );

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $hash = $pbkdf2->PBKDF2 ($salt_buf_bin, $word_buf);

    my $hash_final = substr ($hash,  0, 8)
                   ^ substr ($hash,  8, 8)
                   ^ substr ($hash, 16, 8)
                   ^ substr ($hash, 24, 8);

    $tmp_hash = sprintf ('$rar5$16$%s$%d$%s$8$%s', $salt_buf, $iterations, $iv, unpack ("H*", $hash_final));
  }
  elsif ($mode == 13100)
  {
    my @salt_arr = split ('\$', $salt_buf);

    my $user  = $salt_arr[0];

    my $realm = $salt_arr[1];

    my $spn   = $salt_arr[2];

    my $k = md4 (encode ("UTF-16LE", $word_buf));

    my $k1 = hmac_md5 ("\x02\x00\x00\x00", $k);

    my $cleartext_ticket = '6381b03081ada00703050050a00000a11b3019a003020117a1'.
      '12041058e0d77776e8b8e03991f2966939222aa2171b154d594b5242544553542e434f4e5'.
      '44f534f2e434f4da3133011a003020102a10a30081b067472616e6365a40b3009a0030201'.
      '01a1020400a511180f32303136303231353134343735305aa611180f32303136303231353'.
      '134343735305aa711180f32303136303231363030343735305aa811180f32303136303232'.
      '323134343735305a';

    my $checksum = "";

    if (defined $additional_param)
    {
      $checksum = pack ("H*", $additional_param);
    }
    else
    {
      my $nonce = $salt_arr[3];

      $cleartext_ticket = $nonce . $cleartext_ticket;

      $checksum = hmac_md5 (pack ("H*", $cleartext_ticket), $k1);
    }

    my $k3 = hmac_md5 ($checksum, $k1);

    my $edata2 = "";

    if (defined $additional_param2)
    {
      $edata2 = $additional_param2;

      my $cipher_decrypt = Crypt::RC4->new ($k3);

      my $ticket_decrypt = unpack ("H*", $cipher_decrypt->RC4 (pack ("H*", $edata2)));

      my $check_correct  = ((substr ($ticket_decrypt, 16, 4) eq "6381" && substr ($ticket_decrypt, 22, 2) eq "30") ||
                            (substr ($ticket_decrypt, 16, 4) eq "6382")) &&
                           ((substr ($ticket_decrypt, 32, 6) eq "030500") ||
                            (substr ($ticket_decrypt, 32, 8) eq "050307A0"));

      if ($check_correct == 1)
      {
        $cleartext_ticket = $ticket_decrypt;
      }
      else # validation failed
      {
        # fake/wrong ticket (otherwise if we just decrypt/encrypt we end up with false positives all the time)
        $cleartext_ticket = "0" x (length ($cleartext_ticket) + 16);
      }
    }

    my $cipher = Crypt::RC4->new ($k3);

    $edata2 = $cipher->RC4 (pack ("H*", $cleartext_ticket));

    $tmp_hash = sprintf ('$krb5tgs$23$*%s$%s$%s*$%s$%s', $user, $realm, $spn, unpack ("H*", $checksum), unpack ("H*", $edata2));
  }
  elsif ($mode == 13200)
  {
    my @salt_arr = split ('\*', $salt_buf);

    my $iteration = $salt_arr[0];

    my $mysalt = $salt_arr[1];

    $mysalt = pack ("H*", $mysalt);

    my $iv = "a6a6a6a6a6a6a6a6";

    my $KEK = sha1 ($word_buf);

    $KEK = substr ($KEK ^ $mysalt, 0, 16);

    my $aes = Crypt::Mode::ECB->new ('AES');

    my $B;

    my $A;

    my @R = ();

    if (defined $additional_param)
    {
      $additional_param = pack ("H*", $additional_param);

      $A = substr ($additional_param,  0, 8);
      $B = 0x00 x 8;

      $R[1] = substr ($additional_param,  8, 8);
      $R[2] = substr ($additional_param, 16, 8);

      for (my $j = $iteration - 1; $j >= 0; $j--)
      {
        $A = substr ($A, 0, 8) ^ pack ("l", (2 * $j + 2));

        $B = $R[2];

        $A = $aes->decrypt ($A . $B . "\x00" x 16, $KEK);

        $R[2] = substr ($A, 8, 16);

        $A = substr ($A, 0, 8) ^ pack ("l", (2 * $j + 1));

        $B = $R[1];

        $A = $aes->decrypt ($A . $B . "\x00" x 16, $KEK);

        $R[1] = substr ($A, 8, 16);
      }

      # check if valid
      if (index ($A, "\xa6\xa6\xa6\xa6\xa6\xa6\xa6\xa6") != 0)
      {
        # fake wrong @R and $A values

        @R = ('', "\x00" x 8, "\x00" x 8);

        $A = "\x00" x 16;
      }
    }
    else
    {
      my $DEK = randbytes (16);

      @R = ('', substr (pack ("H*", $DEK), 0, 8), substr (pack ("H*", $DEK), 8, 16));

      $A = pack ("H*", $iv);
    }

    for (my $j = 0; $j < $iteration; $j++)
    {
      $B = $aes->encrypt ($A . $R[1], $KEK);

      $A = substr ($B, 0, 8) ^ pack ("q", (2 * $j + 1));

      $R[1] = substr ($B, 8, 16);

      $B = $aes->encrypt ($A . $R[2], $KEK);

      $A = substr ($B, 0, 8) ^ pack ("q", (2 * $j + 2));

      $R[2] = substr ($B, 8, 16);
    }

    my $wrapped_key = unpack ("H*", $A . substr ($R[1], 0 ,8) . substr ($R[2], 0 ,8));

    $mysalt = unpack ("H*", $mysalt);

    $tmp_hash = sprintf ('$axcrypt$*1*%s*%s*%s', $iteration, $mysalt, $wrapped_key);
  }
  elsif ($mode == 13300)
  {
    my $length = 32;

    if ($additional_param)
    {
      $length = $additional_param;
    }

    $hash_buf = sha1_hex ($word_buf);

    $tmp_hash = sprintf ('$axcrypt_sha1$%s', substr ($hash_buf, 0, $length));
  }
  elsif ($mode == 13400)
  {
    my @salt_arr = split ('\*', $salt_buf);

    my $version   = $salt_arr[0];

    my $iteration = $salt_arr[1];

    my $algorithm = $salt_arr[2];

    my $final_random_seed  = $salt_arr[3];

    my $transf_random_seed = $salt_arr[4];

    my $enc_iv = $salt_arr[5];

    my $contents_hash;

    # specific to version 1
    my $inline_flag;
    my $contents_len;
    my $contents;

    # specific to version 2
    my $expected_bytes;

    # specific to keyfile handling
    my $inline_keyfile_flag;
    my $keyfile_len;
    my $keyfile_content;
    my $keyfile_attributes = "";

    $final_random_seed  = pack ("H*", $final_random_seed);

    $transf_random_seed = pack ("H*", $transf_random_seed);

    $enc_iv = pack ("H*", $enc_iv);

    my $intermediate_hash = sha256 ($word_buf);

    if ($version == 1)
    {
      $contents_hash = $salt_arr[6];

      $contents_hash = pack ("H*", $contents_hash);

      $inline_flag   = $salt_arr[7];


      $contents_len  = $salt_arr[8];


      $contents      = $salt_arr[9];

      $contents      = pack ("H*", $contents);

      # keyfile handling
      if (scalar @salt_arr == 13)
      {
        $inline_keyfile_flag = $salt_arr[10];

        $keyfile_len         = $salt_arr[11];

        $keyfile_content     = $salt_arr[12];

        $keyfile_attributes = $keyfile_attributes
                            . "*" . $inline_keyfile_flag
                            . "*" . $keyfile_len
                            . "*" . $keyfile_content;

        $intermediate_hash = $intermediate_hash . pack ("H*", $keyfile_content);

        $intermediate_hash = sha256 ($intermediate_hash);
      }
    }
    elsif ($version == 2)
    {
      # keyfile handling
      if (scalar @salt_arr == 11)
      {
        $inline_keyfile_flag = $salt_arr[8];

        $keyfile_len         = $salt_arr[9];

        $keyfile_content     = $salt_arr[10];

        $intermediate_hash = $intermediate_hash . pack ("H*", $keyfile_content);

        $keyfile_attributes = $keyfile_attributes
                    . "*" . $inline_keyfile_flag
                    . "*" . $keyfile_len
                    . "*" . $keyfile_content;

      }

      $intermediate_hash = sha256 ($intermediate_hash);
    }

    my $aes = Crypt::Mode::ECB->new ('AES', 1);

    for (my $j = 0; $j < $iteration; $j++)
    {
      $intermediate_hash = $aes->encrypt ($intermediate_hash, $transf_random_seed);

      $intermediate_hash = substr ($intermediate_hash, 0, 32);
    }

    $intermediate_hash = sha256 ($intermediate_hash);

    my $final_key = sha256 ($final_random_seed . $intermediate_hash);

    my $final_algorithm;

    if ($version == 1 && $algorithm == 1)
    {
      $final_algorithm = "Crypt::Twofish";
    }
    else
    {
      $final_algorithm = "Crypt::Rijndael";
    }

    my $cipher = Crypt::CBC->new ({
                   key         => $final_key,
                   cipher      => $final_algorithm,
                   iv          => $enc_iv,
                   literal_key => 1,
                   header      => "none",
                   keysize     => 32
                 });

    if ($version == 1)
    {
      if (defined $additional_param)
      {
        # if we try to verify the crack, we need to decrypt the contents instead of only encrypting it:

        $contents = $cipher->decrypt ($contents);

        # and check the output

        my $contents_hash_old = $contents_hash;

        $contents_hash = sha256 ($contents);

        if ($contents_hash_old ne $contents_hash)
        {
          # fake content
          $contents = "\x00" x length ($contents);
        }
      }
      else
      {
        $contents_hash = sha256 ($contents);
      }

      $contents = $cipher->encrypt ($contents);

      $tmp_hash = sprintf ('$keepass$*%d*%d*%d*%s*%s*%s*%s*%d*%d*%s%s',
            $version,
            $iteration,
            $algorithm,
            unpack ("H*", $final_random_seed),
            unpack ("H*", $transf_random_seed),
            unpack ("H*", $enc_iv),
            unpack ("H*", $contents_hash),
            $inline_flag,
            $contents_len,
            unpack ("H*", $contents),
            $keyfile_attributes);
    }
    if ($version == 2)
    {
      $expected_bytes = $salt_arr[6];

      $contents_hash = $salt_arr[7];
      $contents_hash = pack ("H*", $contents_hash);

      $expected_bytes = $cipher->decrypt ($contents_hash);

      $tmp_hash = sprintf ('$keepass$*%d*%d*%d*%s*%s*%s*%s*%s%s',
            $version,
            $iteration,
            $algorithm,
            unpack ("H*", $final_random_seed),
            unpack ("H*", $transf_random_seed),
            unpack ("H*", $enc_iv),
            unpack ("H*", $expected_bytes),
            unpack ("H*", $contents_hash),
            $keyfile_attributes);
    }
  }
  elsif ($mode == 13500)
  {
    $hash_buf = sha1_hex (pack ("H*", $salt_buf) . encode ("UTF-16LE", $word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 13600)
  {
    my $iterations = 1000;

    my $type = 0;

    if (defined $additional_param)
    {
      $type = $additional_param;
    }

    my $mode = 1 + int rand (3);

    if (defined $additional_param2)
    {
      $mode = $additional_param2;
    }

    my $magic = 0;

    if (defined $additional_param3)
    {
      $magic = $additional_param3;
    }

    if (defined $additional_param4)
    {
      $salt_buf = $additional_param4;
    }

    $salt_buf = substr ($salt_buf, 0, 8 + ($mode * 8));

    my $compress_length = 0;

    if (defined $additional_param5)
    {
      $compress_length = $additional_param5;
    }

    my $data = "";

    if (defined $additional_param6)
    {
      $data = $additional_param6;
    }

    my $key_len = (8 * ($mode & 3) + 8) * 2;

    my $out_len = $key_len + 2;

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1');

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher      => $hasher,
      iterations  => $iterations,
      output_len  => $out_len
    );

    my $key = $pbkdf2->PBKDF2_hex ($salt_buf_bin, $word_buf);

    my $verify_bytes = substr ($key, -4); $verify_bytes =~ s/^0+//; #lol

    $key = substr ($key, $key_len, $key_len);

    my $key_bin = pack ("H*", $key);

    my $auth = hmac_hex ($data, $key_bin, \&sha1, 64);

    $tmp_hash = sprintf ('$zip2$*%u*%u*%u*%s*%s*%u*%s*%s*$/zip2$', $type, $mode, $magic, $salt_buf, $verify_bytes, $compress_length, $data, substr ($auth, 0, 20));
  }
  elsif ($mode == 13800)
  {
    my $word_buf_utf16le = encode ("UTF-16LE", $word_buf);

    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha256_hex ($word_buf_utf16le . $salt_buf_bin);

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 14000)
  {
    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $cipher = new Crypt::DES $word_buf;

    my $hash_buf = $cipher->encrypt ($salt_buf_bin);

    $tmp_hash = sprintf ("%s:%s", unpack ("H*", $hash_buf), $salt_buf);
  }
  elsif ($mode == 14100)
  {
    my $word_buf1 = substr ($word_buf,  0, 8);
    my $word_buf2 = substr ($word_buf,  8, 8);
    my $word_buf3 = substr ($word_buf, 16, 8);

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $cipher1 = new Crypt::DES $word_buf1;

    my $hash_buf1 = $cipher1->encrypt ($salt_buf_bin);

    my $cipher2 = new Crypt::DES $word_buf2;

    my $hash_buf2 = $cipher2->decrypt ($hash_buf1);

    my $cipher3 = new Crypt::DES $word_buf3;

    my $hash_buf3 = $cipher3->encrypt ($hash_buf2);

    $tmp_hash = sprintf ("%s:%s", unpack ("H*", $hash_buf3), $salt_buf);
  }
  elsif ($mode == 14400)
  {
    my $begin = "--" . $salt_buf . "--";
    my $end   = "--" . $word_buf . "----";

    my $hash_buf = sha1_hex ($begin . $end);

    for (my $round = 1; $round < 10; $round++)
    {
      $hash_buf = sha1_hex ($begin . $hash_buf . $end);
    }

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 14700)
  {
    my $iterations = 10000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
      iterations => $iterations,
      output_len => 32
    );

    $salt_buf = pack ("H*", $salt_buf);

    my $key = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

    my $ITUNES_BACKUP_KEY = 12008468691120727718;

    my $WPKY = "\x00" x 40;

    if (defined $additional_param)
    {
      my ($A, $R) = itunes_aes_unwrap ($key, $additional_param);

      if ($A == $ITUNES_BACKUP_KEY)
      {
        $WPKY = itunes_aes_wrap ($key, $A, $R);
      }
    }
    else
    {
      my $max_number = 18446744073709551615; # 0xffffffffffffffff

      my @R;

      for (my $i = 0; $i < 4; $i++)
      {
        $R[$i] = get_random_num (0, $max_number);
      }

      $WPKY = itunes_aes_wrap ($key, $ITUNES_BACKUP_KEY, \@R);
    }

    $tmp_hash = sprintf ("\$itunes_backup\$*9*%s*%i*%s**", unpack ("H*", $WPKY), $iterations, unpack ("H*", $salt_buf));
  }
  elsif ($mode == 14800)
  {
    my $iterations = 10000;

    if (length ($iter))
    {
      $iterations = int ($iter);
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1'),
      iterations => $iterations,
      output_len => 32
    );

    $salt_buf = pack ("H*", $salt_buf);

    my $ITUNES_BACKUP_KEY = 12008468691120727718;

    my $DPIC;
    my $DPSL;

    if (defined $additional_param)
    {
      $DPIC = $additional_param2;
      $DPSL = $additional_param3;
    }
    else
    {
      #$DPIC = 10000000; it's too much for the tests
      $DPIC = 1000;
      $DPSL = randbytes (20);
    }

    my $WPKY = "\x00" x 40;

    my $pbkdf2x = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2'),
      iterations => $DPIC,
      output_len => 32
    );

    my $key_dpsl = $pbkdf2x->PBKDF2 ($DPSL, $word_buf);

    my $key = $pbkdf2->PBKDF2 ($salt_buf, $key_dpsl);

    if (defined $additional_param)
    {
      my ($A, $R) = itunes_aes_unwrap ($key, $additional_param);

      if ($A == $ITUNES_BACKUP_KEY)
      {
        $WPKY = itunes_aes_wrap ($key, $A, $R);
      }
    }
    else
    {
      my $max_number = 18446744073709551615; # 0xffffffffffffffff

      my @R;

      for (my $i = 0; $i < 4; $i++)
      {
        $R[$i] = get_random_num (0, $max_number);
      }

      $WPKY = itunes_aes_wrap ($key, $ITUNES_BACKUP_KEY, \@R);
    }

    $tmp_hash = sprintf ("\$itunes_backup\$*10*%s*%i*%s*%i*%s", unpack ("H*", $WPKY), $iterations, unpack ("H*", $salt_buf), $DPIC, unpack ("H*", $DPSL));
  }
  elsif ($mode == 14900)
  {
    my $salt_bin = pack ("H*", $salt_buf);

    my $skip32 = Crypt::Skip32->new ($word_buf);

    my $hash = $skip32->encrypt ($salt_bin);

    $tmp_hash = sprintf ("%08x:%s", unpack ("N*", $hash), $salt_buf);
  }
  elsif ($mode == 15100)
  {
    my $iterations = 20000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $pbkdf1_salt_buf = sprintf ('%s$sha1$%u', $salt_buf, $iterations);

    my $tmp = hmac ($pbkdf1_salt_buf, $word_buf, \&sha1, 64);

    for (my $r = 1; $r < $iterations; $r++)
    {
      $tmp = hmac ($tmp, $word_buf, \&sha1, 64);
    }

    my $hash_buf = "";

    $hash_buf .= to64 ((int (ord (substr ($tmp,  0, 1))) << 16) | (int (ord (substr ($tmp,  1, 1))) << 8) | (int (ord (substr ($tmp,  2, 1)))), 4);
    $hash_buf .= to64 ((int (ord (substr ($tmp,  3, 1))) << 16) | (int (ord (substr ($tmp,  4, 1))) << 8) | (int (ord (substr ($tmp,  5, 1)))), 4);
    $hash_buf .= to64 ((int (ord (substr ($tmp,  6, 1))) << 16) | (int (ord (substr ($tmp,  7, 1))) << 8) | (int (ord (substr ($tmp,  8, 1)))), 4);
    $hash_buf .= to64 ((int (ord (substr ($tmp,  9, 1))) << 16) | (int (ord (substr ($tmp, 10, 1))) << 8) | (int (ord (substr ($tmp, 11, 1)))), 4);
    $hash_buf .= to64 ((int (ord (substr ($tmp, 12, 1))) << 16) | (int (ord (substr ($tmp, 13, 1))) << 8) | (int (ord (substr ($tmp, 14, 1)))), 4);
    $hash_buf .= to64 ((int (ord (substr ($tmp, 15, 1))) << 16) | (int (ord (substr ($tmp, 16, 1))) << 8) | (int (ord (substr ($tmp, 17, 1)))), 4);
    $hash_buf .= to64 ((int (ord (substr ($tmp, 18, 1))) << 16) | (int (ord (substr ($tmp, 19, 1))) << 8) | 0                                 , 4);

    ## super hackish, but we have no other choice, as this byte is kind of a random byte added to the digest when the hash was created

    if (defined $additional_param)
    {
      $hash_buf = substr ($hash_buf, 0, 24) . substr ($additional_param, 24, 4);
    }

    $tmp_hash = sprintf ("\$sha1\$%d\$%s\$%s", $iterations, $salt_buf, $hash_buf);
  }
  elsif ($mode == 15200)
  {
    my $iterations = 5000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $data = qq|{
  "guid" : "00000000-0000-0000-0000-000000000000",
  "sharedKey" : "00000000-0000-0000-0000-000000000000",
  "options" : {"pbkdf2_iterations":$iterations,"fee_policy":0,"html5_notifications":false,"logout_time":600000,"tx_display":0,"always_keep_local_backup":false}|;

    my $salt_buf_bin = pack ("H*", $salt_buf);

    my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1');

    my $pbkdf2 = Crypt::PBKDF2->new (
      hasher       => $hasher,
      iterations   => $iterations,
      output_len   => 32
    );

    my $key = $pbkdf2->PBKDF2 ($salt_buf_bin, $word_buf);

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

    my $encrypted = unpack ("H*", $cipher->encrypt ($data));

    $tmp_hash = sprintf ("\$blockchain\$v2\$%d\$%s\$%s", $iterations, length ($salt_buf . $encrypted) / 2, $salt_buf . $encrypted);
  }
  elsif ($mode == 15300 || $mode == 15900)
  {
    my @salt_arr = split ('\*', $salt_buf);

    my $version          = $salt_arr[0];

    my $context          = $salt_arr[1];

    my $SID              = $salt_arr[2];

    my $cipher_algorithm = $salt_arr[3];

    my $hash_algorithm   = $salt_arr[4];

    my $iterations       = $salt_arr[5];

    my $salt             = pack ("H*", $salt_arr[6]);

    my $cipher_len       = $salt_arr[7];

    my $cipher;

    # intermediate values

    my $user_hash;
    my $user_derivationKey;
    my $encKey;
    my $expected_hmac;
    my $cleartext;

    if ($context == 1)
    {
       $user_hash = sha1 (encode ("UTF-16LE", $word_buf));
    }
    elsif ($context == 2)
    {
       $user_hash = md4 (encode ("UTF-16LE", $word_buf));
    }

    $user_derivationKey = hmac_sha1 (encode ("UTF-16LE", $SID . "\x00"), $user_hash);

    my $hmacSalt = randbytes (16);
    my $last_key = randbytes (64);

    if ($version == 1)
    {
      $encKey        = hmac_sha1 ($hmacSalt, $user_derivationKey);
      $expected_hmac = hmac_sha1 ($last_key, $encKey);

      # need padding because keyLen is 24 and hashLen 20
      $expected_hmac = $expected_hmac . randbytes (4);
    }
    elsif ($version == 2)
    {
      $encKey        = hmac_sha512 ($hmacSalt, $user_derivationKey);
      $expected_hmac = hmac_sha512 ($last_key, $encKey);
    }

    $cleartext = $hmacSalt . $expected_hmac . $last_key;

    my $derived_key;
    my $key;
    my $iv;

    my $pbkdf2;

    if ($version == 1)
    {
      $derived_key = dpapi_pbkdf2 ($user_derivationKey, $salt, $iterations, 32, \&hmac_sha1);
    }
    elsif ($version == 2)
    {
      $derived_key = dpapi_pbkdf2 ($user_derivationKey, $salt, $iterations, 48, \&hmac_sha512);
    }

    if (defined $additional_param)
    {
      $cipher = pack ("H*", $additional_param);
      my $computed_hmac = "";

      if ($version == 1)
      {
        $key = substr ($derived_key,   0, 24);
        $iv  = substr ($derived_key,  24,  8);

        my $p1 = Crypt::ECB->new ({
          key         => substr ($key, 0, 8),
          cipher      => "DES",
          literal_key => 1,
          header      => "none",
          keysize     => 8,
          padding     => "null",
        });

        my $p2 = Crypt::ECB->new ({
          key         => substr ($key, 8, 8),
          cipher      => "DES",
          literal_key => 1,
          header      => "none",
          keysize     => 8,
          padding     => "null",
        });

        my $p3 = Crypt::ECB->new ({
          key         => substr ($key, 16, 8),
          cipher      => "DES",
          literal_key => 1,
          header      => "none",
          keysize     => 8,
          padding     => "null",
        });

        # let's compute a 3DES-EDE-CBC decryption

        my $out1;
        my $out2;
        my $out3;
        my $expected_cleartext = "";

        # size of cipherlen is 104 bytes
        for (my $k = 0; $k < 13; $k++)
        {
          $out1 = $p3->decrypt (substr ($cipher, $k * 8, 8));
          $out2 = $p2->encrypt ($out1);
          $out3 = $p1->decrypt ($out2);

          $expected_cleartext .= substr ($out3, 0, 8) ^ $iv;

          $iv = substr ($cipher, $k * 8, 8);
        }

        $last_key      = substr ($expected_cleartext,  length ($expected_cleartext) - 64, 64);
        $hmacSalt      = substr ($expected_cleartext, 0, 16);
        $expected_hmac = substr ($expected_cleartext, 16, 20);

        $encKey        = hmac_sha1 ($hmacSalt, $user_derivationKey);
        $computed_hmac = hmac_sha1 ($last_key, $encKey);

        $cleartext = $expected_cleartext;

        if (unpack ("H*", $expected_hmac) ne unpack ("H*", $computed_hmac))
        {
          $cleartext = "0" x 104;
        }
      }
      elsif ($version == 2)
      {
        $key = substr ($derived_key,  0, 32);
        $iv  = substr ($derived_key, 32, 16);

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

        my $expected_cleartext = $aes->decrypt ($cipher);

        $last_key      = substr ($expected_cleartext,  length ($expected_cleartext) - 64, 64);
        $hmacSalt      = substr ($expected_cleartext, 0, 16);
        $expected_hmac = substr ($expected_cleartext, 16, 64);

        $encKey        = hmac_sha512 ($hmacSalt, $user_derivationKey);
        $computed_hmac = hmac_sha512 ($last_key, $encKey);

        $cleartext = $expected_cleartext;

        if (unpack ("H*", $expected_hmac) ne unpack ("H*", $computed_hmac))
        {
          $cleartext = "0" x 144;
        }
      }
    }

    if ($version == 1)
    {
      $key = substr ($derived_key,   0, 24);
      $iv  = substr ($derived_key,  24,  8);

      my $p1 = Crypt::ECB->new ({
        key         => substr ($key, 0, 8),
        cipher      => "DES",
        literal_key => 1,
        header      => "none",
        keysize     => 8,
        padding     => "null",
      });

      my $p2 = Crypt::ECB->new ({
        key         => substr ($key, 8, 8),
        cipher      => "DES",
        literal_key => 1,
        header      => "none",
        keysize     => 8,
        padding     => "null",
      });

      my $p3 = Crypt::ECB->new ({
        key         => substr ($key, 16, 8),
        cipher      => "DES",
        literal_key => 1,
        header      => "none",
        keysize     => 8,
        padding     => "null",
      });

      # let's compute a 3DES-EDE-CBC encryption

      # compute first block
      my $out1 = $p1->encrypt (substr ($cleartext, 0, 8) ^ $iv);
      my $out2 = $p2->decrypt ($out1);
      my $out3 = $p3->encrypt ($out2);

      $cipher = substr ($out3, 0, 8);

      # size of cipherlen is 104 bytes
      for (my $k = 1; $k < 13; $k++)
      {
        $iv = $out3;

        $out1 = $p1->encrypt (substr ($cleartext, $k * 8, 8) ^ $iv);
        $out2 = $p2->decrypt ($out1);
        $out3 = $p3->encrypt ($out2);

        $cipher .= substr ($out3, 0, 8);
      }
    }
    else
    {
      $key = substr ($derived_key,  0, 32);
      $iv  = substr ($derived_key, 32, 16);

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

      $cipher = $aes->encrypt ($cleartext);
    }

    $tmp_hash = sprintf ('$DPAPImk$%d*%d*%s*%s*%s*%d*%s*%d*%s',
                 $version,
                 $context,
                 $SID,
                 $cipher_algorithm,
                 $hash_algorithm,
                 $iterations,
                 unpack ("H*", $salt),
                 $cipher_len,
                 unpack ("H*", $cipher));
  }
  elsif ($mode == 15400)
  {
    my $counter;
    my $offset;
    my $iv;

    if (defined $additional_param)
    {
      $counter = $additional_param;
      $offset  = $additional_param2;
      $iv      = $additional_param3;
    }
    else
    {
      $counter = "0400000000000003";
      $offset  = int (rand (63));
      $iv      = "0200000000000001";
    }

    my $plaintext = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789abcdefghijklmnopqrstuvwxyz0a2b4c6d8e";
    my $eight_byte_iv = pack ("H*", $iv);
    my $eight_byte_counter = pack ("H*", $counter);
    my $pad_len = 32 - length ($word_buf);
    my $key = $word_buf . "\0" x $pad_len;

    my $cipher = Crypt::OpenSSH::ChachaPoly->new ($key);

    $cipher->ivsetup ($eight_byte_iv, $eight_byte_counter);

    my $enc = $cipher->encrypt ($plaintext);

    my $enc_offset = substr ($enc, $offset, 8);

    $hash_buf = $enc_offset;

    $tmp_hash = sprintf ("\$chacha20\$\*%s\*%d\*%s\*%s\*%s", $counter, $offset, $iv, unpack ("H*", substr ($plaintext, $offset, 8)), unpack ("H*", $enc_offset));
  }
  elsif ($mode == 15500)
  {
    my $iv = pack ("H*", $salt_buf);

    if (length $additional_param)
    {
      $iv = pack ("H*", $additional_param);
    }

    my $enc_key = randbytes (get_random_num (1, 1500));

    if (length $additional_param2)
    {
      $enc_key = pack ("H*", $additional_param2);
    }

    my $alias = "test";

    if (length $additional_param3)
    {
      $alias = $additional_param3;
    }

    my $word_buf_utf16be = encode ("UTF-16BE", $word_buf);

    my $hash_buf = sha1 ($word_buf_utf16be . $iv);

    my $DER1 = substr ($hash_buf, 0, 1);
    my $DER2 = substr ($hash_buf, 6, 14);

    my @enc_key_data = split "", $enc_key;

    my $enc_key_data_length = scalar @enc_key_data;

    my @key_data = ();

    for (my $i = 0; $i < scalar $enc_key_data_length; $i += 20)
    {
      my @hash_buf_data = split "", $hash_buf;

      for (my $j = 0; $j < 20; $j++)
      {
        last if (($i + $j) >= $enc_key_data_length);

        $key_data[$i + $j] = $enc_key_data[$i + $j] ^ $hash_buf_data[$j];
      }

      $hash_buf = sha1 ($word_buf_utf16be . $hash_buf);
    }

    my $key = join "", @key_data;

    $hash_buf = sha1 ($word_buf_utf16be . $key);

    $tmp_hash = sprintf ("\$jksprivk\$*%s*%s*%s*%s*%s*%s", uc unpack ("H*", $hash_buf), uc unpack ("H*", $iv), uc unpack ("H*", $enc_key), uc unpack ("H*", $DER1), uc unpack ("H*", $DER2), $alias);
  }
  elsif ($mode == 15600)
  {
    my $iterations;
    my $ciphertext;

    if (defined $additional_param)
    {
      $iterations = $iter;
      $ciphertext = $additional_param;
    }
    else
    {
      $iterations = 1024; # 262144 originally
      $ciphertext = randbytes (32);
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      out_len    => 32
    );

    my $derived_key = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

    my $derived_key_cropped = substr ($derived_key, 16, 16);

    $hash_buf = keccak_256_hex ($derived_key_cropped . $ciphertext);

    $tmp_hash = sprintf ("\$ethereum\$p*%i*%s*%s*%s", $iterations, unpack ("H*", $salt_buf), unpack ("H*", $ciphertext), $hash_buf);
  }
  elsif ($mode == 15700)
  {
    my $scrypt_N;
    my $scrypt_r;
    my $scrypt_p;

    my $ciphertext;

    if (defined $additional_param)
    {
      $scrypt_N = $additional_param;
      $scrypt_r = $additional_param2;
      $scrypt_p = $additional_param3;
      $ciphertext = $additional_param4;
    }
    else
    {
      $scrypt_N = 1024; # 262144 originally
      $scrypt_r = 1;    # 8 originally
      $scrypt_p = 1;
      $ciphertext = randbytes (32);
    }

    my $derived_key = scrypt_raw ($word_buf, $salt_buf, $scrypt_N, $scrypt_r, $scrypt_p, 32);

    my $derived_key_cropped = substr ($derived_key, 16, 16);

    $hash_buf = keccak_256_hex ($derived_key_cropped . $ciphertext);

    $tmp_hash = sprintf ("\$ethereum\$s*%i*%i*%i*%s*%s*%s", $scrypt_N, $scrypt_r, $scrypt_p, unpack ("H*", $salt_buf), unpack ("H*", $ciphertext), $hash_buf);
  }
  elsif ($mode == 16000)
  {
    my $converter = Text::Iconv->new ("utf-8", "shift-jis");

    $word_buf = $converter->convert ($word_buf);

    $salt_buf = substr ($word_buf . '..', 1, 2);

    $salt_buf =~ s/[^\.-z]/\./go;

    $salt_buf =~ tr/:;<=>?@[\\]^_`/A-Ga-f/;

    $hash_buf = crypt ($word_buf, $salt_buf);

    $hash_buf = substr ($hash_buf, -10);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 16100)
  {
    my $session_id;
    my $encrypted_data;
    my $sequence;

    if (defined $additional_param)
    {
      $session_id = pack ("H*", $additional_param);
    }
    else
    {
      $session_id = pack ("H*", randbytes (8));
    }

    if (defined $additional_param2)
    {
      $encrypted_data = pack ("H*", $additional_param2);
    }

    if (defined $additional_param3)
    {
      $sequence = pack ("H*", $additional_param3);
    }
    else
    {
      $sequence = pack ("H*", "c006");
    }

    my $key = md5 ($session_id . $word_buf . $sequence);

    if (defined $encrypted_data)
    {
      ## verify case

      my $encrypted_data_len = length $encrypted_data;

      my $plain_data = substr ($encrypted_data, 0, 6) ^ substr ($key, 0, 6);

      my ($status, $flags, $server_msg_len, $data_len) = unpack ("CCnn", $plain_data);

      if ((($status >= 0x01 && $status <= 0x07) || $status == 0x21)
       &&  ($flags  == 0x01 || $flags  == 0x00)
       &&  (6 + $server_msg_len + $data_len == $encrypted_data_len))
      {
        ## ok
      }
      else
      {
        $encrypted_data = ""; # some invalid data
      }
    }
    else
    {
      my $plain_data = "\x01\x00\x00\x00\x00\x00";

      my $plain_data_len = length $plain_data;

      my $shortest = ($plain_data_len > 16) ? 16 : $plain_data_len;

      $encrypted_data = substr ($plain_data, 0, $shortest) ^ substr ($key, 0, $shortest);
    }

    $tmp_hash = sprintf ('$tacacs-plus$0$%s$%s$%s', unpack ("H*", $session_id), unpack ("H*", $encrypted_data), unpack ("H*", $sequence));
  }
  elsif ($mode == 16200)
  {
    my $salt_bin = pack ("H*", $salt_buf);

    my $iterations = 20000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $Z_PK = 1;

    if (defined $additional_param)
    {
      $Z_PK = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => 16,
    );

    my $KEK = $pbkdf2->PBKDF2 ($salt_bin, $word_buf);

    my $aes = Crypt::Mode::ECB->new ('AES', 0);

    my $blob_bin;

    my $A;
    my $B;
    my $P1;
    my $P2;

    if (defined $additional_param2)
    {
      $blob_bin = pack ("H*", $additional_param2);

      $A  = substr ($blob_bin,  0, 8);
      $P1 = substr ($blob_bin,  8, 8);
      $P2 = substr ($blob_bin, 16, 8);

      for (my $j = 5; $j >= 0; $j--)
      {
        # N = 2

        $B  = $A;
        $B ^= pack ("Q>", (2 * $j + 2));
        $B .= $P2;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P2 = substr ($B, 8, 8);

        # N = 1

        $B  = $A;
        $B ^= pack ("Q>", (2 * $j + 1));
        $B .= $P1;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P1 = substr ($B, 8, 8);
      }

      if ($A eq "\xa6" x 8)
      {
        for (my $j = 0; $j <= 5; $j++)
        {
          # N = 1

          $B  = $A;
          $B .= $P1;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (2 * $j + 1));
          $P1 = substr ($B, 8, 8);

          # N = 2

          $B  = $A;
          $B .= $P2;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (2 * $j + 2));
          $P2 = substr ($B, 8, 8);
        }

        $blob_bin = $A . $P1 . $P2;
      }
      else
      {
        $blob_bin = "\xff" x 24;
      }
    }
    else
    {
      $A  = "\xa6" x 8;
      $P1 = "\xff" x 8;
      $P2 = "\xff" x 8;

      for (my $j = 0; $j <= 5; $j++)
      {
        # N = 1

        $B  = $A;
        $B .= $P1;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (2 * $j + 1));
        $P1 = substr ($B, 8, 8);

        # N = 2

        $B  = $A;
        $B .= $P2;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (2 * $j + 2));
        $P2 = substr ($B, 8, 8);
      }

      $blob_bin = $A . $P1 . $P2;
    }

    $tmp_hash = sprintf ('$ASN$*%d*%d*%s*%s', $Z_PK, $iterations, unpack ("H*", $salt_bin), unpack ("H*", $blob_bin));
  }
  elsif ($mode == 16300)
  {
    my $ethaddr = $salt_buf;

    my $iv      = "";
    my $seed    = "";
    my $encseed = "";

    # setup pbkdf2 params:

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => 2000,
      output_len => 16
    );

    my $key = $pbkdf2->PBKDF2 ($word_buf, $word_buf);

    if (defined $additional_param)
    {
      $iv      = substr ($additional_param, 0, 16);
      $encseed = substr ($additional_param, 16);

      # AES-128-CBC decrypt:

      my $aes_cbc = Crypt::CBC->new ({
        key         => $key,
        cipher      => "Crypt::Rijndael",
        iv          => $iv,
        literal_key => 1,
        header      => "none",
        keysize     => 16
      });

      $seed = $aes_cbc->decrypt ($encseed);
    }
    else
    {
      $iv   = randbytes (16);
      $seed = randbytes (592);

      # AES-128-CBC encrypt:

      my $aes_cbc = Crypt::CBC->new ({
        key         => $key,
        cipher      => "Crypt::Rijndael",
        iv          => $iv,
        literal_key => 1,
        header      => "none",
        keysize     => 16
      });

      $encseed = $aes_cbc->encrypt ($seed);
    }

    $hash_buf = keccak_256_hex ($seed . "\x02");

    $tmp_hash = sprintf ("\$ethereum\$w*%s*%s*%s", unpack ("H*", $iv . $encseed), $ethaddr, substr ($hash_buf, 0, 32));
  }
  elsif ($mode == 16400)
  {
    my $md5 = Digest::Perl::MD5->new;
    my $length = length($word_buf);

    $md5->{_data} = $word_buf ^ ("\x5c" x $length);
    $md5->{_data} .= "\x5c" x (64 - $length);
    $md5->add();

    $hash_buf = unpack("H*", pack('V4', @{$md5->{_state}}));
    $tmp_hash = sprintf ("{CRAM-MD5}%s00000000000000000000000000000000", $hash_buf);
  }
  elsif ($mode == 16500)
  {
    my ($header_base64) = split (/\./, $salt_buf);

    my $header_jwt = decode_base64url ($header_base64);

    my $header = decode_json ($header_jwt);

    my $alg = $header->{"alg"};

    if ($alg eq "HS256")
    {
      $hash_buf = hmac ($salt_buf, $word_buf, \&sha256, 64);
    }
    elsif ($alg eq "HS384")
    {
      $hash_buf = hmac ($salt_buf, $word_buf, \&sha384, 128);
    }
    elsif ($alg eq "HS512")
    {
      $hash_buf = hmac ($salt_buf, $word_buf, \&sha512, 128);
    }
    else
    {
      die "not supported hash\n";
    }

    $tmp_hash = sprintf ("%s.%s", $salt_buf, encode_base64url ($hash_buf, ""));
  }
  elsif ($mode == 16600)
  {
    my $key_bin = sha256 (sha256 ($word_buf));

    my $salt_type;

    if (defined $additional_param)
    {
      $salt_type = $additional_param;

      if ($salt_type ne "1") { die "currently only salt_type 1 supported\n"; }
    }
    else
    {
      $salt_type = 1;
    }

    my $iv;

    if (defined $additional_param2)
    {
      $iv = $additional_param2;
    }
    else
    {
      $iv = substr ($salt_buf, 0, 32);
    }

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

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

    my $plain_bin;

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

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

      if ($test =~ /^[0-9a-f]+$/)
      {
        $plain_bin = $test;
      }
      else
      {
        $plain_bin = "\xff" x 16;
      }
    }
    else
    {
      my $plain = "30313233343536373839616263646566";

      $plain_bin = pack ("H*", $plain);
    }

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

    my $encrypted = unpack ("H*", $encrypted_bin);

    $tmp_hash = sprintf ("\$electrum\$%d*%s*%s", $salt_type, $iv, $encrypted);
  }
  elsif ($mode == 16700)
  {
    my $salt_bin = pack ("H*", $salt_buf);

    my $iterations = 20000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $Z_PK = 1;

    if (defined $additional_param)
    {
      $Z_PK = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => 16,
    );

    my $KEK = $pbkdf2->PBKDF2 ($salt_bin, $word_buf);

    my $aes = Crypt::Mode::ECB->new ('AES', 0);

    my $blob_bin;

    my $A;
    my $B;
    my $P1;
    my $P2;

    if (defined $additional_param2)
    {
      $blob_bin = pack ("H*", $additional_param2);

      $A  = substr ($blob_bin,  0, 8);
      $P1 = substr ($blob_bin,  8, 8);
      $P2 = substr ($blob_bin, 16, 8);

      for (my $j = 5; $j >= 0; $j--)
      {
        # N = 2

        $B  = $A;
        $B ^= pack ("Q>", (2 * $j + 2));
        $B .= $P2;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P2 = substr ($B, 8, 8);

        # N = 1

        $B  = $A;
        $B ^= pack ("Q>", (2 * $j + 1));
        $B .= $P1;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P1 = substr ($B, 8, 8);
      }

      if ($A eq "\xa6" x 8)
      {
        for (my $j = 0; $j <= 5; $j++)
        {
          # N = 1

          $B  = $A;
          $B .= $P1;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (2 * $j + 1));
          $P1 = substr ($B, 8, 8);

          # N = 2

          $B  = $A;
          $B .= $P2;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (2 * $j + 2));
          $P2 = substr ($B, 8, 8);
        }

        $blob_bin = $A . $P1 . $P2;
      }
      else
      {
        $blob_bin = "\xff" x 24;
      }
    }
    else
    {
      $A  = "\xa6" x 8;
      $P1 = "\xff" x 8;
      $P2 = "\xff" x 8;

      for (my $j = 0; $j <= 5; $j++)
      {
        # N = 1

        $B  = $A;
        $B .= $P1;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (2 * $j + 1));
        $P1 = substr ($B, 8, 8);

        # N = 2

        $B  = $A;
        $B .= $P2;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (2 * $j + 2));
        $P2 = substr ($B, 8, 8);
      }

      $blob_bin = $A . $P1 . $P2;
    }

    $tmp_hash = sprintf ('$fvde$%d$%d$%s$%d$%s', $Z_PK, length ($salt_bin), unpack ("H*", $salt_bin), $iterations, unpack ("H*", $blob_bin));
  }
  elsif ($mode == 16800)
  {
    my $macap;
    my $macsta;
    my $essid;

    if (!defined ($additional_param))
    {
      $macap = unpack ("H*", randbytes (6));
    }
    else
    {
      $macap = $additional_param;
    }

    if (!defined ($additional_param2))
    {
      $macsta = unpack ("H*", randbytes (6));
    }
    else
    {
      $macsta = $additional_param2;
    }

    if (!defined ($additional_param3))
    {
      $essid = unpack ("H*", randbytes (get_random_num (8, 32) & 0x1e));
    }
    else
    {
      $essid = $additional_param3;
    }

    # generate the Pairwise Master Key (PMK)

    my $iterations = 4096;

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

    my $essid_bin = pack ("H*", $essid);

    my $pmk = $pbkdf2->PBKDF2 ($essid_bin, $word_buf);

    my $macap_bin  = pack ("H*", $macap);
    my $macsta_bin = pack ("H*", $macsta);

    my $data = "PMK Name" . $macap_bin . $macsta_bin;

    my $pmkid = hmac_hex ($data, $pmk, \&sha1);

    $tmp_hash = sprintf ("%s*%s*%s*%s", substr ($pmkid, 0, 32), $macap, $macsta, $essid);
  }
  elsif ($mode == 17300)
  {
    $hash_buf = sha3_224_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 17400)
  {
    $hash_buf = sha3_256_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 17500)
  {
    $hash_buf = sha3_384_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 17600)
  {
    $hash_buf = sha3_512_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 17700)
  {
    $hash_buf = keccak_224_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 17800)
  {
    $hash_buf = keccak_256_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 17900)
  {
    $hash_buf = keccak_384_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 18000)
  {
    $hash_buf = keccak_512_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_buf);
  }
  elsif ($mode == 18100)
  {
    my $paddedTime = sprintf ("%016x", int (int ($salt_buf) / 30));
    my $data = pack ('H*', $paddedTime);
    my $key = $word_buf;

    $hash_buf = hmac_hex ($data, $key, \&sha1, 64);

    my $offset = hex (substr ($hash_buf, -8)) & 0xf;
    $offset *= 2;
    my $token = hex (substr ($hash_buf, $offset, 8));
    $token &= 0x7fffffff;
    $token %= 1000000;

    ## token must be leading zero padded, and salt leading zero stripped
    $tmp_hash = sprintf ("%06d:%d", $token, int ($salt_buf));
  }
  elsif ($mode == 18200)
  {
    my @salt_arr = split (':', $salt_buf);

    my $user_principal_name = $salt_arr[0];

    my $k = md4 (encode ("UTF-16LE", $word_buf));

    my $k1 = hmac_md5 ("\x08\x00\x00\x00", $k);

    my $cleartext_ticket = '7981df3081dca01b3019a003020117a112041071e026814da2' .
    '3f129f0e67a01b73f79aa11c301a3018a003020100a111180f32303138313033303039353' .
    '831365aa206020460fdc6caa311180f32303337303931343032343830355aa40703050050' .
    'c10000a511180f32303138313033303039353831365aa611180f323031383130333030393' .
    '53831365aa711180f32303138313033303139353831365aa811180f323031383130333131' .
    '30303433385aa90d1b0b545952454c4c2e434f5250aa20301ea003020101a11730151b066' .
    'b72627467741b0b545952454c4c2e434f5250';
    my $checksum = "";

    if (defined $additional_param)
    {
      $checksum = pack ("H*", $additional_param);
    }
    else
    {
      my $nonce = $salt_arr[1];

      $cleartext_ticket = $nonce . $cleartext_ticket;

      $checksum = hmac_md5 (pack ("H*", $cleartext_ticket), $k1);
    }

    my $k3 = hmac_md5 ($checksum, $k1);

    my $edata2 = "";

    if (defined $additional_param2)
    {
      $edata2 = $additional_param2;

      my $cipher_decrypt = Crypt::RC4->new ($k3);

      my $ticket_decrypt = unpack ("H*", $cipher_decrypt->RC4 (pack ("H*", $edata2)));

      my $check_correct  = ((substr ($ticket_decrypt, 16, 4) eq "7981" && substr ($ticket_decrypt, 22, 2) eq "30")) ||
                           ((substr ($ticket_decrypt, 16, 2) eq "79") && (substr ($ticket_decrypt, 20, 2) eq "30")) ||
                           ((substr ($ticket_decrypt, 16, 4) eq "7982")  && (substr ($ticket_decrypt, 24, 2) eq "30"));

      if ($check_correct == 1)
      {
        $cleartext_ticket = $ticket_decrypt;
      }
      else # validation failed
      {
        # fake/wrong ticket (otherwise if we just decrypt/encrypt we end up with false positives all the time)
        $cleartext_ticket = "0" x (length ($cleartext_ticket) + 16);
      }
    }

    my $cipher = Crypt::RC4->new ($k3);

    $edata2 = $cipher->RC4 (pack ("H*", $cleartext_ticket));

    $tmp_hash = sprintf ('$krb5asrep$23$%s:%s$%s', $user_principal_name, unpack ("H*", $checksum), unpack ("H*", $edata2));
  }
  elsif ($mode == 18300)
  {
    my $salt_bin = pack ("H*", $salt_buf);

    my $iterations = 20000;

    if (defined ($iter))
    {
      $iterations = $iter;
    }

    my $Z_PK = 2;

    if (defined $additional_param)
    {
      $Z_PK = $additional_param;
    }

    my $pbkdf2 = Crypt::PBKDF2->new
    (
      hasher     => Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256),
      iterations => $iterations,
      output_len => 32,
    );

    my $KEK = $pbkdf2->PBKDF2 ($salt_bin, $word_buf);

    my $aes = Crypt::Mode::ECB->new ('AES', 0);

    my $blob_bin;

    my $A;
    my $B;
    my $P1;
    my $P2;
    my $P3;
    my $P4;

    if (defined $additional_param2)
    {
      $blob_bin = pack ("H*", $additional_param2);

      $A  = substr ($blob_bin,  0, 8);
      $P1 = substr ($blob_bin,  8, 8);
      $P2 = substr ($blob_bin, 16, 8);
      $P3 = substr ($blob_bin, 24, 8);
      $P4 = substr ($blob_bin, 32, 8);

      for (my $j = 5; $j >= 0; $j--)
      {
        # N = 4

        $B  = $A;
        $B ^= pack ("Q>", (4 * $j + 4));
        $B .= $P4;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P4 = substr ($B, 8, 8);

        # N = 3

        $B  = $A;
        $B ^= pack ("Q>", (4 * $j + 3));
        $B .= $P3;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P3 = substr ($B, 8, 8);

        # N = 2

        $B  = $A;
        $B ^= pack ("Q>", (4 * $j + 2));
        $B .= $P2;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P2 = substr ($B, 8, 8);

        # N = 1

        $B  = $A;
        $B ^= pack ("Q>", (4 * $j + 1));
        $B .= $P1;
        $B  = $aes->decrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $P1 = substr ($B, 8, 8);
      }

      if ($A eq "\xa6" x 8)
      {
        for (my $j = 0; $j <= 5; $j++)
        {
          # N = 1

          $B  = $A;
          $B .= $P1;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (4 * $j + 1));
          $P1 = substr ($B, 8, 8);

          # N = 2

          $B  = $A;
          $B .= $P2;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (4 * $j + 2));
          $P2 = substr ($B, 8, 8);

          # N = 3

          $B  = $A;
          $B .= $P3;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (4 * $j + 3));
          $P3 = substr ($B, 8, 8);

          # N = 4

          $B  = $A;
          $B .= $P4;
          $B  = $aes->encrypt ($B, $KEK);
          $A  = substr ($B, 0, 8);
          $A ^= pack ("Q>", (4 * $j + 4));
          $P4 = substr ($B, 8, 8);
        }

        $blob_bin = $A . $P1 . $P2 . $P3 . $P4;
      }
      else
      {
        $blob_bin = "\xff" x 40;
      }
    }
    else
    {
      $A  = "\xa6" x 8;
      $P1 = "\xff" x 8;
      $P2 = "\xff" x 8;
      $P3 = "\xff" x 8;
      $P4 = "\xff" x 8;

      for (my $j = 0; $j <= 5; $j++)
      {
        # N = 1

        $B  = $A;
        $B .= $P1;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (4 * $j + 1));
        $P1 = substr ($B, 8, 8);

        # N = 2

        $B  = $A;
        $B .= $P2;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (4 * $j + 2));
        $P2 = substr ($B, 8, 8);

        # N = 3

        $B  = $A;
        $B .= $P3;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (4 * $j + 3));
        $P3 = substr ($B, 8, 8);

        # N = 4

        $B  = $A;
        $B .= $P4;
        $B  = $aes->encrypt ($B, $KEK);
        $A  = substr ($B, 0, 8);
        $A ^= pack ("Q>", (4 * $j + 4));
        $P4 = substr ($B, 8, 8);
      }

      $blob_bin = $A . $P1 . $P2 . $P3 . $P4;
    }

    $tmp_hash = sprintf ('$fvde$%d$%d$%s$%d$%s', $Z_PK, length ($salt_bin), unpack ("H*", $salt_bin), $iterations, unpack ("H*", $blob_bin));
  }
  elsif ($mode == 99999)
  {
    $tmp_hash = sprintf ("%s", $word_buf);
  }

  return ($tmp_hash);
}

#Thanks to Jochen Hoenicke <hoenicke@gmail.com>
# (one of the authors of Palm Keyring)
# for these next two subs.
sub dpapi_pbkdf2
{
    my ($password, $salt, $iter, $keylen, $prf) = @_;
    my ($k, $t, $u, $ui, $i);
    $t = "";
    for ($k = 1; length ($t) <  $keylen; $k++)
    {
      $u = $ui = &$prf ($salt.pack ('N', $k), $password);
      for ($i = 1; $i < $iter; $i++)
      {
        # modification to fit Microsoft
        # weird pbkdf2 implementation...
        $ui = &$prf ($u, $password);
        $u ^= $ui;
      }
      $t .= $u;
    }
    return substr ($t, 0, $keylen);
}

## STEP 4: Add custom traits here (optional).
sub rnd
{
  my $mode = shift;

  my $word_len = shift;

  my $salt_len = shift;

  my $max = $MAX_LEN;

  if ($mode == 2410)
  {
    $salt_len = min ($salt_len, 4);
  }

  if (is_in_array ($mode, $IS_UTF16LE))
  {
    if (is_in_array ($mode, $ALLOW_LONG_SALT))
    {
      $word_len = min ($word_len, int ($max / 2));
    }
    else
    {
      $word_len = min ($word_len, int ($max / 2) - $salt_len);
    }
  }
  elsif (is_in_array ($mode, $LESS_FIFTEEN))
  {
    $word_len = min ($word_len, 15);
  }
  else
  {
    if (! is_in_array ($mode, $ALLOW_LONG_SALT))
    {
      $word_len = min ($word_len, $max - $salt_len);
    }
  }

  if ($word_len < 1)
  {
    $word_len = 1;
  }

  ##
  ## gen salt
  ##

  my $salt_buf;

  if ($mode == 4800)
  {
    my @salt_arr;

    for (my $i = 0; $i < $salt_len; $i++)
    {
      my $c = get_random_chr (0x30, 0x39);

      push (@salt_arr, $c);
    }

    $salt_buf = join ("", @salt_arr);

    $salt_buf = get_random_md5chap_salt ($salt_buf);
  }
  elsif ($mode == 5300 || $mode == 5400)
  {
    $salt_buf = get_random_ike_salt ();
  }
  elsif ($mode == 5500)
  {
    $salt_buf = get_random_netntlmv1_salt ($salt_len, $salt_len);
  }
  elsif ($mode == 5600)
  {
    $salt_buf = get_random_netntlmv2_salt ($salt_len, $salt_len);
  }
  elsif ($mode == 6600)
  {
    $salt_buf = get_random_agilekeychain_salt ();
  }
  elsif ($mode == 8200)
  {
    $salt_buf = get_random_cloudkeychain_salt ();
  }
  elsif ($mode == 8300)
  {
    $salt_buf = get_random_dnssec_salt ();
  }
  elsif ($mode == 13100)
  {
    $salt_buf = get_random_kerberos5_tgs_salt ();
  }
  elsif ($mode == 13200)
  {
    $salt_buf = get_random_axcrypt_salt ();
  }
  elsif ($mode == 13400)
  {
    $salt_buf = get_random_keepass_salt ();
  }
  elsif ($mode == 13500)
  {
    $salt_buf = get_pstoken_salt ();
  }
  elsif ($mode == 15300 || $mode == 15900)
  {
    my $version = 2;

    if ($mode == 15300)
    {
      $version = 1;
    }

    $salt_buf = get_random_dpapimk_salt ($version);
  }
  elsif ($mode == 16500)
  {
    $salt_buf = get_random_jwt_salt ();
  }
  elsif ($mode == 18200)
  {
    $salt_buf = get_random_kerberos5_as_rep_salt ();
  }
  else
  {
    my @salt_arr;

    for (my $i = 0; $i < $salt_len; $i++)
    {
      my $c = get_random_chr (0x30, 0x39);

      push (@salt_arr, $c);
    }

    $salt_buf = join ("", @salt_arr);

    if ($mode == 7500)
    {
      $salt_buf = get_random_kerberos5_salt ($salt_buf);
    }
  }

  ##
  ## gen plain
  ##

  my @word_arr;

  for (my $i = 0; $i < $word_len; $i++)
  {
    my $c = get_random_chr (0x30, 0x39);

    if (($mode == 14000) || ($mode == 14100))
    {
      $c &= 0xfe;
    }

    push (@word_arr, $c);
  }

  my $word_buf = join ("", @word_arr);

  ##
  ## gen hash
  ##

  my $tmp_hash = gen_hash ($mode, $word_buf, $salt_buf);

  ##
  ## run
  ##

  my @cmd =
  (
    $hashcat,
    "-a 0 -m", $mode,
    $tmp_hash
  );

  print sprintf ("echo -n %-20s | %s \${OPTS} %s %4d '%s'\n", $word_buf, @cmd);
}

##
## subs
##

sub min
{
  $_[$_[0] > $_[1]];
}

sub get_random_string
{
  my $len = shift;

  my @arr;

  for (my $i = 0; $i < $len; $i++)
  {
    my $c = get_random_chr (0x30, 0x39);

    push (@arr, $c);
  }

  my $buf = join ("", @arr);

  return $buf;
}

sub get_random_num
{
  my $min = shift;
  my $max = shift;

  return int ((rand ($max - $min)) + $min);
}

sub get_random_chr
{
  return chr get_random_num (@_);
}

sub domino_decode
{
  my $str = shift;

  my $decoded  = "";

  for (my $i = 0; $i < length ($str); $i += 4)
  {
    my $num = domino_base64_decode (substr ($str, $i, 4), 4);

    $decoded .= chr (($num >> 16) & 0xff) . chr (($num >> 8) & 0xff) . chr ($num & 0xff);
  }

  my $salt;
  my $digest;
  my $char;

  $salt   = substr ($decoded,  0, 5);

  my $byte10 = (ord (substr ($salt, 3, 1)) - 4);

  if ($byte10 < 0)
  {
    $byte10 = 256 + $byte10;
  }

  substr ($salt, 3, 1) = chr ($byte10);

  $digest = substr ($decoded,  5, 9);
  $char   = substr ($str,     18, 1);

  return ($digest, $salt, $char);
}

sub domino_85x_decode
{
  my $str = shift;

  my $decoded  = "";

  for (my $i = 0; $i < length ($str); $i += 4)
  {
    my $num = domino_base64_decode (substr ($str, $i, 4), 4);

    $decoded .= chr (($num >> 16) & 0xff) . chr (($num >> 8) & 0xff) . chr ($num & 0xff);
  }

  my $digest;
  my $salt;
  my $iterations = -1;
  my $chars;

  $salt   = substr ($decoded,  0, 16);  # longer than -m 8700 (5 vs 16 <- new)

  my $byte10 = (ord (substr ($salt, 3, 1)) - 4);

  if ($byte10 < 0)
  {
    $byte10 = 256 + $byte10;
  }

  substr ($salt, 3, 1) = chr ($byte10);

  $iterations = substr ($decoded,  16, 10);

  if ($iterations =~ /^?d*$/)
  {
    # continue

    $iterations = $iterations + 0;            # hack: make sure it is an int now (atoi ())
    $chars = substr ($decoded, 26, 2);        # in my example it is "02"
    $digest = substr ($decoded, 28, 8);       # only of length of 8 vs 20 SHA1 bytes
  }

  return ($digest, $salt, $iterations, $chars);
}

sub domino_base64_decode
{
  my $v = shift;
  my $n = shift;

  my $itoa64 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";

  my $ret = 0;

  my $i = 1;

  while ($i <= $n)
  {
    my $idx = (index ($itoa64, substr ($v, $n - $i, 1))) & 0x3f;

    $ret += ($idx << (6 * ($i - 1)));

    $i = $i + 1;
  }

  return $ret
}

sub domino_encode
{
  my $final = shift;
  my $char  = shift;

  my $byte10 = (ord (substr ($final, 3, 1)) + 4);

  if ($byte10 > 255)
  {
    $byte10 = $byte10 - 256;
  }

  substr ($final, 3, 1) = chr ($byte10);

  my $passwd = "";

  $passwd .= domino_base64_encode ((int (ord (substr ($final,  0, 1))) << 16) | (int (ord (substr ($final,  1, 1))) << 8) | (int (ord (substr ($final,  2, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final,  3, 1))) << 16) | (int (ord (substr ($final,  4, 1))) << 8) | (int (ord (substr ($final,  5, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final,  6, 1))) << 16) | (int (ord (substr ($final,  7, 1))) << 8) | (int (ord (substr ($final,  8, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final,  9, 1))) << 16) | (int (ord (substr ($final, 10, 1))) << 8) | (int (ord (substr ($final, 11, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 12, 1))) << 16) | (int (ord (substr ($final, 13, 1))) << 8) | (int (ord (substr ($final, 14, 1)))), 4);

  if (defined ($char))
  {
    substr ($passwd, 18, 1) = $char;
  }
  substr ($passwd, 19, 1) = "";

  return $passwd;
}

sub domino_85x_encode
{
  my $final = shift;
  my $char  = shift;

  my $byte10 = (ord (substr ($final, 3, 1)) + 4);

  if ($byte10 > 255)
  {
    $byte10 = $byte10 - 256;
  }

  substr ($final, 3, 1) = chr ($byte10);

  my $passwd = "";

  $passwd .= domino_base64_encode ((int (ord (substr ($final,  0, 1))) << 16) | (int (ord (substr ($final,  1, 1))) << 8) | (int (ord (substr ($final,  2, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final,  3, 1))) << 16) | (int (ord (substr ($final,  4, 1))) << 8) | (int (ord (substr ($final,  5, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final,  6, 1))) << 16) | (int (ord (substr ($final,  7, 1))) << 8) | (int (ord (substr ($final,  8, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final,  9, 1))) << 16) | (int (ord (substr ($final, 10, 1))) << 8) | (int (ord (substr ($final, 11, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 12, 1))) << 16) | (int (ord (substr ($final, 13, 1))) << 8) | (int (ord (substr ($final, 14, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 15, 1))) << 16) | (int (ord (substr ($final, 16, 1))) << 8) | (int (ord (substr ($final, 17, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 18, 1))) << 16) | (int (ord (substr ($final, 19, 1))) << 8) | (int (ord (substr ($final, 20, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 21, 1))) << 16) | (int (ord (substr ($final, 22, 1))) << 8) | (int (ord (substr ($final, 23, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 24, 1))) << 16) | (int (ord (substr ($final, 25, 1))) << 8) | (int (ord (substr ($final, 26, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 27, 1))) << 16) | (int (ord (substr ($final, 28, 1))) << 8) | (int (ord (substr ($final, 29, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 30, 1))) << 16) | (int (ord (substr ($final, 31, 1))) << 8) | (int (ord (substr ($final, 32, 1)))), 4);
  $passwd .= domino_base64_encode ((int (ord (substr ($final, 33, 1))) << 16) | (int (ord (substr ($final, 34, 1))) << 8) | (int (ord (substr ($final, 35, 1)))), 4);

  if (defined ($char))
  {
    substr ($passwd, 18, 1) = $char;
  }

  return $passwd;
}

sub domino_base64_encode
{
  my $v = shift;
  my $n = shift;

  my $itoa64 = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz+/";

  my $ret = "";

  while (($n - 1) >= 0)
  {
    $n = $n - 1;

    $ret = substr ($itoa64, $v & 0x3f, 1) . $ret;

    $v = $v >> 6;
  }

  return $ret
}

sub pseudo_base64
{
  my $itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

  my $md5 = shift;
  my $s64 = "";
  for my $i (0..3) {
      my $v = unpack "V", substr ($md5, $i*4, 4);
      for (1..4) {
          $s64 .= substr ($itoa64, $v & 0x3f, 1);
          $v >>= 6;
      }
  }
  return $s64;
}

sub racf_hash
{
  my ($username, $password) = @_;

  $username = substr ($username . " " x 8, 0, 8);
  $password = substr ($password . " " x 8, 0, 8);

  my $username_ebc = ascii2ebcdic ($username);
  my $password_ebc = ascii2ebcdic ($password);

  my @pw = split ("", $password_ebc);

  for (my $i = 0; $i < 8; $i++)
  {
    $pw[$i] = unpack ("C", $pw[$i]);
    $pw[$i] ^= 0x55;
    $pw[$i] <<= 1;
    $pw[$i] = pack ("C", $pw[$i] & 0xff);
  }

  my $key = join ("", @pw);

  my $cipher = new Crypt::DES $key;

  my $ciphertext = $cipher->encrypt ($username_ebc);

  my $ct = unpack ("H16", $ciphertext);

  return $ct;
}

sub oracle_hash
{
  my ($username, $password) = @_;

  my $userpass = pack ('n*', unpack ('C*', uc ($username.$password)));
  $userpass .= pack ('C', 0) while (length ($userpass) % 8);

  my $key = pack ('H*', "0123456789ABCDEF");
  my $iv = pack ('H*', "0000000000000000");

  my $c = new Crypt::CBC (
    -literal_key => 1,
    -cipher => "DES",
    -key => $key,
    -iv => $iv,
    -header => "none"
  );
  my $key2 = substr ($c->encrypt ($userpass), length ($userpass)-8, 8);

  my $c2 = new Crypt::CBC (
    -literal_key => 1,
    -cipher => "DES",
    -key => $key2,
    -iv => $iv,
    -header => "none"
  );
  my $hash = substr ($c2->encrypt ($userpass), length ($userpass)-8, 8);

  return uc (unpack ('H*', $hash));
}

sub androidpin_hash
{
  my $word_buf = shift;

  my $salt_buf = shift;

  my $w = sprintf ("%d%s%s", 0, $word_buf, $salt_buf);

  my $digest = sha1 ($w);

  for (my $i = 1; $i < 1024; $i++)
  {
    $w = $digest . sprintf ("%d%s%s", $i, $word_buf, $salt_buf);

    $digest = sha1 ($w);
  }

  my ($A, $B, $C, $D, $E) = unpack ("N5", $digest);

  return sprintf ("%08x%08x%08x%08x%08x", $A, $B, $C, $D, $E);
}

sub to64
{
  my $v = shift;
  my $n = shift;

  my $itoa64 = "./0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";

  my $ret = "";

  while (($n - 1) >= 0)
  {
    $n = $n - 1;

    $ret .= substr ($itoa64, $v & 0x3f, 1);

    $v = $v >> 6;
  }

  return $ret
}

sub md5_crypt
{
  my $magic = shift;

  my $iter = shift;
  my $pass = shift;
  my $salt = shift;

  my $hash = ""; # hash to be returned by this function

  my $final = md5 ($pass . $salt . $pass);

  $salt = substr ($salt, 0, 8);

  my $tmp = $pass . $magic . $salt;

  my $pass_len = length ($pass);

  my $i;

  for ($i = $pass_len; $i > 0; $i -= 16)
  {
    my $len = 16;

    if ($i < $len)
    {
      $len = $i;
    }

    $tmp .= substr ($final, 0, $len);
  }

  $i = $pass_len;

  while ($i > 0)
  {
    if ($i & 1)
    {
      $tmp .= chr (0);
    }
    else
    {
      $tmp .= substr ($pass, 0, 1);
    }

    $i >>= 1;
  }

  $final = md5 ($tmp);

  for ($i = 0; $i < $iter; $i++)
  {
    $tmp = "";

    if ($i & 1)
    {
      $tmp .= $pass;
    }
    else
    {
      $tmp .= $final;
    }

    if ($i % 3)
    {
      $tmp .= $salt;
    }

    if ($i % 7)
    {
      $tmp .= $pass;
    }

    if ($i & 1)
    {
      $tmp .= $final;
    }
    else
    {
      $tmp .= $pass;
    }

    $final = md5 ($tmp);
  }

  # done
  # now format the output sting ("hash")

  my $hash_buf;

  $hash  = to64 ((ord (substr ($final, 0, 1)) << 16) | (ord (substr ($final,  6, 1)) << 8) | (ord (substr ($final, 12, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 1, 1)) << 16) | (ord (substr ($final,  7, 1)) << 8) | (ord (substr ($final, 13, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 2, 1)) << 16) | (ord (substr ($final,  8, 1)) << 8) | (ord (substr ($final, 14, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 3, 1)) << 16) | (ord (substr ($final,  9, 1)) << 8) | (ord (substr ($final, 15, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 4, 1)) << 16) | (ord (substr ($final, 10, 1)) << 8) | (ord (substr ($final,  5, 1))), 4);
  $hash .= to64 (ord (substr ($final, 11, 1)), 2);

  if ($iter == 1000) # default
  {
    $hash_buf = sprintf ("%s%s\$%s", $magic , $salt , $hash);
  }
  else
  {
    $hash_buf = sprintf ("%srounds=%i\$%s\$%s", $magic, $iter, $salt , $hash);
  }

  return $hash_buf;
}

sub sha512_crypt
{
  my $iter = shift;
  my $pass = shift;
  my $salt = shift;

  my $hash = ""; # hash to be returned by this function

  my $final = sha512 ($pass . $salt . $pass);

  $salt = substr ($salt, 0, 16);

  my $tmp = $pass . $salt;

  my $pass_len = length ($pass);
  my $salt_len = length ($salt);

  my $i;

  for ($i = $pass_len; $i > 0; $i -= 16)
  {
    my $len = 16;

    if ($i < $len)
    {
      $len = $i;
    }

    $tmp .= substr ($final, 0, $len);
  }

  $i = $pass_len;

  while ($i > 0)
  {
    if ($i & 1)
    {
      $tmp .= $final;
    }
    else
    {
      $tmp .= $pass;
    }

    $i >>= 1;
  }

  $final = sha512 ($tmp);

  # p_bytes

  my $p_bytes = "";

  for ($i = 0; $i < $pass_len; $i++)
  {
    $p_bytes .= $pass;
  }

  $p_bytes = sha512 ($p_bytes);
  $p_bytes = substr ($p_bytes, 0, $pass_len);

  # s_bytes

  my $final_first_byte = ord (substr ($final, 0, 1));

  my $s_bytes = "";

  for ($i = 0; $i < (16 + $final_first_byte); $i++)
  {
    $s_bytes .= $salt;
  }

  $s_bytes = sha512 ($s_bytes);
  $s_bytes = substr ($s_bytes, 0, $salt_len);

  for ($i = 0; $i < $iter; $i++)
  {
    $tmp = "";

    if ($i & 1)
    {
      $tmp .= $p_bytes;
    }
    else
    {
      $tmp .= $final;
    }

    if ($i % 3)
    {
      $tmp .= $s_bytes;
    }

    if ($i % 7)
    {
      $tmp .= $p_bytes;
    }

    if ($i & 1)
    {
      $tmp .= $final;
    }
    else
    {
      $tmp .= $p_bytes;
    }

    $final = sha512 ($tmp);
  }

  # done
  # now format the output string ("hash")

  my $hash_buf;

  $hash .= to64 ((ord (substr ($final,  0, 1)) << 16) | (ord (substr ($final, 21, 1)) << 8) | (ord (substr ($final, 42, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 22, 1)) << 16) | (ord (substr ($final, 43, 1)) << 8) | (ord (substr ($final,  1, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 44, 1)) << 16) | (ord (substr ($final,  2, 1)) << 8) | (ord (substr ($final, 23, 1))), 4);
  $hash .= to64 ((ord (substr ($final,  3, 1)) << 16) | (ord (substr ($final, 24, 1)) << 8) | (ord (substr ($final, 45, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 25, 1)) << 16) | (ord (substr ($final, 46, 1)) << 8) | (ord (substr ($final,  4, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 47, 1)) << 16) | (ord (substr ($final,  5, 1)) << 8) | (ord (substr ($final, 26, 1))), 4);
  $hash .= to64 ((ord (substr ($final,  6, 1)) << 16) | (ord (substr ($final, 27, 1)) << 8) | (ord (substr ($final, 48, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 28, 1)) << 16) | (ord (substr ($final, 49, 1)) << 8) | (ord (substr ($final,  7, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 50, 1)) << 16) | (ord (substr ($final,  8, 1)) << 8) | (ord (substr ($final, 29, 1))), 4);
  $hash .= to64 ((ord (substr ($final,  9, 1)) << 16) | (ord (substr ($final, 30, 1)) << 8) | (ord (substr ($final, 51, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 31, 1)) << 16) | (ord (substr ($final, 52, 1)) << 8) | (ord (substr ($final, 10, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 53, 1)) << 16) | (ord (substr ($final, 11, 1)) << 8) | (ord (substr ($final, 32, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 12, 1)) << 16) | (ord (substr ($final, 33, 1)) << 8) | (ord (substr ($final, 54, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 34, 1)) << 16) | (ord (substr ($final, 55, 1)) << 8) | (ord (substr ($final, 13, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 56, 1)) << 16) | (ord (substr ($final, 14, 1)) << 8) | (ord (substr ($final, 35, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 15, 1)) << 16) | (ord (substr ($final, 36, 1)) << 8) | (ord (substr ($final, 57, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 37, 1)) << 16) | (ord (substr ($final, 58, 1)) << 8) | (ord (substr ($final, 16, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 59, 1)) << 16) | (ord (substr ($final, 17, 1)) << 8) | (ord (substr ($final, 38, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 18, 1)) << 16) | (ord (substr ($final, 39, 1)) << 8) | (ord (substr ($final, 60, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 40, 1)) << 16) | (ord (substr ($final, 61, 1)) << 8) | (ord (substr ($final, 19, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 62, 1)) << 16) | (ord (substr ($final, 20, 1)) << 8) | (ord (substr ($final, 41, 1))), 4);
  $hash .= to64 (ord (substr ($final,  63, 1)), 2);

  my $magic = '$6$';

  if ($iter == 5000) # default
  {
    $hash_buf = sprintf ("%s%s\$%s", $magic, $salt , $hash);
  }
  else
  {
    $hash_buf = sprintf ("%srounds=%i\$%s\$%s", $magic, $iter, $salt , $hash);
  }

  return $hash_buf;
}

sub sha256_crypt
{
  my $iter = shift;
  my $pass = shift;
  my $salt = shift;

  my $hash = ""; # hash to be returned by this function

  my $final = sha256 ($pass . $salt . $pass);

  $salt = substr ($salt, 0, 16);

  my $tmp = $pass . $salt;

  my $pass_len = length ($pass);
  my $salt_len = length ($salt);

  my $i;

  for ($i = $pass_len; $i > 0; $i -= 16)
  {
    my $len = 16;

    if ($i < $len)
    {
      $len = $i;
    }

    $tmp .= substr ($final, 0, $len);
  }

  $i = $pass_len;

  while ($i > 0)
  {
    if ($i & 1)
    {
      $tmp .= $final;
    }
    else
    {
      $tmp .= $pass;
    }

    $i >>= 1;
  }

  $final = sha256 ($tmp);

  # p_bytes

  my $p_bytes = "";

  for ($i = 0; $i < $pass_len; $i++)
  {
    $p_bytes .= $pass;
  }

  $p_bytes = sha256 ($p_bytes);
  $p_bytes = substr ($p_bytes, 0, $pass_len);

  # s_bytes

  my $final_first_byte = ord (substr ($final, 0, 1));

  my $s_bytes = "";

  for ($i = 0; $i < (16 + $final_first_byte); $i++)
  {
    $s_bytes .= $salt;
  }

  $s_bytes = sha256 ($s_bytes);
  $s_bytes = substr ($s_bytes, 0, $salt_len);

  for ($i = 0; $i < $iter; $i++)
  {
    $tmp = "";

    if ($i & 1)
    {
      $tmp .= $p_bytes;
    }
    else
    {
      $tmp .= $final;
    }

    if ($i % 3)
    {
      $tmp .= $s_bytes;
    }

    if ($i % 7)
    {
      $tmp .= $p_bytes;
    }

    if ($i & 1)
    {
      $tmp .= $final;
    }
    else
    {
      $tmp .= $p_bytes;
    }

    $final = sha256 ($tmp);
  }

  # done
  # now format the output string ("hash")

  my $hash_buf;

  $hash .= to64 ((ord (substr ($final,  0, 1)) << 16) | (ord (substr ($final, 10, 1)) << 8) | (ord (substr ($final, 20, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 21, 1)) << 16) | (ord (substr ($final,  1, 1)) << 8) | (ord (substr ($final, 11, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 12, 1)) << 16) | (ord (substr ($final, 22, 1)) << 8) | (ord (substr ($final,  2, 1))), 4);
  $hash .= to64 ((ord (substr ($final,  3, 1)) << 16) | (ord (substr ($final, 13, 1)) << 8) | (ord (substr ($final, 23, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 24, 1)) << 16) | (ord (substr ($final,  4, 1)) << 8) | (ord (substr ($final, 14, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 15, 1)) << 16) | (ord (substr ($final, 25, 1)) << 8) | (ord (substr ($final,  5, 1))), 4);
  $hash .= to64 ((ord (substr ($final,  6, 1)) << 16) | (ord (substr ($final, 16, 1)) << 8) | (ord (substr ($final, 26, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 27, 1)) << 16) | (ord (substr ($final,  7, 1)) << 8) | (ord (substr ($final, 17, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 18, 1)) << 16) | (ord (substr ($final, 28, 1)) << 8) | (ord (substr ($final,  8, 1))), 4);
  $hash .= to64 ((ord (substr ($final,  9, 1)) << 16) | (ord (substr ($final, 19, 1)) << 8) | (ord (substr ($final, 29, 1))), 4);
  $hash .= to64 ((ord (substr ($final, 31, 1)) <<  8) | (ord (substr ($final, 30, 1))), 3);

  my $magic = '$5$';

  if ($iter == 5000) # default
  {
    $hash_buf = sprintf ("%s%s\$%s", $magic, $salt , $hash);
  }
  else
  {
    $hash_buf = sprintf ("%srounds=%i\$%s\$%s", $magic, $iter, $salt , $hash);
  }

  return $hash_buf;
}

sub aix_ssha256_pbkdf2
{
  my $word_buf   = shift;
  my $salt_buf   = shift;
  my $iterations = shift;

  my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 256);

  my $pbkdf2 = Crypt::PBKDF2->new (
    hasher       => $hasher,
    iterations   => $iterations,
    output_len   => 32
  );

  my $hash_buf = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

  my $tmp_hash = "";

  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  0, 1))) << 16) | (int (ord (substr ($hash_buf,  1, 1))) << 8) | (int (ord (substr ($hash_buf,  2, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  3, 1))) << 16) | (int (ord (substr ($hash_buf,  4, 1))) << 8) | (int (ord (substr ($hash_buf,  5, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  6, 1))) << 16) | (int (ord (substr ($hash_buf,  7, 1))) << 8) | (int (ord (substr ($hash_buf,  8, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  9, 1))) << 16) | (int (ord (substr ($hash_buf, 10, 1))) << 8) | (int (ord (substr ($hash_buf, 11, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 12, 1))) << 16) | (int (ord (substr ($hash_buf, 13, 1))) << 8) | (int (ord (substr ($hash_buf, 14, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 15, 1))) << 16) | (int (ord (substr ($hash_buf, 16, 1))) << 8) | (int (ord (substr ($hash_buf, 17, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 18, 1))) << 16) | (int (ord (substr ($hash_buf, 19, 1))) << 8) | (int (ord (substr ($hash_buf, 20, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 21, 1))) << 16) | (int (ord (substr ($hash_buf, 22, 1))) << 8) | (int (ord (substr ($hash_buf, 23, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 24, 1))) << 16) | (int (ord (substr ($hash_buf, 25, 1))) << 8) | (int (ord (substr ($hash_buf, 26, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 27, 1))) << 16) | (int (ord (substr ($hash_buf, 28, 1))) << 8) | (int (ord (substr ($hash_buf, 29, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 30, 1))) << 16) | (int (ord (substr ($hash_buf, 31, 1))) << 8)                                          , 3);

  return $tmp_hash;
}

sub aix_ssha512_pbkdf2
{
  my $word_buf   = shift;
  my $salt_buf   = shift;
  my $iterations = shift;

  my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA2', 512);

  my $pbkdf2 = Crypt::PBKDF2->new (
    hasher       => $hasher,
    iterations   => $iterations,
  );

  my $hash_buf = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

  my $tmp_hash = "";

  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  0, 1))) << 16) | (int (ord (substr ($hash_buf,  1, 1))) << 8) | (int (ord (substr ($hash_buf,  2, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  3, 1))) << 16) | (int (ord (substr ($hash_buf,  4, 1))) << 8) | (int (ord (substr ($hash_buf,  5, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  6, 1))) << 16) | (int (ord (substr ($hash_buf,  7, 1))) << 8) | (int (ord (substr ($hash_buf,  8, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  9, 1))) << 16) | (int (ord (substr ($hash_buf, 10, 1))) << 8) | (int (ord (substr ($hash_buf, 11, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 12, 1))) << 16) | (int (ord (substr ($hash_buf, 13, 1))) << 8) | (int (ord (substr ($hash_buf, 14, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 15, 1))) << 16) | (int (ord (substr ($hash_buf, 16, 1))) << 8) | (int (ord (substr ($hash_buf, 17, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 18, 1))) << 16) | (int (ord (substr ($hash_buf, 19, 1))) << 8) | (int (ord (substr ($hash_buf, 20, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 21, 1))) << 16) | (int (ord (substr ($hash_buf, 22, 1))) << 8) | (int (ord (substr ($hash_buf, 23, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 24, 1))) << 16) | (int (ord (substr ($hash_buf, 25, 1))) << 8) | (int (ord (substr ($hash_buf, 26, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 27, 1))) << 16) | (int (ord (substr ($hash_buf, 28, 1))) << 8) | (int (ord (substr ($hash_buf, 29, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 30, 1))) << 16) | (int (ord (substr ($hash_buf, 31, 1))) << 8) | (int (ord (substr ($hash_buf, 32, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 33, 1))) << 16) | (int (ord (substr ($hash_buf, 34, 1))) << 8) | (int (ord (substr ($hash_buf, 35, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 36, 1))) << 16) | (int (ord (substr ($hash_buf, 37, 1))) << 8) | (int (ord (substr ($hash_buf, 38, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 39, 1))) << 16) | (int (ord (substr ($hash_buf, 40, 1))) << 8) | (int (ord (substr ($hash_buf, 41, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 42, 1))) << 16) | (int (ord (substr ($hash_buf, 43, 1))) << 8) | (int (ord (substr ($hash_buf, 44, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 45, 1))) << 16) | (int (ord (substr ($hash_buf, 46, 1))) << 8) | (int (ord (substr ($hash_buf, 47, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 48, 1))) << 16) | (int (ord (substr ($hash_buf, 49, 1))) << 8) | (int (ord (substr ($hash_buf, 50, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 51, 1))) << 16) | (int (ord (substr ($hash_buf, 52, 1))) << 8) | (int (ord (substr ($hash_buf, 53, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 54, 1))) << 16) | (int (ord (substr ($hash_buf, 55, 1))) << 8) | (int (ord (substr ($hash_buf, 56, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 57, 1))) << 16) | (int (ord (substr ($hash_buf, 58, 1))) << 8) | (int (ord (substr ($hash_buf, 59, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 60, 1))) << 16) | (int (ord (substr ($hash_buf, 61, 1))) << 8) | (int (ord (substr ($hash_buf, 62, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 63, 1))) << 16)                                                                                         , 2);

  return $tmp_hash;
}

sub aix_ssha1_pbkdf2
{
  my $word_buf   = shift;
  my $salt_buf   = shift;
  my $iterations = shift;

  my $hasher = Crypt::PBKDF2->hasher_from_algorithm ('HMACSHA1');

  my $pbkdf2 = Crypt::PBKDF2->new (
    hasher       => $hasher,
    iterations   => $iterations,
  );

  my $hash_buf = $pbkdf2->PBKDF2 ($salt_buf, $word_buf);

  my $tmp_hash = "";

  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  0, 1))) << 16) | (int (ord (substr ($hash_buf,  1, 1))) << 8) | (int (ord (substr ($hash_buf,  2, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  3, 1))) << 16) | (int (ord (substr ($hash_buf,  4, 1))) << 8) | (int (ord (substr ($hash_buf,  5, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  6, 1))) << 16) | (int (ord (substr ($hash_buf,  7, 1))) << 8) | (int (ord (substr ($hash_buf,  8, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf,  9, 1))) << 16) | (int (ord (substr ($hash_buf, 10, 1))) << 8) | (int (ord (substr ($hash_buf, 11, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 12, 1))) << 16) | (int (ord (substr ($hash_buf, 13, 1))) << 8) | (int (ord (substr ($hash_buf, 14, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 15, 1))) << 16) | (int (ord (substr ($hash_buf, 16, 1))) << 8) | (int (ord (substr ($hash_buf, 17, 1)))), 4);
  $tmp_hash .= to64 ((int (ord (substr ($hash_buf, 18, 1))) << 16) | (int (ord (substr ($hash_buf, 19, 1))) << 8)                                          , 3);

  return $tmp_hash;
}

sub sapb_transcode
{
  my $data_s = shift;

  my @data = split "", $data_s;

  my $transTable_s =
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\x3f\x40\x41\x50\x43\x44\x45\x4b\x47\x48\x4d\x4e\x54\x51\x53\x46" .
    "\x35\x36\x37\x38\x39\x3a\x3b\x3c\x3d\x3e\x56\x55\x5c\x49\x5d\x4a" .
    "\x42\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" .
    "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x58\x5b\x59\xff\x52" .
    "\x4c\x01\x02\x03\x04\x05\x06\x07\x08\x09\x0a\x0b\x0c\x0d\x0e\x0f" .
    "\x10\x11\x12\x13\x14\x15\x16\x17\x18\x19\x1a\x57\x5e\x5a\x4f\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" .
    "\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff";

  my @transTable = unpack ("C256", $transTable_s);

  my @out;

  for (my $i = 0; $i < scalar @data; $i++)
  {
    $out[$i] = $transTable[int (ord ($data[$i]))];
  }

  return pack ("C*", @out);
}

sub sapb_waldorf
{
  my $digest_s = shift;

  my $w_s = shift;
  my $s_s = shift;

  my @w = unpack "C*", $w_s;
  my @s = unpack "C*", $s_s;

  my $bcodeTable_s =
    "\x14\x77\xf3\xd4\xbb\x71\x23\xd0\x03\xff\x47\x93\x55\xaa\x66\x91" .
    "\xf2\x88\x6b\x99\xbf\xcb\x32\x1a\x19\xd9\xa7\x82\x22\x49\xa2\x51" .
    "\xe2\xb7\x33\x71\x8b\x9f\x5d\x01\x44\x70\xae\x11\xef\x28\xf0\x0d";

  my @bcodeTable = unpack ("C48", $bcodeTable_s);

  my @abcd = unpack ("C16", $digest_s);

  my $sum20 = ($abcd[0] & 3)
            + ($abcd[1] & 3)
            + ($abcd[2] & 3)
            + ($abcd[3] & 3)
            + ($abcd[5] & 3);

  $sum20 |= 0x20;

  my @out;

  for (my $i2 = 0; $i2 < $sum20; $i2++)
  {
    $out[$i2] = 0;
  }

  for (my $i1 = 0, my $i2 = 0, my $i3 = 0; $i2 < $sum20; $i2++, $i2++)
  {
    if ($i1 < length $w_s)
    {
      if ($abcd[15 - $i1] & 1)
      {
        $out[$i2] = $bcodeTable[48 - 1 - $i1];

        $i2++;
      }

      $out[$i2] = $w[$i1];

      $i1++;
      $i2++;
    }

    if ($i3 < length $s_s)
    {
      $out[$i2] = $s[$i3];

      $i2++;
      $i3++;
    }

    $out[$i2] = $bcodeTable[$i2 - $i1 - $i3];
  }

  return substr (pack ("C*", @out), 0, $sum20);
}

sub setup_des_key
{
  my @key_56 = split (//, shift);

  my $key = "";

  $key = $key_56[0];

  $key .= chr (((ord ($key_56[0]) << 7) | (ord ($key_56[1]) >> 1)) & 255);
  $key .= chr (((ord ($key_56[1]) << 6) | (ord ($key_56[2]) >> 2)) & 255);
  $key .= chr (((ord ($key_56[2]) << 5) | (ord ($key_56[3]) >> 3)) & 255);
  $key .= chr (((ord ($key_56[3]) << 4) | (ord ($key_56[4]) >> 4)) & 255);
  $key .= chr (((ord ($key_56[4]) << 3) | (ord ($key_56[5]) >> 5)) & 255);
  $key .= chr (((ord ($key_56[5]) << 2) | (ord ($key_56[6]) >> 6)) & 255);
  $key .= chr (( ord ($key_56[6]) << 1) & 255);

  return $key;
}

sub randbytes
{
  my $len = shift;

  my @arr;

  for (my $i = 0; $i < $len; $i++)
  {
    my $c = get_random_chr (0, 255);

    push (@arr, $c);
  }

  return join ("", @arr);
}

sub get_random_netntlmv1_salt
{
  my $len_user   = shift;
  my $len_domain = shift;

  my $char;
  my $type;
  my $user = "";

  for (my $i = 0; $i < $len_user; $i++)
  {
    $type = get_random_num (1, 3);

    if ($type == 1)
    {
      $char = get_random_chr (0x30, 0x39);
    }
    elsif ($type == 2)
    {
      $char = get_random_chr (0x41, 0x5A);
    }
    else
    {
      $char = get_random_chr (0x61, 0x7A);
    }

    $user .= $char;
  }

  my $domain = "";

  for (my $i = 0; $i < $len_domain; $i++)
  {
    $type = get_random_num (1, 3);

    if ($type == 1)
    {
      $char = get_random_chr (0x30, 0x39);
    }
    elsif ($type == 2)
    {
      $char = get_random_chr (0x41, 0x5A);
    }
    else
    {
      $char = get_random_chr (0x61, 0x7A);
    }

    $domain .= $char;
  }

  my $c_challenge = randbytes (8);
  my $s_challenge = randbytes (8);

  my $salt_buf = $user . "::" . $domain . ":" . unpack ("H*", $c_challenge) . unpack ("H*", $s_challenge);

  return $salt_buf;
}

sub get_random_netntlmv2_salt
{
  my $len_user   = shift;
  my $len_domain = shift;

  my $char;
  my $type;
  my $user = "";

  if ($len_user + $len_domain > 27)
  {
    if ($len_user > $len_domain)
    {
      $len_user = 27 - $len_domain;
    }
    else
    {
      $len_domain = 27 - $len_user;
    }
  }

  for (my $i = 0; $i < $len_user; $i++)
  {
    $type = get_random_num (1, 3);

    if ($type == 1)
    {
      $char = get_random_chr (0x30, 0x39);
    }
    elsif ($type == 2)
    {
      $char = get_random_chr (0x41, 0x5A);
    }
    else
    {
      $char = get_random_chr (0x61, 0x7A);
    }

    $user .= $char;
  }

  my $domain = "";

  for (my $i = 0; $i < $len_domain; $i++)
  {
    $type = get_random_num (1, 3);

    if ($type == 1)
    {
      $char = get_random_chr (0x30, 0x39);
    }
    elsif ($type == 2)
    {
      $char = get_random_chr (0x41, 0x5A);
    }
    else
    {
      $char = get_random_chr (0x61, 0x7A);
    }

    $domain .= $char;
  }

  my $c_challenge = randbytes (8);
  my $s_challenge = randbytes (8);

  my $temp = "\x01\x01" .
             "\x00" x 6 .
             randbytes (8) .
             $c_challenge .
             "\x00" x 4 .
             randbytes (20 * rand () + 1) .
             "\x00";

  my $salt_buf = $user . "::" . $domain . ":" . unpack ("H*", $s_challenge) . unpack ("H*", $temp);

  return $salt_buf;
}

sub get_random_ike_salt
{
  my $nr_buf = "";

  for (my $i = 0; $i < 40; $i++)
  {
    $nr_buf .= get_random_chr (0, 0xff);
  }

  my $msg_buf = "";

  for (my $i = 0; $i < 440; $i++)
  {
    $msg_buf .= get_random_chr (0, 0xff);
  }

  my $nr_buf_hex  = unpack ("H*", $nr_buf);
  my $msg_buf_hex = unpack ("H*", $msg_buf);

  my $salt_buf = sprintf ("%s:%s:%s:%s:%s:%s:%s:%s", substr ($msg_buf_hex, 0, 256), substr ($msg_buf_hex, 256, 256), substr ($msg_buf_hex, 512, 16), substr ($msg_buf_hex, 528, 16), substr ($msg_buf_hex, 544, 320), substr ($msg_buf_hex, 864, 16), substr ($nr_buf_hex, 0, 40), substr ($nr_buf_hex, 40, 40));

  return $salt_buf;
}

sub get_random_agilekeychain_salt
{
  my $salt_buf = "";

  for (my $i = 0; $i < 8; $i++)
  {
    $salt_buf .= get_random_chr (0x0, 0xff);
  }

  my $iv = "";

  for (my $i = 0; $i < 16; $i++)
  {
    $iv .= get_random_chr (0x0, 0xff);
  }

  my $prefix = "\x00" x 1008;

  my $ret = unpack ("H*", $salt_buf . $prefix . $iv);

  return $ret;
}

sub get_random_cloudkeychain_salt
{
  my $salt_buf = "";

  for (my $i = 0; $i < 16; $i++)
  {
    $salt_buf .= get_random_chr (0x0, 0xff);
  }

  for (my $i = 0; $i < 304; $i++)
  {
    $salt_buf .= get_random_chr (0x0, 0xff);
  }

  my $ret = unpack ("H*", $salt_buf);

  return $ret;
}

sub get_random_kerberos5_salt
{
  my $custom_salt = shift;

  my $clear_data = randbytes (14) .
                   strftime ("%Y%m%d%H%M%S", localtime) .
                   randbytes (8);

  my $user  = "user";
  my $realm = "realm";
  my $salt  = "salt";

  my $salt_buf = $user . "\$" . $realm . "\$" . $salt . "\$" . unpack ("H*", $custom_salt) . "\$" . unpack ("H*", $clear_data) . "\$";

  return $salt_buf;
}

sub get_random_kerberos5_tgs_salt
{
  my $nonce = randbytes (8);

  my $user  = "user";
  my $realm = "realm";
  my $spn   = "test/spn";

  my $salt_buf = $user . "\$" . $realm . "\$" . $spn . "\$" . unpack ("H*", $nonce);

  return $salt_buf;
}

sub get_random_kerberos5_as_rep_salt
{
  my $nonce = randbytes (8);

  my $user_principal_name  = "user\@domain.com";
  my $salt_buf = $user_principal_name . ":" . unpack ("H*", $nonce);

  return $salt_buf;
}

sub get_random_axcrypt_salt
{
  my $mysalt = randbytes (16);

  $mysalt = unpack ("H*", $mysalt);

  my $iteration = get_random_num (6, 100000);

  my $salt_buf = $iteration . '*' . $mysalt;

  return $salt_buf;
}

sub get_random_keepass_salt
{
  my $version = get_random_num (1, 3);

  my $algorithm;

  my $iteration;

  my $final_random_seed;

  if ($version == 1)
  {
    $algorithm = get_random_num (0, 2);

    $iteration = get_random_num (50000, 100000);

    $final_random_seed = randbytes (16);
    $final_random_seed  = unpack ("H*", $final_random_seed);
  }
  elsif ($version == 2)
  {
    $algorithm = 0;

    $iteration = get_random_num (6000, 100000);

    $final_random_seed = randbytes (32);
    $final_random_seed  = unpack ("H*", $final_random_seed);
  }

  my $transf_random_seed = randbytes (32);
  $transf_random_seed = unpack ("H*", $transf_random_seed);

  my $enc_iv = randbytes (16);
  $enc_iv = unpack ("H*", $enc_iv);

  my $contents_hash = randbytes (32);
  $contents_hash = unpack ("H*", $contents_hash);

  my $inline_flag = 1;

  my $contents_len = get_random_num (128, 500);

  my $contents = randbytes ($contents_len);

  $contents_len += 16 - $contents_len % 16;

  $contents = unpack ("H*", $contents);

  my $salt_buf;

  my $is_keyfile = get_random_num (0, 2);

  my $keyfile_attributes = "";

  if ($is_keyfile == 1)
  {
    $keyfile_attributes = $keyfile_attributes
                          . "1*64*"
                          . unpack ("H*", randbytes (32));
  }

  if ($version == 1)
  {
    $salt_buf = $version   . '*' .
                $iteration . '*' .
                $algorithm . '*' .
                $final_random_seed  . '*' .
                $transf_random_seed . '*' .
                $enc_iv        . '*' .
                $contents_hash . '*' .
                $inline_flag   . '*' .
                $contents_len  . '*' .
                $contents      . '*' .
                $keyfile_attributes;
  }
  elsif ($version == 2)
  {
    $contents = randbytes (32);
    $contents = unpack ("H*", $contents);

    $salt_buf = $version   . '*' .
                $iteration . '*' .
                $algorithm . '*' .
                $final_random_seed  . '*' .
                $transf_random_seed . '*' .
                $enc_iv        . '*' .
                $contents_hash . '*' .
                $contents      . '*' .
                $keyfile_attributes;
  }

  return $salt_buf;
}

sub get_pstoken_salt
{
  my $pstoken_length = get_random_num (16, 256);

  ## not a valid pstoken but a better test
  ## because of random length

  my $pstoken_const = randbytes ($pstoken_length);

  return unpack ("H*", $pstoken_const);
}

sub get_random_md5chap_salt
{
  my $salt_buf = shift;

  my $salt = unpack ("H*", $salt_buf);

  $salt .= ":";

  $salt .= unpack ("H*", randbytes (1));

  return $salt;
}

sub get_random_dnssec_salt
{
  my $salt_buf = "";

  $salt_buf .= ".";

  for (my $i = 0; $i < 8; $i++)
  {
    $salt_buf .= get_random_chr (0x61, 0x7a);
  }

  $salt_buf .= ".net";

  $salt_buf .= ":";

  for (my $i = 0; $i < 8; $i++)
  {
    $salt_buf .= get_random_chr (0x30, 0x39);
  }

  return $salt_buf;
}

sub get_random_dpapimk_salt
{
  my $salt_buf = "";

  my $version = shift;

  my $context = get_random_num (1, 3);

  my $cipher_algo = "";

  my $hash_algo = "";

  my $iterations;

  my $SID = sprintf ('S-15-21-%d-%d-%d-%d',
             get_random_num (400000000,490000000),
             get_random_num (400000000,490000000),
             get_random_num (400000000,490000000),
             get_random_num (1000,1999));

  my $cipher_len = 0;

  if ($version == 1)
  {
    $iterations = get_random_num (4000, 24000);

    $cipher_algo = "des3";

    $hash_algo = "sha1";

    $cipher_len = 208;
  }
  elsif ($version == 2)
  {
    $iterations = get_random_num (8000, 17000);

    $cipher_algo = "aes256";

    $hash_algo = "sha512";

    $cipher_len = 288;
  }

  my $iv = randbytes (16);
  $iv    = unpack ("H*", $iv);

  $salt_buf = $version . '*' .
              $context . '*' .
              $SID     . '*' .
              $cipher_algo   . '*' .
              $hash_algo     . '*' .
              $iterations    . '*' .
              $iv         . '*' .
              $cipher_len . '*';

  return $salt_buf;
}

sub get_random_jwt_salt
{
  my @hashes =
  (
    "HS256",
    #"HS384", #this is support in hashcat, but commented out here to prevent mixed hash output files in single mode
    #"HS512", #this is support in hashcat, but commented out here to prevent mixed hash output files in single mode
    #"RS256", #not supported by hashcat
    #"RS384",
    #"RS512",
    #"PS256",
    #"PS384",
    #"PS512",
    #"ES256",
    #"ES384",
    #"ES512",
  );

  my $rnd = get_random_num (0, scalar @hashes);

  my $hash = $hashes[$rnd];

  my $header =
  {
    "alg" => $hash
  };

  my $random_key = get_random_num (1, 100000000);
  my $random_val = get_random_num (1, 100000000);

  my $payload =
  {
    $random_key => $random_val
  };

  my $header_json    = encode_json ($header);
  my $payload_json   = encode_json ($payload);

  my $header_base64  = encode_base64url ($header_json, "");
  my $payload_base64 = encode_base64url ($payload_json, "");

  return $header_base64 . "." . $payload_base64;
}

sub md5bit
{
  my $digest = shift;
  my $bit = shift;

  $bit %= 128;

  my $byte_off = int ($bit / 8);
  my $bit_off  = int ($bit % 8);

  my $char = substr ($digest, $byte_off, 1);
  my $num  = ord ($char);

  return (($num & (1 << $bit_off)) ? 1 : 0);
}

sub sun_md5
{
  my $pw   = shift;
  my $salt = shift;
  my $iter = shift;

  my $constant_phrase =
  "To be, or not to be,--that is the question:--\n"        .
  "Whether 'tis nobler in the mind to suffer\n"            .
  "The slings and arrows of outrageous fortune\n"          .
  "Or to take arms against a sea of troubles,\n"           .
  "And by opposing end them?--To die,--to sleep,--\n"      .
  "No more; and by a sleep to say we end\n"                .
  "The heartache, and the thousand natural shocks\n"       .
  "That flesh is heir to,--'tis a consummation\n"          .
  "Devoutly to be wish'd. To die,--to sleep;--\n"          .
  "To sleep! perchance to dream:--ay, there's the rub;\n"  .
  "For in that sleep of death what dreams may come,\n"     .
  "When we have shuffled off this mortal coil,\n"          .
  "Must give us pause: there's the respect\n"              .
  "That makes calamity of so long life;\n"                 .
  "For who would bear the whips and scorns of time,\n"     .
  "The oppressor's wrong, the proud man's contumely,\n"    .
  "The pangs of despis'd love, the law's delay,\n"         .
  "The insolence of office, and the spurns\n"              .
  "That patient merit of the unworthy takes,\n"            .
  "When he himself might his quietus make\n"               .
  "With a bare bodkin? who would these fardels bear,\n"    .
  "To grunt and sweat under a weary life,\n"               .
  "But that the dread of something after death,--\n"       .
  "The undiscover'd country, from whose bourn\n"           .
  "No traveller returns,--puzzles the will,\n"             .
  "And makes us rather bear those ills we have\n"          .
  "Than fly to others that we know not of?\n"              .
  "Thus conscience does make cowards of us all;\n"         .
  "And thus the native hue of resolution\n"                .
  "Is sicklied o'er with the pale cast of thought;\n"      .
  "And enterprises of great pith and moment,\n"            .
  "With this regard, their currents turn awry,\n"          .
  "And lose the name of action.--Soft you now!\n"          .
  "The fair Ophelia!--Nymph, in thy orisons\n"             .
  "Be all my sins remember'd.\n\x00";

  my $constant_len = length ($constant_phrase);

  my $hash_buf = md5 ($pw . $salt);

  my $W;

  my $to_hash;

  for (my $round = 0; $round < $iter; $round++)
  {
    my $shift_a = md5bit ($hash_buf, $round +  0);
    my $shift_b = md5bit ($hash_buf, $round + 64);

    my @shift_4;
    my @shift_7;

    for (my $k = 0; $k < 16; $k++)
    {
       my $s7shift = ord (substr ($hash_buf, $k, 1)) % 8;

       my $l = ($k + 3) % 16;

       my $num = ord (substr ($hash_buf, $l, 1));

       $shift_4[$k] = $num % 5;

       $shift_7[$k] = ($num >> $s7shift) & 1;
    }

    my @indirect_4;

    for (my $k = 0; $k < 16; $k++)
    {
      $indirect_4[$k] = (ord (substr ($hash_buf, $k, 1)) >> $shift_4[$k]) & 0xf;
    }

    my @indirect_7;

    for (my $k = 0; $k < 16; $k++)
    {
      $indirect_7[$k] = (ord (substr ($hash_buf, $indirect_4[$k], 1)) >> $shift_7[$k]) & 0x7f;
    }

    my $indirect_a = 0;
    my $indirect_b = 0;

    for (my $k = 0; $k < 8; $k++)
    {
      $indirect_a |= md5bit ($hash_buf, $indirect_7[$k + 0]) << $k;

      $indirect_b |= md5bit ($hash_buf, $indirect_7[$k + 8]) << $k;
    }

    $indirect_a = ($indirect_a >> $shift_a) & 0x7f;
    $indirect_b = ($indirect_b >> $shift_b) & 0x7f;

    my $bit_a = md5bit ($hash_buf, $indirect_a);
    my $bit_b = md5bit ($hash_buf, $indirect_b);

    $W = $hash_buf;

    my $pos = 16;

    my $total = $pos;

    $to_hash = "";

    if ($bit_a ^ $bit_b)
    {
      substr ($W, 16, 48) = substr ($constant_phrase, 0, 48);

      $total += 48;

      $to_hash .= substr ($W, 0, 64);

      my $constant_off;

      for ($constant_off = 48; $constant_off < $constant_len - 64; $constant_off += 64)
      {
        substr ($W, 0, 64) = substr ($constant_phrase, $constant_off, 64);

        $total += 64;

        $to_hash .= substr ($W, 0, 64);
      }

      $pos = $constant_len - $constant_off;

      $total += $pos;

      substr ($W, 0, $pos) = substr ($constant_phrase, $constant_off, $pos);
    }

    my $a_len = 0;

    my @a_buf;
    $a_buf[0] = 0;
    $a_buf[1] = 0;
    $a_buf[2] = 0;
    $a_buf[3] = 0;

    my $tmp = $round;

    do
    {
      my $round_div = int ($tmp / 10);
      my $round_mod = int ($tmp % 10);

      $tmp = $round_div;

      $a_buf[int ($a_len / 4)] = (($round_mod + 0x30) | ($a_buf[int ($a_len / 4)] << 8));

      $a_len++;

    } while ($tmp);

    my $tmp_str = "";

    my $g;

    for ($g = 0; $g < $a_len; $g++)
    {
      my $remainder = $a_buf[$g];
      my $factor = 7;
      my $started = 1;

      my $sub;

      while ($remainder > 0)
      {
        $sub = $remainder >> (8 * $factor);

        if ($started != 1 || $sub > 0)
        {
          $started = 0;

          $tmp_str = chr ($sub) . $tmp_str;

          $remainder -= ($sub << (8 * $factor));
        }

        $factor--;
      }

    }

    substr ($W, $pos, $a_len) = $tmp_str;

    $pos += $a_len;

    $total += $a_len;

    $to_hash .= substr ($W, 0, $pos);

    $to_hash = substr ($to_hash, 0, $total);

    $hash_buf = md5 ($to_hash);
  }

  my $passwd = "";

  $passwd .= to64 ((int (ord (substr ($hash_buf,  0, 1))) << 16) | (int (ord (substr ($hash_buf,  6, 1))) << 8) | (int (ord (substr ($hash_buf, 12, 1)))), 4);
  $passwd .= to64 ((int (ord (substr ($hash_buf,  1, 1))) << 16) | (int (ord (substr ($hash_buf,  7, 1))) << 8) | (int (ord (substr ($hash_buf, 13, 1)))), 4);
  $passwd .= to64 ((int (ord (substr ($hash_buf,  2, 1))) << 16) | (int (ord (substr ($hash_buf,  8, 1))) << 8) | (int (ord (substr ($hash_buf, 14, 1)))), 4);
  $passwd .= to64 ((int (ord (substr ($hash_buf,  3, 1))) << 16) | (int (ord (substr ($hash_buf,  9, 1))) << 8) | (int (ord (substr ($hash_buf, 15, 1)))), 4);
  $passwd .= to64 ((int (ord (substr ($hash_buf,  4, 1))) << 16) | (int (ord (substr ($hash_buf, 10, 1))) << 8) | (int (ord (substr ($hash_buf,  5, 1)))), 4);
  $passwd .= to64 ((int (ord (substr ($hash_buf, 11, 1)))), 2);

  return $passwd;
}

sub usage_die
{
  die ("usage: $0 single|passthrough| [mode] [len]\n" .
       "       or\n" .
       "       $0 verify              [mode] [hashfile] [cracks] [outfile]\n");
}

sub pad16
{
  my $block_ref = shift;

  my $offset = shift;

  my $value = 16 - $offset;

  for (my $i = $offset; $i < 16; $i++)
  {
    push @{$block_ref}, $value;
  }
}

sub lotus_mix
{
  my $in_ref = shift;

  my $p = 0;

  for (my $i = 0; $i < 18; $i++)
  {
    for (my $j = 0; $j < 48; $j++)
    {
      $p = ($p + 48 - $j) & 0xff;

      my $c = $LOTUS_MAGIC_TABLE->[$p];

      $p = $in_ref->[$j] ^ $c;

      $in_ref->[$j] = $p;
    }
  }
}

sub lotus_transform_password
{
  my $in_ref  = shift;
  my $out_ref = shift;

  my $t = $out_ref->[15];

  for (my $i = 0; $i < 16; $i++)
  {
    $t ^= $in_ref->[$i];

    my $c = $LOTUS_MAGIC_TABLE->[$t];

    $out_ref->[$i] ^= $c;

    $t = $out_ref->[$i];
  }
}

sub mdtransform_norecalc
{
  my $state_ref = shift;
  my $block_ref = shift;

  my @x;

  push (@x, @{$state_ref});
  push (@x, @{$block_ref});

  for (my $i = 0; $i < 16; $i++)
  {
    push (@x, $x[0 + $i] ^ $x[16 + $i]);
  }

  lotus_mix (\@x);

  for (my $i = 0; $i < 16; $i++)
  {
    $state_ref->[$i] = $x[$i];
  }
}

sub mdtransform
{
  my $state_ref    = shift;
  my $checksum_ref = shift;
  my $block_ref    = shift;

  mdtransform_norecalc ($state_ref, $block_ref);

  lotus_transform_password ($block_ref, $checksum_ref);
}

sub domino_big_md
{
  my $saved_key_ref = shift;

  my $size = shift;

  @{$saved_key_ref} = splice (@{$saved_key_ref}, 0, $size);

  my @state = (0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0);

  my @checksum;

  my $curpos;

  for ($curpos = 0; $curpos + 16 < $size; $curpos += 16)
  {
    my @block = splice (@{$saved_key_ref}, 0, 16);

    mdtransform (\@state, \@checksum, \@block);
  }

  my $left = $size - $curpos;

  my @block = splice (@{$saved_key_ref}, 0, 16);

  pad16 (\@block, $left);

  mdtransform (\@state, \@checksum, \@block);

  mdtransform_norecalc (\@state, \@checksum);

  return @state;
}

sub pdf_compute_encryption_key
{
  my $word_buf  = shift;
  my $padding   = shift;
  my $id        = shift;
  my $u         = shift;
  my $o         = shift;
  my $P         = shift;
  my $V         = shift;
  my $R         = shift;
  my $enc       = shift;

  ## start

  my $data;

  $data .= $word_buf;

  $data .= substr ($padding, 0, 32 - length $word_buf);

  $data .= pack ("H*", $o);

  $data .= pack ("I", $P);

  $data .= pack ("H*", $id);

  if ($R >= 4)
  {
    if (!$enc)
    {
      $data .= pack ("I", -1);
    }
  }

  my $res = md5 ($data);

  if ($R >= 3)
  {
    for (my $i = 0; $i < 50; $i++)
    {
      $res = md5 ($res);
    }
  }

  return $res;
}

sub gen_random_wpa_eapol
{
  my $keyver = shift;
  my $snonce = shift;

  my $ret = "";

  # version

  my $version = 1; # 802.1X-2001

  $ret .= pack ("C*", $version);

  my $type = 3;    # means that this EAPOL frame is used to transfer key information

  $ret .= pack ("C*", $type);

  my $length; # length of remaining data

  if ($keyver == 1)
  {
    $length = 119;
  }
  else
  {
    $length = 117;
  }

  $ret .= pack ("n*", $length);

  my $descriptor_type;

  if ($keyver == 1)
  {
    $descriptor_type = 254; # EAPOL WPA key
  }
  else
  {
    $descriptor_type = 1; # EAPOL RSN key
  }

  $ret .= pack ("C*", $descriptor_type);

  # key_info is a bit vector:
  # generated from these 13 bits: encrypted key data, request, error, secure, key mic, key ack, install, key index (2), key type, key descriptor (3)

  my $key_info = 0;

  $key_info |= 1 << 8; # set key MIC
  $key_info |= 1 << 3; # set if it is a pairwise key

  if ($keyver == 1)
  {
    $key_info |= 1 << 0; # RC4 Cipher, HMAC-MD5 MIC
  }
  else
  {
    $key_info |= 1 << 1; # AES Cipher, HMAC-SHA1 MIC
  }

  $ret .= pack ("n*", $key_info);

  my $key_length;

  if ($keyver == 1)
  {
    $key_length = 32;
  }
  else
  {
    $key_length = 0;
  }

  $ret .= pack ("n*", $key_length);

  my $replay_counter = 1;

  $ret .= pack ("Q>*", $replay_counter);

  $ret .= $snonce;

  my $key_iv = "\x00" x 16;

  $ret .= $key_iv;

  my $key_rsc = "\x00" x 8;

  $ret .= $key_rsc;

  my $key_id = "\x00" x 8;

  $ret .= $key_id;

  my $key_mic = "\x00" x 16;

  $ret .= $key_mic;

  my $key_data_len;

  if ($keyver == 1)
  {
    $key_data_len = 24; # length of the key_data (== WPA info)
  }
  else
  {
    $key_data_len = 22; # length of the key_data (== RSN info)
  }

  $ret .= pack ("n*", $key_data_len);

  my $key_data = "";

  if ($keyver == 1)
  {
    # wpa info

    my $wpa_info = "";

    my $vendor_specific_data = "";

    my $tag_number = 221; # means it is a vendor specific tag

    $vendor_specific_data .= pack ("C*", $tag_number);

    my $tag_len = 22;     # length of the remaining "tag data"

    $vendor_specific_data .= pack ("C*", $tag_len);

    my $vendor_specific_oui = pack ("H*", "0050f2"); # microsoft

    $vendor_specific_data .= $vendor_specific_oui;

    my $vendor_specific_oui_type = 1; # WPA Information Element

    $vendor_specific_data .= pack ("C*", $vendor_specific_oui_type);

    my $vendor_specific_wpa_version = 1;

    $vendor_specific_data .= pack ("v*", $vendor_specific_wpa_version);

    # multicast

    my $vendor_specific_multicast_oui = pack ("H*", "0050f2");

    $vendor_specific_data .= $vendor_specific_multicast_oui;

    my $vendor_specific_multicast_type = 2; # TKIP

    $vendor_specific_data .= pack ("C*", $vendor_specific_multicast_type);

    # unicast

    my $vendor_specific_unicast_count = 1;

    $vendor_specific_data .= pack ("v*", $vendor_specific_unicast_count);

    my $vendor_specific_unicast_oui = pack ("H*", "0050f2");

    $vendor_specific_data .= $vendor_specific_unicast_oui;

    my $vendor_specific_unicast_type = 2; # TKIP

    $vendor_specific_data .= pack ("C*", $vendor_specific_unicast_type);

    # Auth Key Management (AKM)

    my $auth_key_management_count = 1;

    $vendor_specific_data .= pack ("v*", $auth_key_management_count);

    my $auth_key_management_oui = pack ("H*", "0050f2");

    $vendor_specific_data .= $auth_key_management_oui;

    my $auth_key_management_type = 2; # Pre-Shared Key (PSK)

    $vendor_specific_data .= pack ("C*", $auth_key_management_type);

    $wpa_info = $vendor_specific_data;

    $key_data = $wpa_info;
  }
  else
  {
    # rsn info

    my $rsn_info = "";

    my $tag_number = 48; # RSN info

    $rsn_info .= pack ("C*", $tag_number);

    my $tag_len = 20;    # length of the remaining "tag_data"

    $rsn_info .= pack ("C*", $tag_len);

    my $rsn_version = 1;

    $rsn_info .= pack ("v*", $rsn_version);

    # group cipher suite

    my $group_cipher_suite_oui = pack ("H*", "000fac"); # Ieee8021

    $rsn_info .= $group_cipher_suite_oui;

    my $group_cipher_suite_type = 4; # AES (CCM)

    $rsn_info .= pack ("C*", $group_cipher_suite_type);

    # pairwise cipher suite

    my $pairwise_cipher_suite_count = 1;

    $rsn_info .= pack ("v*", $pairwise_cipher_suite_count);

    my $pairwise_cipher_suite_oui = pack ("H*", "000fac"); # Ieee8021

    $rsn_info .= $pairwise_cipher_suite_oui;

    my $pairwise_cipher_suite_type = 4; # AES (CCM)

    $rsn_info .= pack ("C*", $pairwise_cipher_suite_type);

    # Auth Key Management (AKM)

    my $auth_key_management_count = 1;

    $rsn_info .= pack ("v*", $auth_key_management_count);

    my $auth_key_management_oui = pack ("H*", "000fac"); # Ieee8021

    $rsn_info .= $auth_key_management_oui;

    my $auth_key_management_type = 2; # Pre-Shared Key (PSK)

    $rsn_info .= pack ("C*", $auth_key_management_type);

    # RSN Capabilities

    # bit vector of these 9 bits: peerkey enabled, management frame protection (MFP) capable, MFP required,
    # RSN GTKSA Capabilities (2), RSN PTKSA Capabilities (2), no pairwise Capabilities, Pre-Auth Capabilities

    my $rsn_capabilities = pack ("H*", "0000");

    $rsn_info .= $rsn_capabilities;

    $key_data = $rsn_info;
  }

  $ret .= $key_data;

  return $ret;
}

sub wpa_prf_512
{
  my $keyver = shift;
  my $pmk    = shift;
  my $stmac  = shift;
  my $bssid  = shift;
  my $snonce = shift;
  my $anonce = shift;

  my $data = "Pairwise key expansion";

  if (($keyver == 1) || ($keyver == 2))
  {
    $data .= "\x00";
  }

  #
  # Min(AA, SPA) || Max(AA, SPA)
  #

  # compare if greater: Min()/Max() on the MACs (6 bytes)

  if (memcmp ($stmac, $bssid, 6) < 0)
  {
    $data .= $stmac;
    $data .= $bssid;
  }
  else
  {
    $data .= $bssid;
    $data .= $stmac;
  }

  #
  # Min(ANonce,SNonce) || Max(ANonce,SNonce)
  #

  # compare if greater: Min()/Max() on the nonces (32 bytes)

  if (memcmp ($snonce, $anonce, 32) < 0)
  {
    $data .= $snonce;
    $data .= $anonce;
  }
  else
  {
    $data .= $anonce;
    $data .= $snonce;
  }

  my $prf_buf;

  if (($keyver == 1) || ($keyver == 2))
  {
    $data .= "\x00";

    $prf_buf = hmac ($data, $pmk, \&sha1);
  }
  else
  {
    my $data3 = "\x01\x00" . $data . "\x80\x01";

    $prf_buf = hmac ($data3, $pmk, \&sha256);
  }

  $prf_buf = substr ($prf_buf, 0, 16);

  return $prf_buf;
}

sub itunes_aes_wrap
{
  my $key = shift;
  my $A   = shift;
  my $R_l = shift;

  my $k = scalar (@$R_l);
  my $n = $k + 1;

  my @R;

  for (my $i = 0; $i < $n; $i++)
  {
    $R[$i] = @$R_l[$i];
  }

  # AES mode ECB

  my $m = Crypt::Mode::ECB->new ('AES', 0);

  # main wrap loop

  my ($i, $j, $a);

  for ($j = 0; $j <= 5; $j++)
  {
    for ($i = 1, $a = 0; $i <= $k; $i++, $a++)
    {
      my $input;

      $input  = pack ("Q>", $A);
      $input .= pack ("Q>", $R[$a]);

      my $t = $m->encrypt ($input, $key);

      $A     = unpack ("Q>", substr ($t, 0, 8));
      $A    ^= $k * $j + $i;

      $R[$a] = unpack ("Q>", substr ($t, 8, 8));
    }
  }

  my $WPKY = pack ("Q>", $A);

  for (my $i = 0; $i < $k; $i++)
  {
    $WPKY .= pack ("Q>", $R[$i]);
  }

  return $WPKY;
}

sub itunes_aes_unwrap
{
  my $key  = shift;
  my $WPKY = shift;

  my @B;

  for (my $i = 0; $i < length ($WPKY) / 8; $i++)
  {
    $B[$i] = unpack ("Q>", substr ($WPKY, $i * 8, 8));
  }

  my $n = scalar (@B);
  my $k = $n - 1;

  my @R;

  for (my $i = 0; $i < $k; $i++)
  {
    $R[$i] = $B[$i + 1];
  }

  # AES mode ECB

  my $m = Crypt::Mode::ECB->new ('AES', 0);

  # main unwrap loop

  my $A = $B[0];

  my ($i, $j, $a);

  for ($j = 5; $j >= 0; $j--)
  {
    for ($i = $k, $a = $k - 1; $i > 0; $i--, $a--)
    {
      my $input;

      $input  = pack ("Q>", $A ^ ($k * $j + $i));
      $input .= pack ("Q>", $R[$a]);

      my $t = $m->decrypt ($input, $key);

      $A     = unpack ("Q>", substr ($t, 0, 8));
      $R[$a] = unpack ("Q>", substr ($t, 8, 8));
    }
  }

  return ($A, \@R);
}

sub memcmp
{
  my $str1 = shift;
  my $str2 = shift;
  my $len  = shift;

  my $len_str1 = length ($str1);
  my $len_str2 = length ($str2);

  if (($len > $len_str1) || ($len > $len_str2))
  {
    print "ERROR: memcmp () lengths wrong";

    exit (1);
  }

  for (my $i = 0; $i < $len; $i++)
  {
    my $c_1 = ord (substr ($str1, $i, 1));
    my $c_2 = ord (substr ($str2, $i, 1));

    return -1 if ($c_1 < $c_2);
    return  1 if ($c_1 > $c_2);
  }

  return 0;
}
