5

I have a file I encrypted with AES via aescrypt about 5 years ago. I remember a lot of things about the password with several prefixes and suffixes. I have experience as a programmer but not with brute forcing passwords.

How might I go about brute forcing my encrypted archive?

What I think I know is: I need a rainbow table (list of possible words) and some way to guide a tool to likely suffixes and special characters.

I don't know what tools would be ideal for this particular scenario nor do I know how they'd be set up. I've been reading man pages on many of the tools available from Ubuntu's package management system but instructions are focused on different scenarios and I can't seem to find a way to indicate what I need to indicate.

If I were to program my own solution for it I know I can take the aescrypt command and pass a password guess right in on the command line. If the password fails it returns a non zero exit status so that's easy enough to check with. For performance I'd write it in Rust and have a crack at a threaded implementation. But I don't know much beyond trying permutations as far as perhaps needing some other characters injected.

The rules for this password are simple. There are two periods in it, up to 8 likely words intermixed with possible conjunctions in them, possible dashes or underscores, and possible abbreviations of those words. If that should fail I'd want it to fall back to a normal brute force with a suffix attached.

That should give me the highest chance of retrieving my files. I would really appreciate being pointed in the right direction for this with some getting started tips and outlined instruction. Thank you so much!

6
  • 6
    Slight terminology thing: a rainbow table isn't a list of possible words. You might find some useful results by searching "word list creation" or "crunch"
    – Matthew
    Commented Jun 9, 2017 at 15:04
  • 1
    have you tried john the ripper? you can at least use its rules if you have to have a custom decrypter.
    – dandavis
    Commented Jun 9, 2017 at 15:26
  • @dandavis No, the man page only shows how to get started against /etc/passwd . I don't know of a way to use it with aescrypt as I haven't used it before.
    – 6ft Dan
    Commented Jun 9, 2017 at 15:46
  • @Matthew With crunch, even if I leave off the suffix with the 22 possible characters from 9 to 16 in length it would take 593TB of space. That's brute forcing through every combo without the words I know likely to be there.
    – 6ft Dan
    Commented Jun 9, 2017 at 16:17
  • 1
    Make a custom dictionary based on the fractions you remember. Use that in the BF.
    – Overmind
    Commented Jun 14, 2017 at 7:55

2 Answers 2

8

I'm going to post what I've learned here for posterity. I've found the way to do what I want, although I doubt I have the system power to complete the task.

The first thing I tried was all possible combinations of what my guesses would be. First I wrote a Ruby script to generate that.

#!/usr/bin/env ruby

JOINERS = ['', '-', '_', ' ']
PREFIX = %w[words for prefix]
CONJUNCTION = ['and', nil]
OPTIONAL = %w[words for optional]
SUFFIX = %w[.tar.gz .tgz .tar.gz.aes .tgz.aes]

def permute collection, length = nil
  (1..(length || collection.length)).
    flat_map {|qty| collection.permutation(qty).to_a }
end

def prefixer
  permute(PREFIX, 3).each do |prf|
    yield prf
  end
end

def optionaler(pfx, j, s, cap = false)
  permute(OPTIONAL, 3).each do |opt|
    opt = opt.map(&:capitalize) if cap
    CONJUNCTION.each do |cj|
      puts [pfx.join(j), cj, opt.join(j)].compact.join(j) + s
    end
  end
end

SUFFIX.each do |s|
  JOINERS.each do |j|
    prefixer do |prf|
      puts prf.join(j) + s
      optionaler(prf, j, s)

      prf = prf.map(&:capitalize)
      puts prf.join(j) + s
      optionaler(prf, j, s, true)
    end
  end
end

I ran this script and the output gave me just shy of 500,000 entries. I then used my FishShell to iterate over each of those and try the password.

for password in (ruby script.rb)
  aescrypt -d -p $password encrypted_file.aes 2>/dev/null
end

None of those worked so I have to switch to brute force… which means I may never get it open.

So I learned how to use crunch as per Matthew's suggestion and in combination with xargs it's pretty awesome!

Since I'm running a shell command each time, which attempts to decrypt a file, I though disk drive IO might be a performance consideration so I've created a tmpfs file system folder to put the file in and operate everything from within RAM.

mkdir tmpfs
sudo mount -t tmpfs -o size=800M,mode=0755 tmpfs tmpfs/
cp encrypted_file.aes tmpfs/
cd tmpfs

And here's the first command I worked out.

crunch 3 26 abcdefhilmnoprstuvwyz0123456789\ -_. -d 1@ --stdout | \
xargs -r -P 4 -I PASSWORD bash -c \
"! aescrypt -d -p 'PASSWORD' encrypted_file.aes 2>/dev/null"

Here are the parts. crunch generates all possibilities of the following parameters. The first number is the minimum amount of characters, the next is the maximum. After that is a character list to use for every attempt. I've excluded characters which I'm certain aren't in it so the character list is shorter and the process will get done much more quickly. Next I have -d 1@ which tells crunch that no two of the same characters will be next to each other.

Then I pipe it to xargs which is a nice tool generally used for streamed input data to be passed as a parameter to the following command/argument. But in this case we're piping it in to a particular place with the -I option which determines which text to replace. The -r option says not to run with empty input. The -P 4 tells it to run 4 instances in parallel with four different parameters passed in (this takes advantage of all 4 cores on my system). Using 4 cores instead of 1 shaves off about 60% of the cost in time.

Next I execute aescrypt via a bash command bash -c and the PASSWORD area is substituted with every individual input passed in. The reason I start this with a bang (!) is because when aescrypt doesn't successfully decrypt a file it writes to STDERR and gives a failing exit status which will stop the entire command immediately. The bang flips the result so that on every failure it will keep running but when the file finally gets decrypted it will negate the positive response and exit the loop because our work is done.

Notes

I realize this isn't the most performant way to go about it but I'm giving a good stab at it. I'll provide some additional performance observances I've seen.

When using the xargs parallel parameter it doesn't matter how high you go you will still get the same performance as the number of cores you have. But if you run multiple shells with xargs each asking for more cores than you have then you will slow down the performance of all of them as if you only had 1 core.

Since one of my best guesses is that the password ends in .tar.gz I split the task in half by changing the command above to use two cores and shortening the character list for the second command to not include gz or any numbers (because if the extension exists the numbers won't exist for this password) and I've substituted 'PASSWORD' to 'PASSWORD.tar.gz'. The second command I also gave 2 parallel processes.

Because I was flying blind and I wondered what the progress was I looked into how to read what's going through pipes. First you can get the PID for each crunch instance with pidof crunch. Once you have that you can watch what's streaming through each individual PID with:

sudo strace -pPID_NUM_HERE -s99999 -e write

From here I could write down the progress, stop a crunch process, and restart the crunch command where I left off by adding a -s option for a start point.

Towards Better Performance

At two character passwords I get about 35 passwords tried per second, at 3 character passwords I get about 12 passwords tried per second. Tested by putting time -v --output=somefile before the command above. It's not fast.

I realize that using pipes, xargs, and bash to run these commands each have their own costs involved and it would be much more expedient for me to write an implementation in Rust to do exactly what's being done above. I'll need to take the source code for AES Crypt and link the library in to Rust to call the C code directly. And either do the same for crunch or just implement the parts I need by hand (the latter is probably better).

It will definitely be more performant if I take out all of the middleware shell stuff and just run pure Rust. Heck if I could take advantage of my GPU that would be cool to. But there's a bit of a learning curve for me to get there.

I hope this information has been useful for others. Feel free to share answers just as enlightening as this one. Thanks!


UPDATE

Ori asked if I had tested if a happy path would work as expected. Turns out I didn't account for that in either case. I've got an updated version of the FishShell script here which works with a happy path.

for password in (ruby script.rb)
  if test -s the_file
    break
  else
    aescrypt -d -p $password the_file.aes 2>/dev/null
  end
end

The test -s tests to see whether the file the_file is blank or not. aescrypt will write an empty file of the same file name being decrypted without the .aes extension in the same directory. It uses the same name for a successful decryption as well. This should be taken into account in all scenarios.

Alright the new updated crunch and xargs code that will work is:

crunch 3 26 abcdefhilmnoprstuvwyz0123456789\ -_. -d 1@ --stdout | \
xargs -r -P 4 -I PASSWORD bash -c \
"! aescrypt -d -p 'PASSWORD' -o 'PASSWORD' encrypted_file.aes \
2>/dev/null; if [[ -s \"PASSWORD\" ]]; then exit 255; fi"

This names the output file whatever the current password is and if the file's not empty it exits for success. I have tested this with an example and it works!

Hopefully I'll get my file decrypted now.

Update 9/22/2017: Program Written

I've written a multi-threaded AES file brute force decryption software called abrute. You need to have aescrypt in the systems executable path for it to work.

5
  • 4
    Are you certain the validation of an unencrypted success works correctly? (Eg did you test a known good scenario?)
    – Ori
    Commented Jun 14, 2017 at 6:58
  • Good catch! The happy path does not work. The aescrypt's decrypt process writes an empty file of the same name without the aes extension during decrypt and removes it on fail. This conflicts with the would be successful result. But even with just 1 xarg process it still won't exit on success. In my testing it works if I use echo instead of crunch so that's what I based this off of. I'm going to retry my first solution above with a bit of a rewrite.
    – 6ft Dan
    Commented Jun 14, 2017 at 8:58
  • I'm in this exact situation. How did it work out for you? Did you ever create a more performant tool?
    – tuxbear
    Commented Aug 28, 2017 at 13:42
  • @tuxbear It's a work in progress. I've still got a PC running on this attempt. It's “still” in the 9 character range. I've created a way to sequence characters in Rust with github.com/danielpclark/digits I just need to write a tool around that to handle decryption attempts in threads or parallel processes.
    – 6ft Dan
    Commented Aug 30, 2017 at 21:09
  • 1
    Thanks for responding, and thanks for your script! I was actually able to decrypt my file using your ruby script and sheer luck. Turns out I've entered my password twice in AESCrypt with a space between them. I tried for a long time with all combinations, and was about to give up when I just wanted to play with your ruby code and entered the same password fragment twice in the prefix array with different casings to see how it behaved. Al voila, the file was unlocked :D
    – tuxbear
    Commented Sep 1, 2017 at 6:49
1

Instead of reviewing your code, hereby an out-of the box solution.

An "intelligent" dictionary attack might be what you're searching for. Although that does require sufficient hardware to speed things up. A static dictionary attack is not what you're searching for (unless you actually do remember the password) but proper software like Password Recovery Toolkit (PRTK) might be able to help you out. The PRTK documentation describes:

During a dictionary attack, PRTK and DNA use the information in the selected profile to create variations, permutations, and combinations of the biographical and user dictionaries. Additionally, it uses phonetic alterations, adds prefixes or suffixes, and substitutes characters.

Source: https://ad-pdf.s3.amazonaws.com/PRTK_DNA_UserGuide.pdf

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .