You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

363 lines
12 KiB

This file contains ambiguous Unicode characters!

This file contains ambiguous Unicode characters that may be confused with others in your current locale. If your use case is intentional and legitimate, you can safely ignore this warning. Use the Escape button to highlight these characters.

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()