Supergenpass

A perl implementation of supergenpass

I had been wanting a perl implementation of the supergenpass script and couldn’t find one anywhere, so I just wrote one myself. It is a very simple script, but I’m making it available because I had a hard time getting the Perl hash to give the same result as the javascript one used by SGP. I’ve tested it with a lot of password and domain combinations and have always found it to give the same results.

The main function is easy enough to identify in the code. I also wanted an easy way to tell if the password I typed in had a typo (I fat finger passwords quite a bit), so I added a function that would allow you to store the hash of commonly used passwords. Then, if any of those are defined, it prints the password as either green or red, depending on whether the master password matches one of the hashes.

If you have any suggestions or improvements on the script, please leave a comment below.

#!/usr/bin/perl

#  The following was written by John R Saathoff
#  www.jrsaathoff.com

#  To the extent possible under law, John Saathoff  has waived all
#  copyright and related or neighboring rights to this work.

use strict;
use warnings;
use MIME::Base64;
use Digest::MD5 qw(md5 md5_hex md5_base64);
use Getopt::Long;


# Hashes of master passwords.  These variables are optional, determined by
# using the -m option when running
# They are used so that during normal operation the color will be green for
# a regularly entered password, and red for an unrecognized one
my @masterhashlist = ();
#my @masterhashlist = ('dfd9798dfdkadfkdjfdkfjAA', '21378421897edfdaafdfdfAA');

# Handle command line options
my ($opt_d, $opt_m, $opt_p, $opt_h, $opt_l) = (0,0,0,0,10);
my $result = GetOptions ("d|domain=s"      => \$opt_d,     # string
                         "h|?|help"        => \$opt_h,     # boolean
                         "master-hash"     => \$opt_m,     # boolean
                         "l|length=i"      => \$opt_l);    # integer
if (! $result) { die "\nIncorrect input, run -h for help\n"; }
if ($opt_h)    { usage(); }

# Store command line value (can also use -d), if one exists, as domain $opt_d
if ($ARGV[0]) {
  $opt_d = $ARGV[0];
  shift;
}

print "master password = ";
system("stty -echo");               # Don't display the password on the terminal
chop($opt_p = <>);
print "\n";
system("stty echo");

# Print the hash of the master password if the option is provided and exit program
if ($opt_m) {
  print "master hash     = ", masterHash($opt_p), "\n";
  die "\n";
}

# If no domain has been defined prompt for one on command line
if (! $opt_d) {
  print "domain          = ";
  chomp($opt_d = <>);
}

print "site password   = ";
# this checks to see if any master hashes have been defined.  If so, it prints the
# password using the appropriate color.
if (@masterhashlist > 0) {
  setPasswordColor ($opt_p);
  print generatePassword($opt_p.':'.$opt_d), "\033[0m\n";
} else {
  print generatePassword($opt_p.':'.$opt_d), "\n";
}

###########  hash  ###########################

# Returns the md5 value of a string as expected by SGP.  Pass the string to hash.
sub hash {
  my $md5string = $_[0];
  $md5string = md5_base64($md5string);
  $md5string = $md5string.'AA';                            # The following three lines are simply to make the Perl MD5
  $md5string =~ s/\+/9/g;                                  # return the same result as the javascript MD5 funciton
  $md5string =~ s/\//8/g;

  return $md5string;
}

###########  masterHash  ###########################

# Returns the result of the hash function called 50 times on a value
sub masterHash {
  my $mhash = $_[0];

  for (my $i=0; $i<50; $i++) {
    $mhash = hash($mhash);
  }
  return $mhash;
}

###########  setPasswordColor  ###########################

# Sets the color as green if it matches a saved hash, red if it doesn't
sub setPasswordColor {
  my $chash = $_[0];
  my $md5match = 0;
  
  $chash = masterHash($chash);
  foreach (@masterhashlist) {
    if ($chash eq $_) { $md5match = 1; }
  }

  if ($md5match) {
    print "\033[32m";
  } else {
    print "\033[31m";
  }
}

###########  validpassword  ###########################

# Returns true if the passed string is a valid password
sub validPassword {
  my $password = $_[0];
  my $valid    = 1;

  $valid = $valid && ($password =~ m/[A-Z]/);   # Contain at least one uppercase letter of the alphabet
  $valid = $valid && ($password =~ m/[0-9]/);   # Contain at least one numeral
  $valid = $valid && ($password =~ m/^[a-z]/);  # Start with a lowercase letter of the alphabet

  return $valid;
}

###########  generatePassword  ###########################

# MD5 the starting string 10 times or until we get a valid password
sub generatePassword {
  my $hashstring         = $_[0];
  my ($count, $password) = (0, 0);

  while (! (validPassword($password)) || ($count < 10)) {
    $hashstring = hash($hashstring);
    $password   = substr($hashstring, 0, $opt_l);
    $count += 1;
  }
  return $password;
}

###########  USAGE  ###########################

# Print help and exit program
sub usage {
  print "\n";
  print "-d, --domain       the site domain\n";
  print "--master-hash      print a check hash value for a password.  It is the result of\n";
  print "                   hashing the password 50 times\n";
  print "-l, --length       the length of the generated password (default is 10)\n";
  print "-h, -?, --help     prints help and exits\n";

  die "\n";
}