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.