diff --git a/padding_oracle_demo.py b/padding_oracle_demo.py new file mode 100644 index 0000000..706028b --- /dev/null +++ b/padding_oracle_demo.py @@ -0,0 +1,362 @@ +from Crypto.Cipher import AES +from gmssl import func # 该模块用于bytes与列表之间的转换 + + +# 密钥设置,请设置为16.24.32 +# ENCKEY = '1234567812345678' + +def po_main(plain=None, iv=None, enckey=None, attacker_enc=None, plain_want=None): + """ + 函数作用:主函数,用于基本信息的输入和整体提示信息的输出 + 调用其他函数完成攻击流程 + """ + print("=== Padding Oricle Attrack POC(CBC-MODE) ===") + + # 服务端默认设置 + cipher = "aes" # 加密方式(当前代码只支持AES) + block_size = 16 # 分组大小(AES默认为16) + + # 测试设置(实际使用时请注释掉) + # iv = "1234567812345678".encode('utf-8') # 初始向量 + # plain = "111111111111111122222".encode('utf-8') # 明文 + # plain_want = "opaas".encode('utf-8') # 想要获取密文的明文 + + iv = iv.encode('utf-8') + # 判断IV是否长度是否符合要求 + if len(iv) != block_size: + print("[-] IV must be" + str(block_size) + "bytes long!") + return False + + if plain: + plain = plain.encode('utf-8') + + print("=== Generate Target Ciphertext ===") + # 调用函数加密 + ciphertext = encrypt(plain, iv, cipher, enckey) + if not ciphertext: + print("[-] Encrypt Error!") + return False + + # 输出一些基本信息 + print("[+] plaintext is: " + str(plain, encoding="utf-8")) + print("[+] iv is: ", iv.decode("utf-8")) + print("[+] ciphertext is: ", ciphertext) + + return ciphertext + + if attacker_enc: + print(attacker_enc) + print(type(attacker_enc)) + + if plain_want: + plain_want = plain_want.encode('utf-8') + + # 开始进行Padding Oracle攻击 + print("=== Start Padding Oracle Decrypt ===") + print("[+] Choosing Cipher: " + cipher.upper()) + guess = padding_oracle_decrypt(cipher, attacker_enc, iv, block_size=block_size, enckey=enckey) + if guess: + print("[+] Guess intermediary value is: ", guess["intermediary"]) + print("[+] plaintext = intermediary_value XOR original_IV") + print("[+] Guess plaintext is: ", guess["plaintext"]) + + # 开始利用获取的中间值求解指定明文加密后的密文 + if plain_want: + print("=== Start Padding Oracle Encrypt ===") + print("[+] plaintext want to encrypt is: ", plain_want) + print("[+] Choosing Cipher: " + cipher.upper()) + en = padding_oracle_encrypt(cipher, attacker_enc, plain_want, iv, block_size=block_size, enckey=enckey) + if en: + print("[+] Encrypt Success!") + print("[+] The ciphertext you want is: ", en[block_size:]) + print("[+] IV is: ", iv) + print("=== Let's verify the custom encrypt result ===") + print("[+] Decrypt of ciphertext ", en[block_size:], " is:") + de = decrypt(en[block_size:], en[:block_size], cipher, enckey) + if de == add_PKCS5_padding(plain_want, block_size): + print(de) + print("[+] Bingo!") + + else: + print("[-] It seems something wrong happened!") + return False + return guess["plaintext"], en[block_size:] + + else: + return False + + +def padding_oracle_encrypt(cipher, ciphertext, plaintext, iv, enckey, block_size=16): + """" + 函数功能:完成指定明文加密的功能 + 输入: + cipher:加密类型 + ciphertext:已知的密文 + plaintext:需要加密的明文 + iv:初始向量 + block_size:分组大小 + 输出: + guess_cipher:猜测的密文(包含IV和密文) + 注意:这个函数功能的实现是在前面攻击的基础上实现的,即已经获取到了中间值 + """ + + guess_cipher = ciphertext[0 - block_size:] + + plaintext = add_PKCS5_padding(plaintext, block_size) + + print("[*] After padding, plaintext becomes to: ", plaintext) + + block = len(plaintext) + + iv_nouse = iv # 用不到,只需要中间值 + + prev_cipher = ciphertext[0 - block_size:] # init with the last cipher block + + while block > 0: + tmp = padding_oracle_decrypt_block(cipher, prev_cipher, iv_nouse, block_size=block_size, debug=False, enckey=enckey) + prev_cipher = xor_str(plaintext[block - block_size:block], tmp["intermediary"]) + guess_cipher = prev_cipher + guess_cipher.decode(encoding="ISO-8859-1") + block = block - block_size + guess_cipher = guess_cipher.encode(encoding="ISO-8859-1") + return guess_cipher + + +# 一个简单的异或操作,用于求解明文 +def xor(first, second): + return bytearray(x ^ y for x, y in zip(first, second)) + + +def padding_oracle_decrypt(cipher, ciphertext, iv, enckey, block_size=8, debug=True): + """" + 函数功能:对密文进行分组,并调用函数攻击每一个分组以获取中间值 + 输入: + cipher:加密类型 + ciphertext:已知的密文 + iv:初始向量 + block_size:分组大小 + 输出: + result:获取的中间值和明文信息 + 注意:这个函数不是主要的攻击函数,而是完成分组和调用攻击函数的功能 + """ + + # 将密文进行分组 + cipher_block = split_cipher_block(ciphertext, block_size) + + if cipher_block: + result = {} + result["intermediary"] = '' + result["plaintext"] = '' + counter = 0 + for c in cipher_block: + if debug: + print("[*] Now try to decrypt block " + str(counter)) + print("[*] Block " + str(counter) + "'s ciphertext is: ", c) + + # 调用真正的攻击函数(每一次攻击一个分组) + guess = padding_oracle_decrypt_block(cipher, c, iv, block_size=block_size, debug=debug, enckey=enckey) + + if guess: + iv = c + guess_inter = guess["intermediary"].decode(encoding="ISO-8859-1") + result["intermediary"] += guess_inter + guess_plain = guess["plaintext"].decode(encoding="ISO-8859-1") + result["plaintext"] += guess_plain + if debug: + print("[+] Block " + str(counter) + " decrypt!") + print("[+] intermediary value is: ", guess["intermediary"]) + print("[+] The plaintext of block " + str(counter) + " is: ", guess["plaintext"]) + counter = counter + 1 + else: + print("[-] padding oracle decrypt error!") + return False + # 返回值的格式要进行转换 + result["intermediary"] = result["intermediary"].encode(encoding="ISO-8859-1") + result["plaintext"] = result["plaintext"].encode(encoding="ISO-8859-1") + return result + else: + print("[-] ciphertext's block_size is incorrect!") + return False + + +def padding_oracle_decrypt_block(cipher, ciphertext, iv, enckey, block_size=16, debug=True): + """" + 函数功能:对单个分组进行攻击。获取中间值和明文 + 输入: + cipher:加密类型 + ciphertext:已知的密文(分组) + iv:初始向量 + block_size:分组大小 + 输出: + result:获取的中间值和明文信息(单个分组) + 注意:这个函数不是主要的攻击函数,而是完成分组和调用攻击函数的功能 + """ + result = {} + intermediary = bytearray(16) # 中间值 + + iv_p = bytearray(16) # 用于猜测的IV向量 + + # 通过for循环实现猜测的功能 + for i in range(1, block_size + 1): + for b in range(0, 256): + iv_p[16 - i] = b + iv_bytes = bytes(iv_p) + plain = decrypt(ciphertext, iv_bytes, cipher, enckey) + + # 最关键的一步,用于模拟服务器的回显,即padding是否符合要求 + if check_PKCS5_padding(plain, i): + if debug: + print("[*] Try IV: ", func.bytes_to_list(iv_bytes)) + print("[*] Found padding oracle: ", hex_s(plain)) + intermediary[16 - i] = i ^ b + for m in range(1, i + 1): + iv_p[16 - m] = (i + 1) ^ intermediary[16 - m] + plain = xor(iv, intermediary) + result["plaintext"] = plain + result["intermediary"] = bytes(intermediary) + return result + + +# 一个简单的分组函数 +def split_cipher_block(ciphertext, block_size=8): + if len(ciphertext) % block_size != 0: + return False + result = [] + length = 0 + while length < len(ciphertext): + # 每一个分组都是一个字符串 + result.append(ciphertext[length:length + block_size]) + length += block_size + return result + + +def check_PKCS5_padding(plain, p): + """" + 函数功能:服务器回显判断 + 输入: + plain:明文 + p:正在猜测的位数 + 输出: + True or False + 注意:这个函数用于模拟服务端,给出回显 + """ + if len(plain) % 16 != 0: + return False + + # 字符串反向,为了下面便于比较 + plain = plain[::-1] + ch = 0 + found = 0 # 相等的位数 + + while ch < p: + if bytes(plain[ch]) == bytes(p): + found += 1 + ch += 1 + + if found == p: + return True + else: + return False + + +def add_PKCS5_padding(plaintext, block_size): + """" + 函数功能:PKCS5填充 + 输入: + plaintext:明文 + block_size:分组大小 + 输出: + plaintext:填充后的明文 + 注意:这个函数用于填充缺失值,是本次攻击可以实施的本源 + """ + s = '' + + if len(plaintext) % block_size == 0: + return plaintext + + if len(plaintext) < block_size: + padding = block_size - len(plaintext) + + else: + padding = block_size - (len(plaintext) % block_size) + plaintext = str(plaintext, encoding="utf-8") + for i in range(0, padding): + plaintext += chr(padding) + plaintext = bytes(plaintext, encoding="utf-8") + return plaintext + + +def decrypt(ciphertext, iv, cipher, enckey): + """" + 函数功能:解密密文(AES) + 输入: + ciphertext:密文 + iv:初始向量 + cipher:加解密方式 + 输出: + o.decrypt(ciphertext):解密后的明文 + 注意:这个函数在本次实验里主要是用于验证密文的有效性 + """ + + key = enckey + if cipher.lower() == "aes": + o = AES.new(key, AES.MODE_CBC, iv) + else: + return False + + if len(iv) % 16 != 0: + return False + + if len(ciphertext) % 16 != 0: + return False + + return o.decrypt(ciphertext) + + +def encrypt(plaintext, iv, cipher, enckey): + """" + 函数功能:加密明文(AES) + 输入: + plaintext:明文 + iv:初始向量 + cipher:加解密方式 + 输出: + o.encrypt(plaintext):加密后的密文 + 注意:这个函数在本次实验里主要是用于初始服务端加密以获取到密文 + """ + key = enckey + + if cipher.lower() == "aes": + + if len(key) != 16 and len(key) != 24 and len(key) != 32: + print("[-] AES key must be 16/24/32 bytes long!") + return False + + o = AES.new(key, AES.MODE_CBC, iv) + else: + return False + + plaintext = add_PKCS5_padding(plaintext, len(iv)) + return o.encrypt(plaintext) + + +# 这个函数主要用于第二段攻击时某个值的计算 +def xor_str(a, b): + if len(a) != len(b): + return False + + c = [] + for i in range(0, len(a)): + c.append(a[i] ^ b[i]) + c = bytes(c) + return c.decode(encoding="ISO-8859-1") + + +# 可有可无 +def hex_s(str2): + re = [] + re = func.bytes_to_list(str2) + return re + + +if __name__ == "__main__": + po_main()