Shell Script – How to Force a Script to Use More Resources

resourcesscriptingshell-script

I'm running a very time-consuming script which takes many hours to end. Watching top I see that it's only taking 5% of the CPU at best, usually around 3%.

Is there any way to force the script to use more CPU in order to end faster?

Edit:

Basically the script is bruteforcing 5 chars length passwords given a the salt and the hash.

Not at home right now, but something like:

charset = ['a','b',.........'z'];
for i in $charset do
   for j in $charset do
       for k in $charset do
           for l in $charset do
              for m in $charset do
                  pass = `openssl passwd -salt $1 $i$j$k$l$m`
                  if [ pass == $2 ]]; then
                     echo "Password: $i$j$k$l$m";
                     exit;
                  fi
               done
            done
        done
    done
 done

Best Answer

Improvement #1 - Loops

Your looping structure seems completely unnecessary if you use brace expansions instead, it can be condensed like so:

$ more pass.bash 
#!/bin/bash

for str in $(echo {a..z}{a..z}{a..z}); do
  pass=$(openssl passwd -salt $1 $str)
  if [[ "$pass" == "$2" ]]; then
    echo "Password: $str"
    exit;
  fi
done

# vim: set nolist ts=2 :

I'm showing 4 characters just to make it run faster, simply add additional {a..z} braces for additional characters for password length.

Example runs

4 characters
$ openssl passwd -salt ab hhhh
abBYJnOuV8dUA

$ time ./pass.bash ab abBYJnOuV8dUA
Password: hhhh

real    18m3.304s
user    6m58.204s
sys     9m34.468s

So it completed in 18 minutes.

5 characters
$ openssl passwd -salt ab ccccc
abZwsITAI6uwM

$ time ./pass.bash ab abZwsITAI6uwM
Password: ccccc

real    426m37.234s
user    16m34.444s
sys     398m20.399s

This took ~426 minutes. I actually Ctrl+C this, so it hadn't finished, but I didn't want to wait any more than this!

NOTE: Both these runs were on this CPU:

brand = "Intel(R) Core(TM) i5 CPU       M 560  @ 2.67GHz

Improvement #2 - Using nice?

The next logical step would be to nice the above runs so that they can consume more resources.

 $ nice -n -20 ./pass.bash ab hhhhh

But this will only get you so far. One of the "flaws" in your approach is the calling of openssl repeatedly. With {a..z}^5 you're calling openssl 26^5 = 11881376 times.

One major improvement would be to generate the patterns of {a..z}.... and save them to a file, and then pass this as a single item to openssl one time. Thankfully openssl has 2 key features that we can exploit to get what we want.

Improvement #3 - our call structure to openssl

The command line tool openssl provides the switches -stdin and -table which we can make use of here to have a single invoke of openssl irregardless of how many passwords we want to pass to it. This is single modification will remove all the overhead of having to invoke openssl, do work, and then exit it, instead we keep a single instance of it open indefinitely, feeding it as many passwords as we want.

The -table switch is also crucial since it tells openssl to include the original password along side the ciphers version, so we can make fairly quick work of looking for our match.

Here's an example using just 3 characters to show what we're changing:

$ openssl passwd -salt ab abc
abFZSxKKdq5s6

$ printf "%s\n" {a..z}{a..z}{a..z} | \
    openssl passwd -stdin -table -crypt -salt ab | grep -m1 abFZSxKKdq5s6
abc abFZSxKKdq5s6

So now we can really revamp our original pass.bash script like so:

$ cat pass2.bash 
#!/bin/bash

pass=$(printf "%s\n" {a..z}{a..z}{a..z}{a..z}{a..z} | \
  openssl passwd -stdin -table -crypt -salt $1 | grep -m1 $2)

if [[ "$pass" =~ "$2" ]]; then
  echo "Password: $pass"
fi

# vim: set nolist ts=2 :

Now when we run it:

$ time ./pass2.bash ab aboznNh9QV/Q2
Password: hhhhh aboznNh9QV/Q2

real    1m11.194s
user    1m13.515s
sys     0m7.786s

This is a massive improvement! This same search that was taking more than 426 minutes is now done in ~1 minute! If we search through to say "nnnnn" that's roughly in the middle of the {a..z}^5 character set space. {a..n} is 14 characters, and we're taking 5 of them.

$ echo "14^5" | bc 
537824

$ openssl passwd -salt ab nnnnn
abRRCp5N3WN32

$ time ./pass2.bash ab abRRCp5N3WN32
Password: nnnnn abRRCp5N3WN32

real    1m10.865s
user    1m12.842s
sys     0m8.530s

This search took ~1.1 minutes. NOTE: We can search the entire space of 5 character passwords in ~1 minute too.

$ time ./pass2.bash ab abBQdT5EcUvYA
Password: zzzzz abBQdT5EcUvYA

real    1m10.783s
user    1m13.556s
sys     0m8.251s

Conclusions

So with a restructuring we're running much faster. This approach scales much better too as we add a 6th, 7th, etc. character to the overall length of the password.

Be warned though that we're using a smallish character set, mainly only the lowercase alphabet characters. If you mix in all the number, both cases, and special characters you can typically get ~96 characters per position. This may not seem like a big deal but this increase your pool tremendously:

$ echo 26^5 | bc
11881376
$ echo 96^5 | bc
8153726976

Adding all those characters just increased by 2 orders of magnitude our search space. If we go up to roughly 10-12 characters of length to the password, it really puts a brute force hacking methodology out of reach.

Using proper a salt as well as additional NONCE's throughout the construction of a hashed password can add still more stumbling blocks.

What else?

You've mentioned using John (John the Ripper) or other cracking tools. Probably the state of the art currently would be HashCat.

Where John is a tighter version of the approach you're attempting to use, HashCat takes it to another level by enlisting the use of GPUs (up to 128) to really make your hacking attempts fly.

You can even make use of CloudCrack, which is a hosted version, and for a mere $17 US you can pay to have a password crack attempted.

References

Related Question