Simple password dictionary attack script This is my simple Perl script implement password dictionary attacking, encryption algorithm SHA1 with different length.

It takes 2 arguments as input: first argument is password dictionary file path, the second one is a text file contains line-by-line encrypted text. I used 2 common dictionaries for testing: rockyou and phpbb.

Any suggestions to make it more efficient?

#!/usr/bin/perl
use strict;
use warnings;
use Digest::SHA;
use autodie qw(open close);

# command arguments
my $dic = $ARGV[0];
my $pwd = $ARGV[1];

# read input cipher texts and dictionary
open (my $input, '&lt;', $pwd);
my @ciphers = &lt;$input&gt;;
# remove end of line characters
chomp(@ciphers);

print "\n";
print "dictionary attack demo\n";
print "encrypt algorithm: SHA1\n";
print "======================================\n\n";

foreach my $cipher(@ciphers){
    my $password = "*";
    my $count = 0;
    my $factor = 0;

    # calculate factor depends on length of cipher text
    $factor = length($cipher);
    if ($factor == 40) {
        $factor = 1;
    }
    elsif(($factor == 56) ||
          ($factor == 64) ||
          ($factor == 96) ||
          ($factor == 128))
    {
        $factor = $factor * 4;
    }
    else {
        next;
    }

    # create new SHA algorithm object with key length is factor
    my $sha = Digest::SHA-&gt;new($factor);
    open (my $dict, '&lt;', $dic);

    while(&lt;$dict&gt;){
        chomp;
        $sha-&gt;add($_);
        # encrypt each word with SHA1 algorithm
        my $encrypt_text = $sha-&gt;hexdigest;

        # compare with cipher text, if matches
        if($encrypt_text eq $cipher){
            $password = $_;
            last;
        }
        # count number of tested words
        $count++;
    }

    # print out result, "*" if not found
    print "$cipher\:\:$password\t[ATTEMPT\:\:$count words]\n";
    # close dictionary file handle
    close ($dict);
}

# close input file handle
close($input);
Performance

For each cipher, you re-read and re-encrypt the dictionary again and again. If some of your ciphers have the same length, then this is wasteful. At the minimum, you should load the dictionary only once. If you have a lot of passwords, then you could pre-encrypt all words in the dictionary for each unique factor. That should speed things up.

Coding style

You could simplify this if condition:


if (($factor == 56) ||
      ($factor == 64) ||
      ($factor == 96) ||
      ($factor == 128))
{ ... }



by using a helper function, perhaps something like this:

sub one_of {
    my $value = shift;
    for (@_) { return 1 if $_ eq $value; }
}
# ...
if (&amp;one_of($factor, 56, 64, 96, 128)) { ... }




This can be slightly simpler:


my @ciphers = &lt;$input&gt;;
# remove end of line characters
chomp(@ciphers);



like this:

chomp(my @ciphers = &lt;$input&gt;);


And then close($input) right after. It's good to close an input file immediately after you're done reading from it.



You could simplify $factor = $factor * 4; as $factor *= 4;



I think all your comments are pointless. You can remove them and let the code speak for itself. 

If the meaning of a variable is not clear enough without a comment, then try to rename it to be self-explanatory. Note that the variable will be self-explanatory every time you use it, whereas a comment only explains it in once place.

Likewise, you can extract non-trivial logic to a function with a name that explains what it does. For example:

sub get_factor {
    my $cipher = shift;
    my $factor = length($cipher);
    if ($factor == 40) {
        return 1;
    } elsif (&amp;one_of($factor, 56, 64, 96, 128)) {
        return $factor * 4;
    }
}

foreach my $cipher(@ciphers) {
    # ...
    my $factor = &amp;get_factor($cipher) || next;
    # ...
}