Coursera_Stanford密码学公开课 Programming Assignment 4

问题:

In this project you will experiment with a padding oracle attack against a toy web site hosted at crypto-class.appspot.com. Padding oracle vulnerabilities affect a wide variety of products, including secure tokens. This project will show how they can be exploited. We discussed CBC padding oracle attacks inLecture 7.6, but if you want to read more about them, please see Vaudenay's paper

Now to business. Suppose an attacker wishes to steal secret information from our target web site crypto-class.appspot.com. The attacker suspects that the web site embeds encrypted customer data in URL parameters such as this:

http://crypto-class.appspot.com/po?er=f20bdba6ff29eed7b046d1df9fb7000058b1ffb4210a580f748b4ac714c001bd4a61044426fb515dad3f21f18aa577c0bdf302936266926ff37dbf7035d5eeb4
That is, when customer Alice interacts with the site, the site embeds a URL like this in web pages it sends to Alice. The attacker intercepts the URL listed above and guesses that the ciphertext following the " po?er= " is a hex encoded AES CBC encryption with a random IV of some secret data about Alice's session. 

After some experimentation the attacker discovers that the web site is vulnerable to a CBC padding oracle attack. In particular, when a decrypted CBC ciphertext ends in an invalid pad the web server returns a 403 error code (forbidden request). When the CBC padding is valid, but the message is malformed, the web server returns a 404 error code (URL not found). 

Armed with this information your goal is to decrypt the ciphertext listed above. To do so you can send arbitrary HTTP requests to the web site of the form
http://crypto-class.appspot.com/po?er="your ciphertext here"
and observe the resulting error code. The padding oracle will let you decrypt the given ciphertext one byte at a time. To decrypt a single byte you will need to send up to 256 HTTP requests to the site. Keep in mind that the first ciphertext block is the random IV. The decrypted message is ASCII encoded. 

To get you started here is a  short Python script  that sends a ciphertext supplied on the command line to the site and prints the resulting error code. You can extend this script (or write one from scratch) to implement the padding oracle attack. Once you decrypt the given ciphertext, please enter the decrypted message in the box below. 

This project shows that when using encryption you must prevent padding oracle attacks by either using encrypt-then-MAC as in EAX or GCM, or if you must use MAC-then-encrypt then ensure that the site treats padding errors the same way it treats MAC errors.

思路:

密文有64 byte,前16 byte为IV,后48 byte共3个block为需要解密的信息。基本思路如课上所说,要解开某一个block的值时,将其前一个block作为IV,从最后一个byte的值开始猜测,将作为IV的block的最后一个byte xor 猜测值 xor 0x01,当服务器解密后,如果为有效padding,就返回404,说明猜测值正确。之后猜倒数第二个byte值,将作为IV的block的倒数第二个byte xor 猜测值 xor 0x02,并注意利用之前最后一个byte的猜测值保证解密后的最后一个byte值也为0x02,直到返回404,得到倒数第二个byte的值。之后的byte依此类推。

有一种特殊情况需要注意:在未对IV做任何操作的情况下,若解密后的信息后几位为(x-1)个x值加上最后一个byte值n,即 2n, 33n, 444n, 5555n这样的形式,则对最后一位byte值n的猜测过程中可能得到两次404的返回。这是因为:假设猜测值为g,则解密后最后一个byte值为 n xor g xor 0x01,当 g=n时, n xor g xor 0x01= 1,服务器会判定padding有效,返回404,当n xor g xor 0x01=x 时,服务器同样会判定padding有效,例如n=x时,g=1会被认为是正确的猜测,而实际上最后一位byte值并不为1。在本题中,当解密最后一个block时就会碰上这样的情况,因为最后一个block本身带有padding,正好符合这一特殊情况。要区分两个猜测中哪一个是正确的猜测,只需要再次代入其中一个猜测,同时改变倒数第二个byte的值,如 xor 32,这样就改变了之前的特殊形式,那么只有g=n的情况才会返回404。

当正确猜测最后一个byte后,之后的猜测不会出现返回两次404的情况,例如猜测倒数第二byte的值m:解密后最后一位byte为0x02,而m xor g xor 0x02也为0x02时padding才有效时,即m xor g xor 0x02只有一个有效结果0x02,不像之前猜测最后一位byte时,n xor g xor 0x01时可以有 0x01或 x 这两个有效结果,因此padding有效时m xor g 一定为0,即 g=m,只会有一次404情况。猜测其他byte同理。 

代码:

import urllib2  
import sys  

AES_SIZE=16
ASCII_SIZE=128   
TARGET = 'http://crypto-class.appspadding_oraclet.com/padding_oracle?er='  
#--------------------------------------------------------------  
# padding oracle  
#--------------------------------------------------------------  
class PaddingOracle(object):  
    def query(self, q):  
        target = TARGET + urllib2.quote(q)    # Create query URL  
        req = urllib2.Request(target)         # Send HTTP request to server  
        try:  
            f = urllib2.urlopen(req)          # Wait for respadding_oraclense  
        except urllib2.HTTPError, e:            
            #print "We got: %d" % e.code       # Print respadding_oraclense code  
            if e.code == 404:  
                print "We got: %d" % e.code       # Print respadding_oraclense code  
                return True # good padding  
            return False # bad padding  
  
def strxor(a, b):     # xor two strings of different lengths  
    if len(a) > len(b):  
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a[:len(b)], b)])  
    else:  
        return "".join([chr(ord(x) ^ ord(y)) for (x, y) in zip(a, b[:len(a)])])  
   
def padding_attack(iv,ciphertext):    
    padding_oracle = PaddingOracle()  
    iv=iv.decode('hex')  
    pad={}  
    count=0   
    for guess_value in range(ASCII_SIZE): 
        # Guess the value of last byte. Note that the last byte maybe padding.
        iv_xor_guess=iv[:AES_SIZE-1]+chr(ord(iv[AES_SIZE-1])^1^guess_value)  
        request=iv_xor_guess.encode('hex')+ciphertext 
        if padding_oracle.query(request):  
            count=count+1  
            pad[count]=guess_value  
        if count>1:  
            break  
  
    if count==2:  
        iv_xor_guess=iv[:AES_SIZE-2]+chr(ord(iv[AES_SIZE-2])^32)+chr(ord(iv[AES_SIZE-1])^1^pad[count])  
        request=iv_xor_guess.encode('hex')+ciphertext  
        if padding_oracle.query(request):  
            message=chr(pad[count]) 
            iv=iv[:AES_SIZE-1]+chr(ord(iv[AES_SIZE-1])^pad[count])  
        else:  
            message=chr(pad[count-1])  
            iv=iv[:AES_SIZE-1]+chr(ord(iv[AES_SIZE-1])^pad[count-1])  
    else:  
        message=chr(pad[count])
        iv=iv[:AES_SIZE-1]+chr(ord(iv[AES_SIZE-1])^pad[count])  
    for idx in range(1,AES_SIZE):  
        for guess_value in range(ASCII_SIZE):  
            iv_xor_guess=iv[:AES_SIZE-idx-1]+chr(ord(iv[AES_SIZE-idx-1])^(idx+1)^guess_value)+strxor(iv[AES_SIZE-idx:],chr(idx+1)*idx)  
            request=iv_xor_guess.encode('hex')+ciphertext  
            if padding_oracle.query(request):  
                message=chr(guess_value)+message  
                iv=iv[:AES_SIZE-idx-1]+chr(ord(iv[AES_SIZE-idx-1])^guess_value)+iv[AES_SIZE-idx:]  
                break   
    return message 

cipher='f20bdba6ff29eed7b046d1df9fb7000058b1ffb4210a580f748b4ac714c001bd4a61044426fb515dad3f21f18aa577c0bdf302936266926ff37dbf7035d5eeb4'  
block_number=len(cipher)/2/AES_SIZE  
block=[0]*block_number  
for idx in range(block_number):
    block[idx]=cipher[idx*AES_SIZE*2:(idx+1)*AES_SIZE*2]
print padding_attack(block[0],block[1])+padding_attack(block[1],block[2])+padding_attack(block[2],block[3])  



你可能感兴趣的:(Coursera_Stanford密码学公开课 Programming Assignment 4)