-
Notifications
You must be signed in to change notification settings - Fork 8
/
Copy pathkey_brute.py
258 lines (227 loc) · 10.2 KB
/
key_brute.py
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
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
# Standalone encryption key brute forcer by Daniel Crowley, X-Force Red
# Based on AES trial decryption FeatherModule by Daniel Crowley
#
from Crypto.Cipher import DES3
from Crypto.Cipher import DES
from Crypto.Cipher import AES
import argparse
from binascii import unhexlify
from base64 import b64decode as b64d
from string import rstrip
import sys
tool_desc = '''
A wordlist-based encryption key brute force utility capable of finding keys by trial decryption
of one or more samples followed by a check for correct decryption involving padding
correctness checks or known plaintext matching.
'''
parser = argparse.ArgumentParser(description='Encryption key brute forcer')
parser.add_argument('keylist', help='''A list of hex-encoded keys
newline separated in a single file, such as the
ones produced by the generator scripts.''')
parser.add_argument('sample_file', help='''A list of samples of data
encrypted with the same encryption key for cracking''')
parser.add_argument('-a', '--algorithm', default='AES', choices=['AES',
'3DES','DES'],help='''The encryption algorithm used to encrypt
the samples''')
parser.add_argument('-m', '--mode', default=None, help='''The block cipher
mode of operation to use, if known.''')
parser.add_argument('--iv',default=None, help='''A known initialization
vector (IV) for decrypting in CBC mode''')
parser.add_argument('-c', '--crib', help='''A string to check for in
decrypted data to help identify when the right
key may have been found. If not provided, the
decrypted data will instead be checked for
correct pkcs7 padding.''', default=None)
parser.add_argument('-e', '--encoding', help='''The encoding scheme used
on the samples. Can be "hex" or "base64".''',
choices=['hex', 'base64'], default='hex')
parser.add_argument('-o','--output', default=None, help='''If specified, a key file
will be written with the provided name containing all keys
that produced successful decryption.''')
args = parser.parse_args()
# parse sample file
sample_fh = open(args.sample_file, 'r')
samples = map(rstrip, sample_fh.readlines())
if args.encoding == 'base64':
samples_decoded = map(b64d, samples)
else:
samples_decoded = map(unhexlify, samples)
sample_fh.close()
# prepare constants
if args.algorithm == 'AES':
blocksize = 16
keysize = 16
elif args.algorithm == 'DES':
blocksize = 8
keysize = 8
elif args.algorithm == '3DES': #TODO: support 2-key EDE
blocksize = 8
keysize = 24
else:
raise ValueError('Specified algorithm is unsupported')
# prepare output file
if args.output != None:
output_fh = open(args.output, 'a')
blocksizex2 = 2 * blocksize #probably unnecessary optimization but we save multiply operations
# prepare mode selection
do_ecb = args.mode in [None, 'ECB']
do_cbc = args.mode in [None, 'CBC']
# Prepare cipher in ECB mode
def prepare_ecb(key):
if args.algorithm == 'AES':
cipher = AES.new(key, AES.MODE_ECB)
return cipher
elif args.algorithm == 'DES':
cipher = DES.new(key, DES.MODE_ECB)
return cipher
elif args.algorithm == '3DES':
try:
cipher = DES3.new(key, DES3.MODE_ECB)
except ValueError:
cipher = DES.new(key[:8], DES.MODE_ECB)
return cipher
# Prepare cipher in CBC mode
def prepare_cbc(key, iv):
if args.algorithm == 'AES':
cipher = AES.new(key, AES.MODE_CBC, iv)
return cipher
elif args.algorithm == 'DES':
cipher = DES.new(key, DES.MODE_CBC, iv)
return cipher
elif args.algorithm == '3DES':
try:
cipher = DES3.new(key, DES3.MODE_CBC, iv)
except ValueError:
cipher = DES.new(key[:8], DES.MODE_CBC, iv[:8])
return cipher
# Perform the actual brute force
def key_brute(samples):
# Check that the samples are the correct size
if not all([len(sample) % blocksize == 0 for sample in samples]):
return False
def decrypt_and_check(cipher, ciphertext):
'''Branch to correct decrypt and check method based on user args'''
if args.crib != None:
return decrypt_and_check_crib(cipher, ciphertext)
else:
return decrypt_and_check_pkcs7(cipher, ciphertext)
def decrypt_and_check_crib(cipher, ciphertext):
'''Decrypt under constructed cipher and return True or False indicating presence of crib'''
pt = cipher.decrypt(ciphertext)
return (args.crib in pt)
def decrypt_and_check_pkcs7(cipher, ciphertext):
'''Decrypt under constructed cipher and return True or False indicating correct pkcs7 padding'''
pt = cipher.decrypt(ciphertext)
last_byte = ord(pt[-1])
if last_byte > blocksize:
return False
elif pt[-last_byte:] == chr(last_byte)*last_byte:
return True
else:
return False
results = []
# filter samples into one-block samples and multi-block samples
one_block_samples = filter(lambda x: len(x) == blocksize, samples)
multi_block_samples = filter(lambda x: len(x) > blocksize, samples)
if len(multi_block_samples) == 1 and args.crib == None:
print('[*] Only a single multi-block sample exists. This has a 1 in 256 chance of false positives with the CBC test.')
if len(one_block_samples) == 1 and args.crib == None:
print('[*] Only a single one-block sample exists. This has a 1 in 256 chance of false positives with the ECB, CBC key-as-IV, and CBC known IV tests.')
# parse wordlist
with open(args.keylist, 'r') as keys_fh:
keys = keys_fh.readlines(100000)
num_keys = 0
num_candidate_keys = 0
while keys:
num_keys += 100000
sys.stdout.write('\rNumber of keys processed: %d | Number of candidate keys: %d' % (num_keys, num_candidate_keys))
sys.stdout.flush()
keys = map(rstrip, keys)
keys = map(unhexlify, keys)
for key in keys:
if len(key) != keysize:
print("Bad key size, skipping key...")
continue
# set all bad_decryption flags to False
ecb_bad_decrypt = cbc_key_as_iv_bad_decrypt = cbc_bad_decrypt = cbc_known_iv_bad_decrypt = False
# ECB
if do_ecb:
for sample in samples:
cipher = prepare_ecb(key)
# If any decryption fails to produce valid padding, flag bad ECB decryption and break
if args.crib == None:
sample = sample[-blocksize:]
if decrypt_and_check(cipher, sample) == False:
ecb_bad_decrypt = True
break
else:
ecb_bad_decrypt = True
# CBC last block with second to last block as IV
if do_cbc:
if len(multi_block_samples) != 0:
for sample in multi_block_samples:
if args.crib == None:
cipher = prepare_cbc(key, sample[-blocksizex2:-blocksize])
# If any decryption fails to produce valid padding, flag bad CBC decryption and break
if decrypt_and_check(cipher, sample[-blocksize:]) == False:
cbc_bad_decrypt = True
break
elif args.iv != None:
cipher = prepare_cbc(key, unhexlify(args.iv))
if decrypt_and_check(cipher, sample) == False:
cbc_bad_decrypt = True
break
else:
cipher = prepare_cbc(key, key)
if decrypt_and_check(cipher, sample) == False:
cbc_bad_decrypt = True
break
else:
cbc_bad_decrypt = True
if len(one_block_samples) != 0:
if args.iv != None:
cbc_key_as_iv_bad_decrypt = True
# CBC with entered IV
for sample in one_block_samples:
cipher = prepare_cbc(key, unhexlify(args.iv))
# If any decryption fails to produce valid padding, flag bad CBC decryption and break
if decrypt_and_check(cipher, sample) == False:
cbc_known_iv_bad_decrypt = True
break
else:
cbc_known_iv_bad_decrypt = True
# CBC with key as IV
for sample in one_block_samples:
cipher = prepare_cbc(key, key)
# If any decryption fails to produce valid padding, flag bad CBC_key_as_IV decryption and break
if decrypt_and_check(cipher, sample) == False:
cbc_key_as_iv_bad_decrypt = True
break
else:
cbc_known_iv_bad_decrypt = cbc_key_as_iv_bad_decrypt = True
else:
cbc_bad_decrypt = cbc_key_as_iv_bad_decrypt = cbc_known_iv_bad_decrypt = True
if any([not ecb_bad_decrypt, not cbc_bad_decrypt, not cbc_key_as_iv_bad_decrypt, not cbc_known_iv_bad_decrypt]):
num_candidate_keys += 1
if args.output:
output_fh.write(key.encode('hex') + '\n')
if not ecb_bad_decrypt:
results.append(key.encode('hex') + ' may be the correct key in ECB mode or CBC mode with static all-NUL IV.')
if not cbc_bad_decrypt:
results.append(key.encode('hex') + ' may be the correct key in CBC mode, IV unknown.')
if not cbc_key_as_iv_bad_decrypt:
results.append(key.encode('hex') + ' may be the correct key and static IV in CBC mode.')
if not cbc_known_iv_bad_decrypt:
results.append(key.encode('hex') + ' may be the correct key in CBC mode using the provided IV.')
keys = keys_fh.readlines(100000)
print('Potentially correct encryption keys:')
print('-' * 80)
print('\n'.join(results))
return results
# run the brute force
results = key_brute(samples_decoded)
if results == False:
# TODO: Be more specific
print('Something went wrong while attempting to brute force.')
if args.output:
output_fh.close()