""" $Id: break-xmlenc.py $ $ Date: 2020-01-02 09:37 $ $ Version: 1.0.0 $ Copyright (C) 2020-21 David Ireland, DI Management Services Pty Ltd <https://di-mgt.com.au> SPDX-License-Identifier: MIT Implementation of the Toy Example in 'How to break XML encryption' by T Jager and J Somorovsky. In: Proceedings of the 18th ACM Conference on Computer and Communications Security, CCS 2011, Chicago, Illinois, USA, October 17-21, 2011 <http://www.nds.rub.de/media/nds/veroeffentlichungen/2011/10/22/HowToBreakXMLenc.pdf> """ # pylint: disable=unused-wildcard-import from cryptosyspki import * print("PKI version =", Gen.version()) # GLOBAL VARIABLES n = 16 # Block length in bytes of AES NCHARS = 128 # Number of ASCII characters w = 0x0 # Our only Type-A character type_a = [w] # Set of Type-A characters # AES-128 key known by the Oracle and the setup code in main, but not known by ``break_it_all`` key = Cnv.fromhex("0123456789ABCDEFF0E1D2C3B4A59687") def Dec(iv, c): """ Decrypt ciphertext block c using global AES-128 key and given IV""" dt = Cipher.decrypt_block(c, key, iv, Cipher.Alg.AES128, Cipher.Mode.CBC) return dt def Oracle(iv, c): """ O(C) = 1, if the plaintext m = Dec_cbc(k,C) contains only Type-B characters, else 0. """ reply = 0 m = list(Dec(iv, c)) # print(m) # Are there any type A characters in the list? if any(x in type_a for x in m): # print("Found Type A") reply = 0 else: # print("All Type B") reply = 1 return reply def xor_bytes(a, b): assert(len(a) == len(b)) return bytes(x ^ y for x, y in zip(a, b)) def find_iv(iv, ct1): # First query the oracle whether O((IV,C(1))) = 1. # In this case we can set IV' := IV. # Otherwise we set IV' to a random bit string. # Does O((IV, C_1) = 1? iv1 = iv if 1 == Oracle(iv1, ct1): return iv1 # Else try some random bit strings for IV' for i in range(0,1000): # Safer than ``while 1``! iv1 = Rng.bytestring(n) # print("Random iv1 =", Cnv.tohex(iv1)) if 1 == Oracle(iv, ct1): break return iv1 def break_block(iv, ct1): # 1. Use the oracle to compute an initialization vector IV' # such that C' = (IV',C(1)) is well-formed. iv1 = find_iv(iv, ct1) # To recover x_j # we modify the initialization vector IV′ # by XOR-ing a byte-mask msk to the j-th byte of IV' m = bytearray(n) iv2 = None for j in range(0, n): # Recover x_j for msk in range(1, NCHARS): # for msk = 1 to 127 (all ASCII characters) # repeat until O((IV'', C_1)) = 0 # [NB typo in line 5 of Algorithm 1 in paper: change ``= 1`` to ``= 0``] iv2 = bytearray(iv1) iv2[j] = iv1[j] ^ msk # print("IV'' =", Cnv.tohex(iv2)) if (Oracle(iv2, ct1) == 0): break xj = w ^ iv2[j] # x_j := w XOR IV''(j) # print(f"x={hex(xj)}") mj = iv[j] ^ xj # m_j = IV_j XOR x_j # print(f"m[{j}]={hex(mj)}") m[j] = mj return m def break_it_all(iv, ct): """Given the ciphertext and IV, use the Oracle to find the plaintext.""" # Split CT into chunks c1, c2, ... ct_blocks = [ct[i:i+n] for i in range(0, len(ct), n)] nblocks = len(ct_blocks) print(f"Found {nblocks} blocks") m = [] # Break the first block ct1 = ct_blocks[0] print("CT1=", Cnv.tohex(ct1)) m1 = break_block(iv, ct1) m.append(m1) print("PT1=", m[0]) # Now break any subsequent blocks # passing CT(i-1) as the IV for the block CT(i) for i in range(1,nblocks): m1 = break_block(ct_blocks[i-1], ct_blocks[i]) m.append(m1) return m def main(): # SET UP msg = "Now is the time for all good men to come to the aid of their par" iv = Cnv.fromhex("FEDCBA9876543210FEDCBA9876543210") # key is a global variable available to the Oracle (but no peeking!) print("KY=", Cnv.tohex(key)) print("IV=", Cnv.tohex(iv)) ct = Cipher.encrypt_block(msg.encode(), key, iv, Cipher.Alg.AES128, Cipher.Mode.CBC) print("CT=", Cnv.tohex(ct)) # What we expect print("OK= C3153108A8DD340C0BCB1DFE8D25D2320EE0E66BD2BB4A313FB75C5638E9E177211FC26A1FF51CE35741B76A77DB6DE27435A4C79E56F29BB12B595404C222F1") # GO AHEAD AND BREAK IT... m = break_it_all(iv, ct) print(m) m1 = [x.decode() for x in m] print(m1) # Check against original message # Split msg into chunks m1, m2, ... msg_blocks = [msg[i:i+n] for i in range(0, len(msg), n)] print("Correct:") print(msg_blocks) if __name__ == "__main__": main()