Example #1
0
 def __init__(self):            
     # initialize a string of chars that cannot be encrypted
     self.illegalChars = string.punctuation + ' ' 
     # initialize the character used to pad messages to fit the blocks
     self.padChar = 'x'
     # create a reference to the matrix operations
     self.matrixOps = MatrixOperations()
     # array of units
     self.units = [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
Example #2
0
# key used to encrypt the message
key = [ [1, 0, 1, 1],
        [1, 0, 1, 0],
        [0, 1, 0, 0],
        [1, 1, 0, 0]]

# dimension 'n' of the (n x n) key matrix
keyDim = len(key)

print "Encrypting:\n%s\n" % msg
print "Using key:\n%s\n" % key
 
# get the binary representation of the message
binMsg = ''.join(format(ord(ch), 'b').zfill(8) for ch in msg)
# set up a reference to the crypto system
matrixOps = MatrixOperations()

# holds the encrypted message
encryptedMsg = ""

# holds a chunk of the encrypted message
binChunk = ""

# loop through the binary
for i in range(0, len(binMsg), keyDim):
    # extract a block from the message
    binBlock = map(int, binMsg[i:i+keyDim])
    # encrypt the block using the key
    encryptedBlockMatrix = matrixOps.matrixMult(key, map(list, zip(binBlock)))
    # because the encrypted block is an n x 1 matrix, the rows
    # need to be joined into a 1D list and then to a string
Example #3
0
class HillSystem:

    def __init__(self):            
        # initialize a string of chars that cannot be encrypted
        self.illegalChars = string.punctuation + ' ' 
        # initialize the character used to pad messages to fit the blocks
        self.padChar = 'x'
        # create a reference to the matrix operations
        self.matrixOps = MatrixOperations()
        # array of units
        self.units = [1, 3, 5, 7, 9, 11, 15, 17, 19, 21, 23, 25]
       
    def encrypt(self, msg, key, decrypt=False, zeroSystem=False):
        keyDim = len(key)
        msg = msg.lower().translate(string.maketrans("", ""), self.illegalChars)
        # determine if we need to pad 'x's at the end of the msg to make it fill a block
        numToFill = (keyDim - (len(msg) % keyDim)) % keyDim
        msg += numToFill * self.padChar
        encryptedMsg = ""
        for block in [msg[i:i+keyDim] for i in range(0,len(msg),keyDim)]:           
            encryptedMatrix = self.matrixOps.matrixMult(key, [[self.letToInt(char, zeroSystem)] for char in block])
            for encryptedRow in encryptedMatrix:
                cipherInt = encryptedRow[0] % 26
                
                # cover the 'z' case
                if cipherInt == 0 and not zeroSystem:
                    cipherInt = 26
                encryptedMsg += self.intToLet(cipherInt, zeroSystem)
        return encryptedMsg if decrypt else encryptedMsg.upper()
    
    def decrypt(self, msg, key, invertKey=False, zeroSystem=False):
        # remove spaces from the message
        msg = ''.join(msg.split())
        # invert the key for decryption of the msg
        if invertKey:
            return self.encrypt(msg.lower(), self.matrixOps.invertMatrix(key), zeroSystem=zeroSystem, decrypt=True)
        return self.encrypt(msg.lower(), key, zeroSystem=zeroSystem, decrypt=True)
        
    # The parameters are tuples of the constants
    # for each equation in the form
    # ax + by = c(mod 26)
    # where a, b, and c are the constants in the 
    # parameter tuples
    def solveLinEquPair(self, equConstants1, equConstants2):
        (a1, b1, c1) = equConstants1
        (a2, b2, c2) = equConstants2
        return filter(lambda tup: (a1*tup[0] + b1*tup[1]) % 26 == c1 and (a2*tup[0] + b2*tup[1]) % 26 == c2, [(x,y) for x in range (26) for y in range(26)])               
        
    # The parameters are tuples of the constants
    # for each equation in the form
    # ax + by = c(mod 26)
    # where a, b, and c are the constants in the 
    # parameter tuples
    def solveLinEqu(self, equConstants):
        (a, b, c) = equConstants
        
        return filter(lambda tup: (a*tup[0] + b*tup[1]) % 26 == c, [(x,y) for x in range (26) for y in range(26)])

    def diAndKeyToLet(self, digraph, key, zeroSystem):
        d1Int = self.letToInt(digraph[0], zeroSystem)
        d2Int =  self.letToInt(digraph[1], zeroSystem)
        letterInt = (d1Int*key[0] + d2Int*key[1]) % 26
        return self.intToLet(letterInt, zeroSystem)
    
    # intPairs is a list of lists of 2-tuples
    def mostFreqIntPairs(self, intPairs):
        flatList = list(itertools.chain(*intPairs))
        # count pair frequencies
        freqDict = {}
        for pair in flatList:
            if pair in freqDict:
                freqDict[pair] += 1
            else:
                freqDict[pair] = 1
        return sorted(freqDict.items(), key=operator.itemgetter(1), reverse=True)
    
    def letToInt(self, let, zeroSystem=False):
        return ord(let.lower()) - 96 - (1 if zeroSystem else 0)
        
    def intToLet(self, int, zeroSystem=False):
        return chr(int+96 + (1 if zeroSystem else 0))
    
    ## psuedo algorithm/code for discovering the potential key matrices
    # find the most frequent digraph
    #   assume the digraph maps to 'th'
    #   create a set of constants for the linear equations using the numeric values of
    #       each letter
    #   i.e. 'OT' mapping to 'th' would produce (14, 19, 19) and (14, 19, 7) using a zero_system
    # get the list of digraphs that follow the most frequent digraph
    #   create a list of constants tuples assuming that these digraphs map to 'e*'
    #   i.e. 'GW' mapping to 'e*' would produce (6, 22, 4) using the zero_system
    # solve for x,y in the pairs of linear equations for ax + by = c where c is the integer
    # representation of 't' or 'e' depending on the equation
    #   these are the potential solutions mapping the first letter of the digraph
    #   to 't' and assuming that 'e' follows the letter after 't'
    # from all of the possible keys, if the same key shows up as a solution to each
    # equation, or it simply shows up more frequently than other keys for (a, b) then
    #   use that key for a and b
    # get a list of possible keys for cx + dy = e where e is the integer representation of 'h'
    # run through the 1 or 2 best keys for a, b and all of the keys for c, d and decipher
    # the ciphertext using them.
    # Look at the output and try to find the english one
    def super_decrypt(self, msg, digraphOverride=None, zeroSystem=False):
        diFreq = DigraphFrequency()
        letFreq = LetterFrequency()
        if digraphOverride is None:
            (mfd, mfdf) = diFreq.getFrequencies(msg)[1][0]
        else:        
            # most frequent digraph
            mfd = digraphOverride
        mfdIntsTuple = diFreq.digraphToIntTuple(mfd, zeroSystem)
        mfdConsts = [mfdIntsTuple + (diFreq.letToInt('t', zeroSystem),), 
                    mfdIntsTuple + (diFreq.letToInt('h', zeroSystem),)]   
        followingDigraphs = diFreq.getFollowingDigraphs(msg, mfd)
        followingDigraphConstants = [diFreq.digraphToIntTuple(diTup, zeroSystem) + (diFreq.letToInt('e', zeroSystem),) for diTup in followingDigraphs]
        # the possible values of a and b in the key matrix:
        # a b
        # c d        
        abConsts = mfdConsts[0]
        abPossibleValues = [self.solveLinEquPair(abConsts, folDiConsts) for folDiConsts in followingDigraphConstants]
        mostLikelyABValues = self.mostFreqIntPairs(abPossibleValues)
        bestABValues = []
        for abValue, count in mostLikelyABValues:
            # if the abValue works for all equations, it is very likely
            # that it is the correct values for a and b
            if count == len(followingDigraphConstants):
                bestABValues.append(abValue)
        # if there are still no abValues 
        #   (i.e. no a, b pair works for all of the equations
        #   created by assuming that the digraphs following the mfd correspond to 'e*')
        # then just pick the top 2        
        if not bestABValues:
            bestABValues = mostLikelyABValues[:2]
            
        # if there are two a,b pairs to try out, do the following
        if len(bestABValues) > 1:
            # of the two most likely values for both a and b, go through every digraph in the message
            # and determine the int value of a1x+b1y, and a2x + b2y where x and y are the two letters
            # in the digraph and a1,b1 and a2,b2 are the abValues
            # convert the resulting int values to letters, and see which key pair produced the more
            # frequent letter
            # keep a running tally of the more frequent letters for each pair to see which one is
            # more likely to be the actual key
            key1 = bestABValues[0][0]
            key2 = bestABValues[1][0]
            msgDigraphs = diFreq.getUniqueDigraphs(msg)
            key1Lets = [self.diAndKeyToLet(digraph, key1, zeroSystem) for digraph in msgDigraphs]
            key2Lets = [self.diAndKeyToLet(digraph, key2, zeroSystem) for digraph in msgDigraphs]
            
            englishLetterFrequenciesDict = letFreq.getStandardFrequencies()[0]
            key1MoreFreqCounter = 0
            key2MoreFreqCounter = 0
            for i in range(0, len(key1Lets)):
                if englishLetterFrequenciesDict[key1Lets[i]] > englishLetterFrequenciesDict[key2Lets[i]]:
                    key1MoreFreqCounter += 1
                else:
                    key2MoreFreqCounter += 1
            # try the more likely key first!
            abKeys = [key1, key2] if key1MoreFreqCounter > key2MoreFreqCounter else [key2, key1]
            print "Key %s is the more likely of the keys %s and %s" % (abKeys[0], key1, key2)
        # there is only one AB value, so it must be the correct key pair for a,b!!!
        else:
            abKeys = bestABValues
            
        # find possible values for c and d that solve the equation
        cdConsts = mfdConsts[1]
        cdKeys = self.solveLinEqu(cdConsts)
        
        ## print out a description of the progress thus far
        if digraphOverride is None:
            print "\nThe most frequent digraph (MFD) in the message was:\n\t%s' with frequency %0.2f%%" % (mfd, mfdf)
        else:
            print "\nThe most frequent digraph (MFD) in the message was manually set as:\n\t%s'" % (mfd)
            
        print "\nMapping '%s' to 'th' resulted in the following equations:\n\t%da + %db = %d\n\t%dc + %dd = %d" % (mfd, abConsts[0], abConsts[1], abConsts[2], cdConsts[0], cdConsts[1], cdConsts[2])
        print "\nLooking at digraphs following '%s' resulted in the following digraphs:\n\t%s" % (mfd, followingDigraphs)
        print "\nMapping the above digraphs to 'e*' resulted in the following equations:"
        for fdc in followingDigraphConstants:
            print "\t%da + %db = %d" % (fdc[0], fdc[1], fdc[2])        
        print "\nSolving for 'a' and 'b' in these equations resulted in the following most\nlikely key(s):"
        for abKey in abKeys:
            print "\ta = %d, b = %d" % (abKey[0], abKey[1])
        print "\nSolving for 'c' and 'd' in the original set of equations resulted in the\nfollowing most likely key(s):"
        for cdKey in cdKeys:
            print "\tc = %d, d = %d" % (cdKey[0], cdKey[1])        
        print "\nThese keys as matrices in the form [[a, b], [c, d]] will now be brute forced so \nthat the user can attempt to spot the correct deciphering of the ciphertext.\n\n"
            
        for abKey in abKeys:
            for cdKey in cdKeys:
                key = [list(abKey), list(cdKey)]
                print "Trying decryption key: %s, encryption key %s" % (key, self.matrixOps.invertMatrix(key))
                print self.decrypt(msg, key, False, zeroSystem)