Friday, January 18, 2008

Script To Generate All Possible Passwords With Perl

In Yesterday's post, we took a look at how it's possible to use Perl to deal with generating random passwords, and that post was meant to link in with its preceding post. While it, technically, fulfilled that requirement, it took a while to elaborate on scripting out random passwords so I tried to keep on point, since it turned out to be a post's worth of information in itself.

With that in mind, in today's post, we're going to look at another part of the password puzzle: Generation of all possible passwords within a given numeric range. This might otherwise be referred to as brute-force password generation (which is the reason I wrote it using brute-force scripting :). What we're going to accomplish today is to create a simple "password generator" script that will allow us to generate all possible passwords (or all possible combinations of the 94 standard ASCII characters that make up valid passwords) up to an 8 character password. This has been tested on both Solaris Unix and RedHat Linux. One day, I'll stop compulsively typing that. If a script works in Perl, using that language's basics, its a given that it will work on most Unix/Linux systems you can install it on.

Please bear in mind that using this program is simple, but, depending on how you use it, it may take up all of your disk space and lots of your computer's time ;)

By way of explanation: This script produces one entry per line into a file, so that you could feed that file to a program like the one we looked at in this post.

Now, if we were to run it with the following arguments (we'll call it for whatever goofy reason I pick the names I pick for my scripts ;)

host # ./ 1 >OUTPUT <--- Note that, especially when running with larger number arguments, you should redirect the script output to a file, rather than "tee" it off, since viewing the tty/pty buffer as password combinations are generated could introduce a giant lag between completion of the script's execution and your getting a shell prompt back!

it would generate, in under a second, a file 94 lines long, with each line containing 1 of each of the 94 characters available (Please see the script's @94 array to check out all the variables. It's too insane to type over and over again ;), like this:

host # wc -l OUTPUT
host # 94 OUTPUT

These numbers (both the size of the list in lines and the amount of time it takes to run the program) increase with each added level. They both increase exponentially which is more evident to the user if you're running an 8 character execution than if you're running a 2 character execution.

Let's say we decide we want to list out all possible 2 letter passwords. We would do this:

host # ./ 2 >OUTPUT

And check out how big that gets (all combinations from aa to ??):

host # wc -l OUTPUT
host # 8836 OUTPUT

Just to be sure we're right about this, let's check with the standard Linux and Unix "dc" utility by typing:

host # echo 94 2 ^ p | dc
host # 8836

and see that using 2 characters is actually the 94 original characters to the exponent of 2. This theory relates to every level you go up. So running it for all combinations of 4 letter passwords would be the 94 characters to the exponent of 4 as demonstrated here:

host # echo 94 4 ^ p | dc
host # 78074896
host # ./ 4 >OUTPUT
host # wc -l OUTPUT
host # 78074896 OUTPUT

And the time to wait for your list to generate? It may very well be exponentially longer. If you're going to run a 4 character execution, go grab a cup of coffee. If you're going to run an 8 character execution, depending on your machine, you might as well go home, get some rest and come back in to work the next day. Then it might be halfway done ;) I've never had the patience or desire to try and sneak this in at work and the boxes I have at home would take weeks to run this (94 to the 8th power will generate 6,095,689,385,410,816 unique passwords).

Example running on a SunFire v490 with 4 x 1350Mhz CPU and 32GB RAM:

host # time ./ 2 >OUTPUT
real 0m0.47s
user 0m0.04s
sys 0m0.38s
host # time ./ 4 >OUTPUT
real 5m59.03s
user 5m37.10s
sys 0m2.86s

Once you've created a master password file, like so:

host # ./ 8 >OUTPUT

you can use the OUTPUT file with the "pwdcheck" Perl script we introduced a few posts ago. Then, assuming you have the root privilege required to access and read your /etc/shadow file, you can set that on auto (perhaps trim the print statements so "pwdcheck" only prints out matches) and will eventually guess everyone's password. At this point, it's really only a matter of time, because you will be checking every possible combination of 94 possible characters in all 8 positions of the password. You can prove this to yourself simply by grepping any valid 8 character string from your OUTPUT file. It will be there (trust me)!

host # grep sN@gg3r$ OUTPUT
host # sN@gg3r$

Note that this also assumes that your password system is limited to an 8 character boundary, like Sun's standard. If you wanted to run up against more advanced password systems with better encryption and longer possible passwords, you'd just need to use your scripting abilities to modify both scripts slightly in order to achieve the same end result.

This Perl script should hopefully be a helpful tool in your constant fight against lame passwords. And, as always, though it can be used for less than ethical purposes, please understand that I only put this stuff out to try and help other admins like myself make their workplace more secure. Since any disgruntled lunatic can use these same methods to make your work-life miserable, you owe it to yourself to know how to do it, too.

Knowledge is power. If you know what your adversary knows, you're doing better than most :)


Creative Commons License

This work is licensed under a
Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License


# - generate all possible password
# combinations up to 8 characters
# Usage: Password_Length (1 - 8)
# 2008 - Mike Golvach -
# Creative Commons Attribution-Noncommercial-Share Alike 3.0 United States License

if ( $#{ARGV} < 0 ) {
print "\nUsage: $0 password_length\n";
print "Only 8 characters please. This is\n";
print "going to take a long time as it is!\n";

$pass_length = $ARGV[0];

@94 = qw(a b c d e f g h i j k l m n o p q r s t u v w x y z A B C D E F G H I J K L M N O P Q R S T U V W X Y Z ` 1 2 3 4 5 6 7 8 9 0 - = [ ] \ ; ' , . / ~ ! @ # $ % ^ & * ( ) _ + { } | : " < > ?);

if ( $pass_length < 1 || $pass_length > 8 ) {
print "Usage: $0 password_length\n";
print "Only 8 characters please. This is\n";
print "going to take a long time as it is!\n";

$a = $b = $c = $d = $e = $f = $g = $h = 94;

if ( $pass_length == 8 ) {
for ($a=0;$a<94;$a++) { for ($b=0;$b<94;$b++) { for ($c=0;$c<94;$c++) { for ($d=0;$d<94;$d++) { for ($e=0;$e<94;$e++) { for ($f=0;$f<94;$f++) { for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s%s%s%s%s%s%s\n", $94[$a], $94[$b], $94[$c], $94[$d], $94[$e], $94[$f], $94[$g], $94[$h]);
} } } } } } } }
} elsif ( $pass_length == 7 ) {
for ($b=0;$b<94;$b++) { for ($c=0;$c<94;$c++) { for ($d=0;$d<94;$d++) { for ($e=0;$e<94;$e++) { for ($f=0;$f<94;$f++) { for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s%s%s%s%s%s\n", $94[$b], $94[$c], $94[$d], $94[$e], $94[$f], $94[$g], $94[$h]);
} } } } } } }
} elsif ( $pass_length == 6 ) {
for ($c=0;$c<94;$c++) { for ($d=0;$d<94;$d++) { for ($e=0;$e<94;$e++) { for ($f=0;$f<94;$f++) { for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s%s%s%s%s\n", $94[$c], $94[$d], $94[$e], $94[$f], $94[$g], $94[$h]);
} } } } } }
} elsif ( $pass_length == 5 ) {
for ($d=0;$d<94;$d++) { for ($e=0;$e<94;$e++) { for ($f=0;$f<94;$f++) { for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s%s%s%s\n", $94[$d], $94[$e], $94[$f], $94[$g], $94[$h]);
} } } } }
} elsif ( $pass_length == 4 ) {
for ($e=0;$e<94;$e++) { for ($f=0;$f<94;$f++) { for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s%s%s\n", $94[$e], $94[$f], $94[$g], $94[$h]);
} } } }
} elsif ( $pass_length == 3 ) {
for ($f=0;$f<94;$f++) { for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s%s\n", $94[$f], $94[$g], $94[$h]);
} } }
} elsif ( $pass_length == 2 ) {
for ($g=0;$g<94;$g++) { for ($h=0;$h<94;$h++) {
printf("%s%s\n", $94[$g], $94[$h]);
} }
} elsif ( $pass_length == 1 ) {
for ($h=0;$h<94;$h++) {
printf("%s\n", $94[$h]);

, Mike