summaryrefslogtreecommitdiff
path: root/Assignment3/break.py
blob: 7666d337b1c1decc0d180ef65bacc7c254c960da (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
#!/usr/bin/python
import sys, getopt, binascii, random, time, os.path, time
from Crypto.Cipher import DES

dictionary = {}

# From http://stackoverflow.com/a/843846/1544337
# 1 for odd parity, 0 for even parity
# Was used only in a previous version of nthByte
# def parity(b):
#     c = 0
#     while b != 0:
#         c += 1
#         b &= b - 1
#     return c % 2

# Find the nth possibility for a byte with odd parity
oddBytes = [1,2,4,7,8,11,13,14,16,19,21,22,25,26,28,31,32,35,37,38,41,42,44,47,49,50,52,55,56,59,61,62,64,67,69,70,73,74,76,79,81,82,84,87,88,91,93,94,97,98,100,103,104,107,109,110,112,115,117,118,121,122,124,127,128,131,133,134,137,138,140,143,145,146,148,151,152,155,157,158,161,162,164,167,168,171,173,174,176,179,181,182,185,186,188,191,193,194,196,199,200,203,205,206,208,211,213,214,217,218,220,223,224,227,229,230,233,234,236,239,241,242,244,247,248,251,253,254]
def nthByte(n):
    return oddBytes[n]
    # c = -1                        # This is the old version. The new version uses a faster lookup table.
    # b = 0
    # while c != n:
    #     b += 1
    #     if parity(b) == 1:
    #         c += 1
    # return b

# Find the nth key in which all bytes have odd parity
def nthKey(n):
    key = ''
    for b in range(7,-1,-1):
        key += chr(nthByte((n >> b*7) & 0x7f))
    return key

# Create a dictionary for the first 2^l keys and a given plaintext
def createDictionary(plaintext, l):
    for n in range(0, pow(2, l)):
        encryption = DES.new(nthKey(n), DES.MODE_ECB).encrypt(plaintext)
        if encryption in dictionary:
            dictionary[encryption].append(n)
        else:
            dictionary[encryption] = [n]

# Read the dictionary from a file if it exists, or generate it
def readOrMakeDictionary(plaintext, l):
    if os.path.isfile('dict-' + binascii.b2a_hex(plaintext) + '-' + str(l) + '.txt'):
        with open('dict-' + binascii.b2a_hex(plaintext) + '-' + str(l) + '.txt') as f:
            for line in f:
                dictionary[binascii.a2b_hex(line[:16])] = [int(x) for x in line[17:-1].split(',')]
        return False
    else:
        createDictionary(plaintext, l)
        return True

# Save the dictionary to a file
def saveDictionary(plaintext, l):
    dictionary_file = open('dict-' + binascii.b2a_hex(plaintext) + '-' + str(l) + '.txt', 'w')
    for encryption in dictionary:
        dictionary_file.write(binascii.b2a_hex(encryption) + ':' + ','.join(str(x) for x in dictionary[encryption]) + '\n')
    print 'Saved dictionary as dict-' + binascii.b2a_hex(plaintext) + '-' + str(l) + '.txt'

def main(argv):
    l = 24
    plaintext = ''
    ciphertext = ''
    save_dictionary = False

    # Parse arguments. Use -h for help.
    try:
        opts, args = getopt.getopt(argv, "hl:p:c:s")
    except getopt.GetoptError:
        usage()
        sys.exit(2)

    for opt, arg in opts:
        if opt == '-h':
            usage()
            sys.exit()
        elif opt == '-l':
            l = int(arg)
        elif opt == '-p':
            plaintext = binascii.a2b_hex(arg)
        elif opt == '-c':
            ciphertext = binascii.a2b_hex(arg)
        elif opt == '-s':
            save_dictionary = True

    assert plaintext != ''
    assert ciphertext != ''

    # Make the dictionary if it doesn't exist yet
    print 'Making dictionary (p:' + binascii.b2a_hex(plaintext) + ';l:' + str(l) + ')...',
    timer = time.clock()
    if not readOrMakeDictionary(plaintext, l):
        save_dictionary = False
    print str(time.clock() - timer) + 's'

    if save_dictionary:
        saveDictionary(plaintext, l)

    # Find matches
    print 'Finding matches...',
    timer = time.clock()
    time_key = time_decryption = time_matching = 0
    matches = []
    for k in range(0, pow(2,l)):
        # Generate key
        time_t = time.clock()
        key = nthKey(k)
        time_key += time.clock() - time_t
        
        # Decrypt
        time_t = time.clock()
        des = DES.new(key, DES.MODE_ECB)
        decryption = des.decrypt(ciphertext)
        time_decryption += time.clock() - time_t
        
        # Find matches
        time_t = time.clock()
        if decryption in dictionary:
            [matches.append({'k1': nthKey(i), 'k2': key}) for i in dictionary[decryption]]
        time_matching += time.clock() - time_t
    print str(time.clock() - timer) + 's'

    print 'Key generation:      ' + str(time_key) + 's'
    print 'Decryption:          ' + str(time_decryption) + 's'
    print 'Matching dictionary: ' + str(time_matching) + 's'

    for match in matches:
        print 'k1: ' + binascii.b2a_hex(match['k1']) + '; k2: ' + binascii.b2a_hex(match['k2'])

def usage():
    print 'Usage: break.py -p <plaintext> -c <ciphertext> [-l <keylength>] [-s]'
    print '  plaintext  : plaintext of the known-plaintext attack'
    print '  ciphertext : ciphertext of the known-plaintext attack'
    print '  keylength  : pick keys from the first 2^l keys (default: 24)'
    print '  -s         : save the dictionary for this plaintext and keylength to a file'

if __name__ == "__main__":
    main(sys.argv[1:])