#!perl

use strictures 1;

use App::bmkpasswd qw/ mkpasswd passwdcmp /;
use Term::ReadKey  qw/ ReadMode /;
use Time::HiRes    qw/ gettimeofday tv_interval /;

my $type = 'bcrypt';
my $bcost = '08';
my $bench = 0;
my $strong = 0;
my $check;

use Getopt::Long;
GetOptions(

  'benchmark!'    => \$bench,
  'strong!'       => \$strong,
  'check=s'       => \$check,
  'method|type=s' => \$type,
  'workcost=s'    => \$bcost,
  
  'version' => sub {
     require Crypt::Eksblowfish::Bcrypt;
     print(
           "App::bmkpasswd $App::bmkpasswd::VERSION\n\n",
           "  Crypt::Eksblowfish::Bcrypt-", 
            $Crypt::Eksblowfish::Bcrypt::VERSION, "\n\n",
     );
     
     print "  SHA256 available\n" if App::bmkpasswd::have_sha(256);
     print "  SHA512 available\n" if App::bmkpasswd::have_sha(512);

     print "  Using Crypt::Passwd::XS\n" 
       if App::bmkpasswd::have_passwd_xs;
     
     exit 0
   },

  'help' => sub {
    print(
      "bmkpasswd [OPTS]... [PASSWD]\n",
      "Opts:\n",
      " -b, --benchmark\n",
      "   Show timers. [Default: off]\n",
      "\n",
      " -m, --method=<TYPE>\n",
      "   Default: -m bcrypt\n",
      "   Types: \n",
      "   - bcrypt (recommended)\n",
      "   - sha256 (requires Crypt::Passwd::XS or recent libc)\n",
      "   - sha512 (requires Crypt::Passwd::XS or recent libc)\n",
      "   - md5 (insecure, not recommended)\n",
      "\n",
      " -c, --check=<HASH>\n",
      "   Compare password against specified hash.\n",
      "\n",
      " -s, --strong\n",
      "   Use a blocking random source to generate salts.\n",
      "\n",
      " bcrypt-only:\n",
      " -w, --workcost=<two digit power of 2>\n",
      "   Default: -w 08\n",
      "   bcrypt work cost factor; higher is slower.\n",
      "   When it comes to comparing passwds, slower can be better.\n",
      "   See http://codahale.com/how-to-safely-store-a-password/\n",
    );
    exit 0
  },  

);

my $pwd;
if (@ARGV) {
  $pwd = $ARGV[0];
} else {
  ReadMode(2);
  print "Password: ";
  $pwd = <STDIN>;
  ReadMode(0);
  chomp($pwd);
  print "\n";
}

my $timer = [gettimeofday()] if $bench;

if ($check) {
  if ( passwdcmp($pwd, $check) ) {
    print "Match\n", "$check\n";
  } else {
    exit 1
  }
} else {
  print mkpasswd($pwd, $type, $bcost, $strong)."\n";
}
if ($bench) {
  my $interval = tv_interval($timer);
  print " bench: $type, timer0 -> $interval\n";
}
exit 0

__END__
=pod

=head1 NAME

 bmkpasswd - bcrypt-enabled mkpasswd

=head1 SYNOPSIS

 bmkpasswd [OPTIONS]... [PASSWD]

=head1 OPTIONS

 -h, --help

 -m, --method <TYPE>
     crypt method.
     Types:
       bcrypt  (default; requires Crypt::Eksblowfish)
       sha512  (requires recent libc or Crypt::Passwd::XS)
       sha256  (requires recent libc or Crypt::Passwd::XS)
       md5     (weak, not recommended)

 -c, --check <HASH>
     Check password against <HASH>.
     Method will be auto-detected.

 -s, --strong
     Use a blocking random source like /dev/random
     to generate salts.

 -w, --workcost <COST>
     bcrypt-only.
     Specify a work cost factor. Higher is slower.
     Must be a two-digit power of 2.
     Pad with zeros as necessary.

 -b, --benchmark
     Show timers; useful for comparing hash generation times.

=head1 DESCRIPTION

Simple bcrypt-enabled mkpasswd.

While SHA512 isn't a bad choice if you have it, bcrypt has the 
advantage of including a configurable work cost factor.

A higher work cost factor exponentially increases hashing time, meaning 
a brute-force attack against stolen hashes can take a B<very> long time.

Salts are randomly generated using L<Bytes::Random::Secure>.
Using the C<--strong> option requires a reliable source of entropy; try
B<haveged> (L<http://www.issihosts.com/haveged/downloads.html>), especially on
headless Linux systems.

See L<App::bmkpasswd> for more details on bcrypt and the inner workings of
this software.

See L<Crypt::Bcrypt::Easy> if you'd like a simple interface to creating and
comparing bcrypted passwords from your own modules.

=head1 AUTHOR

Jon Portnoy <avenj@cobaltirc.org>

=cut
