#!/usr/bin/env perl

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

use strict;
use warnings;
use Digest::MD4      qw (md4 md4_hex);
use Digest::MD5      qw (md5 md5_hex);
use Digest::SHA      qw (sha1 sha256 sha384 sha512 sha1_hex sha256_hex sha384_hex sha512_hex);
use Digest::HMAC     qw (hmac hmac_hex);
use Digest::Keccak   qw (keccak_256_hex);
use Crypt::MySQL     qw (password41);
use Digest::GOST     qw (gost gost_hex);
use Digest::HMAC_MD5 qw (hmac_md5);
use Digest::CRC      qw (crc32);
use Crypt::PBKDF2;
use Crypt::DES;
use Crypt::ECB       qw (encrypt);
use Crypt::CBC;
use Crypt::Eksblowfish::Bcrypt qw (bcrypt en_base64);
use Crypt::Digest::RIPEMD160   qw (ripemd160_hex);
use Crypt::Digest::Whirlpool   qw (whirlpool_hex);
use Crypt::RC4;
use Crypt::ScryptKDF qw (scrypt_hash scrypt_b64);
use Crypt::Rijndael;
use Crypt::Twofish;
use Crypt::Mode::ECB;
use Crypt::UnixCrypt_XS qw (crypt_rounds fold_password base64_to_int24 block_to_base64 int24_to_base64);
use MIME::Base64;
use Authen::Passphrase::NTHash;
use Authen::Passphrase::MySQL323;
use Authen::Passphrase::PHPass;
use Authen::Passphrase::LANManager;
use Encode;
use POSIX qw (strftime);
use Net::DNS::SEC;
use Net::DNS::RR::NSEC3;
use Convert::EBCDIC qw (ascii2ebcdic);
use Digest::SipHash qw/siphash/;

my $hashcat = "./hashcat";

my $MAX_LEN = 55;

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, 900, 1000, 1100, 1400, 1410, 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, 4600, 4700, 4800, 4900, 5000, 5100, 5300, 5400, 5500, 5600, 5700, 5800, 6000, 6100, 6300, 6400, 6500, 6600, 6700, 6800, 6900, 7100, 7200, 7300, 7400, 7500, 7600, 7700, 7800, 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, 11900, 12000, 12100, 12200, 12300, 12400, 12600, 12700, 12800, 12900, 13000, 13100, 13200, 13300, 13400, 13500, 13600, 13800, 13900, 14000, 14100);

my %is_unicode      = map { $_ => 1 } qw(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 %less_fifteen    = map { $_ => 1 } qw(500 1600 1800 2400 2410 3200 6300 7400 10500 10700);
my %allow_long_salt = map { $_ => 1 } qw(2500 5500 5600 7100 7200 7300 9400 9500 9600 9700 9800 10400 10500 10600 10700 1100 11000 11200 11300 11400 11600 12600 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);
}

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 ($mode == 0 || $mode == 100 || $mode == 101 || $mode == 133 || $mode == 200 || $mode == 300 || $mode == 900 || $mode == 1000 || $mode == 1400 || $mode == 1700 || $mode == 2400 || $mode == 2600 || $mode == 3000 || $mode == 3500 || $mode == 4300 || $mode == 4400 || $mode == 4500 || $mode == 4600 || $mode == 4700 || $mode == 5000 || $mode == 5100 || $mode == 5700 || $mode == 6000 || $mode == 6100 || $mode == 6900 || $mode == 8600 || $mode == 9900 || $mode == 10800 || $mode == 11500)
    {
      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 ($mode == 10 || $mode == 11 || $mode == 12 || $mode == 20 || $mode == 21 || $mode == 22 || $mode == 23 || $mode == 30 || $mode == 40 || $mode == 50 || $mode == 60 || $mode == 110 || $mode == 112 || $mode == 120 || $mode == 121 || $mode == 130 || $mode == 140 || $mode == 150 || $mode == 160 || $mode == 1100 || $mode == 1410 || $mode == 1420 || $mode == 1430 || $mode == 1440 || $mode == 1450 || $mode == 1460 || $mode == 1710 || $mode == 1720 || $mode == 1730 || $mode == 1740 || $mode == 1750 || $mode == 1760 || $mode == 2410 || $mode == 2611 || $mode == 2711 || $mode == 2811 || $mode == 3100 || $mode == 3610 || $mode == 3710 || $mode == 3720 || $mode == 3800 || $mode == 3910 || $mode == 4010 || $mode == 4110 || $mode == 4210 || $mode == 4900 || $mode == 5800 || $mode == 7600 || $mode == 8400 || $mode == 11000 || $mode == 12600 || $mode == 13500 || $mode == 13800 || $mode == 13900 || $mode == 14000 || $mode == 14100)
    {
      # 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);
    }
    # OSX (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);
    }
    # OSX 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)
    {
      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/WPA2
    elsif ($mode == 2500)
    {
      print "ERROR: verify currently not supported for WPA/WPA2 (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})));
    }
    # BSDiCrypt, 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 == 2;

      shift @data;

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

      next unless ($signature eq '$axcrypt_sha1');
      next unless (length ($digest) == 32 || length ($digest) == 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})));
    }
    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 == 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 == 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);
    }
    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) > 31;

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

    if ($mode == 0 || $mode == 100 || $mode == 101 || $mode == 133 || $mode == 200 || $mode == 300 || $mode == 600 || $mode == 900 || $mode == 1000 || $mode == 1400 || $mode == 1700 || $mode == 2400 || $mode == 2600 || $mode == 3500 || $mode == 4300 || $mode == 4400 || $mode == 4500 || $mode == 4600 || $mode == 4700 || $mode == 5000 || $mode == 5100 || $mode == 6000 || $mode == 6100 || $mode == 6900 || $mode == 5700 || $mode == 9900 || $mode == 10800 || $mode == 11500 || $mode == 13300)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, "");
    }
    elsif ($mode == 10 || $mode == 20 || $mode == 23 || $mode == 30 || $mode == 40 || $mode == 50 || $mode == 60 || $mode == 110 || $mode == 120 || $mode == 130 || $mode == 140 || $mode == 150 || $mode == 160 || $mode == 1410 || $mode == 1420 || $mode == 1430 || $mode == 1440 || $mode == 1450 || $mode == 1460 || $mode == 1710 || $mode == 1711 || $mode == 1720 || $mode == 1730 || $mode == 1740 || $mode == 1750 || $mode == 1760 || $mode == 3610 || $mode == 3710 || $mode == 3711 || $mode == 3720 || $mode == 3800 || $mode == 3910 || $mode == 4010 || $mode == 4110 || $mode == 4210 || $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 || $mode == 7600 || $mode == 12300)
    {
      $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)
    {
      $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)
    {
      next if length ($word_buf) > 15;

      my $salt_len = get_random_num (1, 15);

      my $word_len = length ($word_buf);

      $salt_len = min ($salt_len, 15 - $word_len);

      $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)
    {
      $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 == 12200)
    {
      $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 == 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 == 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)
    {
      next if length ($word_buf) > 8;

      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 12));
    }
    elsif ($mode == 7800)
    {
      $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)
    {
      $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 || $mode == 9700 || $mode == 9800)
    {
      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 == 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 == 12400)
    {
      $tmp_hash = gen_hash ($mode, $word_buf, substr ($salt_buf, 0, 4));
    }
    elsif ($mode == 12600)
    {
      $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));
    }
    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 ($mode == 0 || $mode == 100 || $mode == 101 || $mode == 133 || $mode == 200 || $mode == 300 || $mode == 600 || $mode == 900 || $mode == 1000 || $mode == 1400 || $mode == 1700 || $mode == 2400 || $mode == 2600 || $mode == 3500 || $mode == 4300 || $mode == 4400 || $mode == 4500 || $mode == 4600 || $mode == 4700 || $mode == 5000 || $mode == 5100 || $mode == 5300 || $mode == 5400 || $mode == 6000 || $mode == 6100 || $mode == 6600 || $mode == 6900 || $mode == 5700 || $mode == 8200 || $mode == 8300 || $mode == 9900 || $mode == 10800 || $mode == 11500 || $mode == 13300)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 0);
        }
        else
        {
          rnd ($mode, $i, 0);
        }
      }
    }
    elsif ($mode == 10 || $mode == 20 || $mode == 23 || $mode == 30 || $mode == 40 || $mode == 50 || $mode == 60 || $mode == 110 || $mode == 120 || $mode == 121 || $mode == 130 || $mode == 140 || $mode == 150 || $mode == 160 || $mode == 1410 || $mode == 1420 || $mode == 1430 || $mode == 1440 || $mode == 1450 || $mode == 1460 || $mode == 1710 || $mode == 1711 || $mode == 1720 || $mode == 1730 || $mode == 1740 || $mode == 1750 || $mode == 1760 || $mode == 2410 || $mode == 3610 || $mode == 3710 || $mode == 3711 || $mode == 3720 || $mode == 3910 || $mode == 4010 || $mode == 4110 || $mode == 4210 || $mode == 8900 || $mode == 10000 || $mode == 10200 || $mode == 10900 || $mode == 11900 || $mode == 12000 || $mode == 12100)
    {
      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 || $mode == 7600 || $mode == 12300)
    {
      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 < 32; $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 == 12200)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 16);
        }
        else
        {
          rnd ($mode, $i, 16);
        }
      }
    }
    if ($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 == 2500)
    {
      my $salt_len = get_random_num (0, 32);

      for (my $i = 8; $i < 16; $i++)
      {
        my $generate_from_len = 0;

        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 < 32; $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 == 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 == 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)
    {
      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)
    {
      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)
    {
      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 || $mode == 9700 || $mode == 9800)
    {
      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 == 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 == 12400)
    {
      for (my $i = 1; $i < 32; $i++)
      {
        if ($len != 0)
        {
          rnd ($mode, $len, 4);
        }
        else
        {
          rnd ($mode, $i, 4);
        }
      }
    }
    elsif ($mode == 12600)
    {
      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);
    }
  }
}

exit;

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

    chomp ($base64_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);

    chomp ($base64_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);
    $hash_buf =~ s/[\r\n]//g;

    $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);

    chomp ($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 == 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 == 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 == 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);

    chomp ($base64_salt_buf);

    my $base64_hash_buf = encode_base64 ($hash_buf);

    chomp ($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)
  {
    $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);

    $base64_buf =~ s/[ \n]//g;

    $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)
  {
    $tmp_hash = sprintf ("%s", pseudo_base64 (Digest::MD5::md5 ($word_buf . "\0" x (16 - length ($word_buf)))));
  }
  elsif ($mode == 2410)
  {
    my $salt_len = length ($salt_buf);

    my $salt_len_max4 = ($salt_len < 4) ? $salt_len : 4;

    my $hash_buf = pseudo_base64 (Digest::MD5::md5 ($word_buf . substr ($salt_buf, 0, $salt_len_max4) . "\0" x (16 - length ($word_buf) - $salt_len_max4)));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 2500)
  {
    my ($bssid, $stmac, $snonce, $anonce, $eapol, $keyver, $eapol_size);

    if (! defined ($additional_param))
    {
      # random stuff

      $bssid  = randbytes (6);
      $stmac  = randbytes (6);
      $snonce = randbytes (32);
      $anonce = randbytes (32);

      $keyver = get_random_num (1, 3); # 1 or 2

      # 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_size = 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 ($pmk, $stmac, $bssid, $snonce, $anonce);

    # generate the Message Integrity Code (MIC)

    my $mic = "";

    if ($keyver == 1) # WPA1 => MD5
    {
      $mic = hmac ($eapol, $ptk, \&md5);
    }
    else # WPA2 => SHA1
    {
      $mic = hmac ($eapol, $ptk, \&sha1);
    }

    $mic = substr ($mic, 0, 16);

    #
    # format the binary output
    #

    $hash_buf = "";

    # first the essid (NULL-padded up to the first 36 bytes)

    $hash_buf .= $salt_buf;
    $hash_buf .= "\x00" x (36 - length ($salt_buf));

    # the 2 MAC addresses

    $hash_buf .= $bssid;
    $hash_buf .= $stmac;

    # nonces

    $hash_buf .= $snonce;
    $hash_buf .= $anonce;

    # eapol

    $hash_buf .= $eapol;
    $hash_buf .= "\x00" x (256 - $eapol_size);

    # eapol size

    $hash_buf .= pack ("L*", $eapol_size);

    # key version

    $hash_buf .= pack ("L*", $keyver);

    # and finally: the key mic

    $hash_buf .= $mic;

    # 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 == 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 == 5000)
  {
    $hash_buf = keccak_256_hex ($word_buf);

    $tmp_hash = sprintf ("%s", $hash_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 == 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;
      my $num;

      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 == 7600)
  {
    $hash_buf = sha1_hex ($salt_buf . sha1_hex ($word_buf));

    $tmp_hash = sprintf ("%s:%s", $hash_buf, $salt_buf);
  }
  elsif ($mode == 7700)
  {
    $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;

    $tmp_hash = sprintf ("%s\$%08X%08X", $salt_buf, $a, $b);
  }
  elsif ($mode == 7800)
  {
    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);

    $tmp_hash = sprintf ("%s\$%s", $salt_buf, uc $hash_buf);
  }
  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);

    my $iteration_str = "" . $iterations;

    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));
    $hash_buf =~ s/[\r\n]//g;

    $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);
    $challengeb64 =~ s/[\r\n]//g;

    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);
    $responseb64 =~ s/[\r\n]//g;

    $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);
    $hash_buf =~ s/[\r\n]//g;

    $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));
    $hash_buf =~ s/[\r\n]//g;

    my $base64_salt_buf = encode_base64 ($salt_buf);

    chomp ($base64_salt_buf);

    $tmp_hash = sprintf ("sha256:%i:%s:%s", $iterations, $base64_salt_buf, $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 == 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);
    $word_buf_base64 =~ s/[\r\n]//g;

    my $salt_buf_base64 = encode_base64 ($salt_buf);
    $salt_buf_base64 =~ s/[\r\n]//g;

    # 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);
    $hash_buf =~ s/[\r\n]//g;

    my $base64_salt_buf = encode_base64 ($salt_buf);

    chomp ($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));
    $hash_buf =~ s/[\r\n]//g;

    my $base64_salt_buf = encode_base64 ($salt_buf);

    chomp ($base64_salt_buf);

    $tmp_hash = sprintf ("sha1:%i:%s:%s", $iterations, $base64_salt_buf, $hash_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));
    $hash_buf =~ s/[\r\n]//g;

    my $base64_salt_buf = encode_base64 ($salt_buf);

    chomp ($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)
  {
    $hash_buf = sha1_hex ($word_buf);

    $tmp_hash = sprintf ('$axcrypt_sha1$%s', substr ($hash_buf, 0, 32));
  }
  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_unicode = encode ("UTF-16LE", $word_buf);

    my $salt_buf_bin = pack ("H*", $salt_buf);

    $hash_buf = sha256_hex ($word_buf_unicode . $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);
  }

  return ($tmp_hash);
}

sub rnd
{
  my $mode = shift;

  my $word_len = shift;

  my $salt_len = shift;

  my $max = $MAX_LEN;

  $max = 15 if ($mode == 2410);

  if ($is_unicode{$mode})
  {
    if (! $allow_long_salt{$mode})
    {
      $word_len = min ($word_len, int ($max / 2) - $salt_len);
    }
    else
    {
      $word_len = min ($word_len, int ($max / 2));
    }
  }
  elsif ($less_fifteen{$mode})
  {
    $word_len = min ($word_len, 15);

    if ($mode == 2410)
    {
      $salt_len = min ($salt_len, 15 - $word_len);
    }
  }
  else
  {
    if (! $allow_long_salt{$mode})
    {
      $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 ();
  }
  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);

    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_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 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 $curpos16 = $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_multicast_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 $pmk    = shift;
  my $stmac  = shift;
  my $bssid  = shift;
  my $snonce = shift;
  my $anonce = shift;

  my $data = "Pairwise key expansion";

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

  $data .= "\x00";

  my $prf_buf = hmac ($data, $pmk, \&sha1);

  $prf_buf = substr ($prf_buf, 0, 16);

  return $prf_buf;
}

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