Friday, September 2, 2011

Password combination generator

After several months without mounting an encrypted filesystem, i found out i had forgotten its passphrase. However, i remembered the words i had used, but not the case sensitivity of each character nor the characters i'd replaced for numbers or symbols('o' for '0', 's' for '5', etc). Moreover, i didn't remember which symbol i'd used to separate these words(i could have used '_', '!', '#', etc..).

So after spending half an hour trying out every combination of upper and lower case characters, digits and symbols, i came out with a script to do this automatically.

This script expects several words in lower case as arguments, printing them in the same order, but modifying their case, and transforming characters to numbers or symbols, using a conversion map. There's also a '-e' parameter which allows the user to directly execute a certain command for each combination. The command to execute must contain the string "{0}", which will indicate where each combination will be replaced.

For example, in my quest to mount my encrypted file, i used:
./dictionary.py -e "truecrypt -p {0} --non-interactive encrypted.file" one two three
Where "one two three" are the words which will be used the perform character combinations.

This is the script:

#!/usr/bin/python
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#       
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
# GNU General Public License for more details.
#       
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
# MA 02110-1301, USA.


import sys, os


class Word:
    # Dictionary used for conversions between characters, other than
    # simple lower-to-upper conversions. Add here as many as you want,
    # as long as you don't create a cycle ;).
    leet_map = {'A' : '4',
                'E' : '3',
                'I' : '1',
                'O' : '0',
                '1' : '!',
                'S' : '5',
                '5' : '$'
               }

    # The characters to append after each word. Only one of these will
    # be appended at a time.
    appended_list = [' ', '!', '_']
    
    # Appended characters to avoid if this Word is last in the sequence.
    # By default, no spaces will be appended at the end of the phrase,
    # however, they will be included in the middle of it.
    appended_list_avoid = [' ']
    
    
    def __init__(self, base, is_last = False):
        self.base = list(base)
        self.current = list(base)
        self.current_index = 0
        self.is_last = is_last
        self.appended = ''
        self.done = False

    # Increment a particular character. Add any conversion rules in here.
    def _next_char(self, char):
        if char.isalpha():
            if char.islower():
                return char.upper()
            else:
                if char in Word.leet_map:
                    return Word.leet_map[char]
                return char
        else:
            return char if not char in Word.leet_map else Word.leet_map[char]

    # Increment the word. Should only be called if has_next returns True.
    def next(self):
        this_char = self._next_char(self.current[self.current_index])
        if this_char == self.current[self.current_index]:
            # No more conversions for this char, reset previous ones.
            for i in range(self.current_index + 1):
                self.current[i] = self.base[i]
            self.current_index += 1
            if self.current_index < len(self.base):
                self.next()
                self.current_index = 0
            else:
                # Find the current appended character
                try:
                    index = Word.appended_list.index(self.appended)
                except:
                    index = -1
                if index == len(Word.appended_list) - 1:
                    # Appended char cannot be incremented. We're done.
                    self.done = True
                else:
                    try:
                        if self.is_last:
                            while Word.appended_list[index+1] in Word.appended_list_avoid:
                                index += 1
                        # Increment the appended character.
                        self.appended = Word.appended_list[index+1]
                        self.current_index = 0
                    except:
                        self.done = True
                
        else:
            self.current[self.current_index] = this_char

    # Returns boolean indicating whether this Word can be incremented.
    def has_next(self):
        return not self.done

    # Returns the current string.
    def get_current(self):
        return ''.join(self.current) + self.appended

    # Resets every field in this Word.
    def reset(self):
        self.current = list(self.base)
        self.current_index = 0
        self.appended = ''
        self.done = False
        

class Wordlist:
    def __init__(self, words):
        self.words = []
        for i in words[:-1]:
            self.words.append(Word(i))
        self.words.append(Word(words[-1], True))

    # Increment the words one step.
    def _inc(self, index):
        # No words to increment left
        if index == len(self.words):
            return index
        if self.words[index].has_next():
            self.words[index].next()
            if not self.words[index].has_next():
                # We've got carry. Reset words[0:index], 
                # then increment words[index+1] and propagate.
                for i in range(index+1):
                    self.words[i].reset()
                return self._inc(index+1)
            else:
                return index
        else:
            return index + 1

    def do_action(self, to_exec):
        line = ''.join(map(lambda x: ''.join(x.get_current()), self.words))
        if len(to_exec) == 0:
            print line
        else:
            os.system(to_exec.format('"' + line + '"'))

    def generate(self, to_exec):
        i = 0
        while i < len(self.words):
            self.do_action(to_exec)
            i = self._inc(0)
            


def usage():
    print ' Usage: ' + sys.argv[0] + ' [-e EXEC] <WORD1> [WORD2] [WORD3]\n'
    print ' If -e option is used, then the next parameter is the command to execute'
    print ' for each word combination. The command must contain {0} where each '
    print ' combination will be included. Example: "echo {0}"\n'
    print ' If no command is given, then each permutation will be printed to stdout'
    
    exit(1)

if __name__ == '__main__':
    if len(sys.argv) == 1 or '-h' in sys.argv:
        usage()

    args = sys.argv[1:]
    to_exec = ''

    if args[0] == '-e':
        if len(args) <= 2:
            usage()
        to_exec = args[1]
        args = args[2:]

    words = Wordlist(args)
    words.generate(to_exec)



Hope you find it useful!

1 comment: