In: Computer Science
Create a Python Module named piphash_rainbow.py that Creates a rainbow table named sixdigithash_rainbow.csv that stores all possible six-digit pins and their corresponding md5, sha1, sha256, and sha512 hashes.
lettersLower = 'abcdefghijklmnopqrstuvwxyz'
lettersUpper = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ'
numbers = "0123456789"
allChars = lettersLower + lettersUpper + numbers
from hashlib import md5, sha1
def generatePasswords(nbChar, chars=lettersLower):
    """Generates a list of all the possible passwords given certain parameters.
nbChar: Number of characters in the password.
chars: List of covered characters.
Note: only for testing purposes.
"""
    results = []
    nbDifferentChars = len(chars)
    for i in range(nbDifferentChars**nbChar):
        word = ""
        for j in range(nbChar):
            word = chars[i % nbDifferentChars] + word
            i //= nbDifferentChars
        results.append(word)
    return results
class RainbowTable:
    """Rainbow table: Structure to crack hashed passwords.
"""
    # Dictionary for finding hash functions by their name.
    hashFunctions = {'': None,
                     sha1.__name__ : sha1,
                     'sha1' : sha1,
                     md5.__name__ : md5,
                     'md5' : md5}
    
    def __init__(self, columns=0, chars="", pwdLength=0, func='', rows=1000):
        """Initializes the rainbow table.
columns: Length of a chain, i.e. number of times the password at the start of the chain is hashed and reduced.
chars: List of characters covered.
pwdLength: Length of the passwords.
func: Name of the hashing function.
rows: Number of chains.
"""
        from RB import RBTree, rbnode
        self.table = RBTree()
        if columns > 0:
            self.columns = columns
            self.chars = chars
            self.pwdLength = pwdLength
            self.func = RainbowTable.hashFunctions[func]
            for i in range(rows):
                pwd = self.randomPassword()
                hashV = self.createChain(pwd)
                self.table.insert(hashV, pwd)
    def __repr__(self):
        """Prints the content of the table.
"""
        return repr(self.table._root)
    def writeToFile(self, output):
        """Writes rainbow table into a file, so that it can be recovered at a different time later.
output: Name of the file to write to.
"""
        f = open(output, 'w')
        data = [self.columns, self.chars, self.pwdLength, self.func.__name__]
        data = [str(x) for x in data]
        f.write(" ".join(data))
        f.write("\n")
        f.write(repr(self))
        f.close()
    def readFromFile(self, input):
        """Read a rainbow table from a file.
input: Name of the file to read from.
"""
        f = open(input, "r")
        line = f.readline()
        line = line.strip().split(sep=" ", maxsplit=3)
        self.columns, self.chars, self.pwdLength, self.func = line
        self.columns = int(self.columns)
        self.pwdLength = int(self.pwdLength)
        self.func = RainbowTable.hashFunctions[self.func]
        line = f.readline()
        while line != '':
            pwd, hashV = line.strip().split(sep=" ", maxsplit=1)
            self.table.insert(hashV, pwd)
            line = f.readline()
        f.close()
    def _find(self, hashV):
        """Find the passwords in the table corresponding to the given hash.
hashV: Hash to find.
Returns a list of corresponding starting passwords.
"""
        return self.table.search(hashV)
    def hashWord(self, word):
        """Hash a word.
word: Word to hash.
Returns the hash of the word.
"""
        word = word.encode('utf-8')
        return self.func(word).hexdigest()
    def reduce(self, hashV, column):
        """Reduces a hash.
hashV: Hash to reduce.
column: Column to hash at.
Returns a valid password.
"""
        results = []
        # Cast hash from str to int then decompose into bytes
        byteArray = self.getBytes(hashV)
        for i in range(self.pwdLength):
            index = byteArray[(i + column) % len(byteArray)]
            newChar = self.chars[index % len(self.chars)]
            results.append(newChar)
        return "".join(results)
    def getBytes(self, hashV):
        """Transforms a hash into a list of bytes.
hashV: Hash to transform into bytes.
Returns a list of bytes.
"""    
        results = []
        remaining = int(hashV, 16)
        while remaining > 0:
            results.append(remaining % 256)
            remaining //= 256
        return results
        
    def createChain(self, pwd):
        """Creates a chain.
pwd: Password to start the chain with.
Returns the hash at the end of the chain.
"""
        for col in range(self.columns):
            hashV = self.hashWord(pwd)
            pwd = self.reduce(hashV, col)
        return hashV
    def randomPassword(self):
        """Generates a random password.
Returns the generated password.
"""
        from random import randrange
        pwd = ""
        charsLength = len(self.chars)
        for i in range(self.pwdLength):
            pwd += self.chars[randrange(charsLength)]
        return pwd
    def crackHash(self, startHash):
        """Tries to crack a hash.
startHash: Hash to crack.
Returns the resulting password, if one is found, '' otherwise.
"""
        for col in range(self.columns, -1, -1):
            hashV = self._getFinalHash(startHash, col)
            pwdList = self._find(hashV)
            for pwd in pwdList:
                resPwd = self._findHashInChain(pwd, startHash)
                if resPwd != None:
                    return resPwd
        return ''
    def _getFinalHash(self, startHash, startCol):
        """Returns the hash at the end of a chain, starting from a hash and at a given column.
startHash: Hash to start with.
startCol: Column to start from.
Returns the hash at the end of a chain.
"""
        hashV = startHash
        for col in range(startCol, self.columns-1):
            pwd = self.reduce(hashV, col)
            hashV = self.hashWord(pwd)
        return hashV
    def _findHashInChain(self, startPwd, startHash):
        """Tries to find a hash in a chain.
startPwd: Password at the beginning of a chain.
startHash: Hash to find.
Returns the corresponding password if one is found, None otherwise.
"""
        hashV = self.hashWord(startPwd)
        if hashV == startHash:
            return startPwd
        col = 0
        # hash and reduce until the password has been found or the end of the chain has been reached.
        while col < self.columns:
            pwd = self.reduce(hashV, col)
            hashV = self.hashWord(pwd)
            if hashV == startHash:
                # If the password has been, return it
                return pwd
            col += 1
        # The password hasn't been found.
        return None
    def allPasswords(self):
        """Returns the list of all password this table could and should cover.
Note: only for testing purposes.
"""
        res = []
        length = len(self.chars)
        for i in range(length**self.pwdLength):
            pwd = ""
            for j in range(self.pwdLength):
                pwd += self.chars[i % length]
                i //= length
            res.append(pwd)
        return res
    
    def testWord(self, word):
        """Tries to find a password from its hash.
word: Word to find.
Return the result of trying to crack the password of the hash.
Note: only for testing purposes.
"""
        return self.crackHash(self.hashWord(word))
    def testWords(self, words):
        """Tests a list of words from their hash.
words: Lists of words to find.
Note: only for testing purposes.
"""
        for word in words:
            print(word, self.crackHash(rain.hashWord(word)))
    
from testRainbow import *
#rain = RainbowTable() ; rain.readFromFile("D:/Coding/Python/RainbowTable/rain.txt")
#rain = RainbowTable(100, lettersLower, 3, 'md5')