require_relative '../util' KEY = random_bytes(16) PREFIX = random_bytes(rand(2..40)) UNKNOWN = b64decode('Um9sbGluJyBpbiBteSA1LjAKV2l0aCBteSByYWctdG9wIGRvd24gc28gbXkgaGFpciBjYW4gYmxvdwpUaGUgZ2lybGllcyBvbiBzdGFuZGJ5IHdhdmluZyBqdXN0IHRvIHNheSBoaQpEaWQgeW91IHN0b3A/IE5vLCBJIGp1c3QgZHJvdmUgYnkK') BLOCKSIZE = 16 A = 'A'.ord def encryption_oracle(input) aes_ecb_encrypt(pkcs7pad(PREFIX + input + UNKNOWN, BLOCKSIZE), KEY) end # XXXXXXXAAAAAAAAA|AAAAAAAAAAAAAAAA|AAAAAAAAAAAAAAAA|YYYYYYYYYYYYYYYY # prefix: 7, junk: 9, duplicate at: 1 # XXXXXXXXXXXXXXXX|XXXAAAAAAAAAAAAA|AAAAAAAAAAAAAAAA|AAAAAAAAAAAAAAAA|YYYYYYYYYYYYYYYY|YYYYYYYYYYYYYZZZ # prefix: 19, junk: 13, duplicate at: 2 def find_prefix_length glue = Array.new(32, A) duplicate_index = nil loop do output = encryption_oracle(glue) blocks = output.each_slice(BLOCKSIZE).to_a duplicate_index = blocks.index(find_duplicate(blocks)) break if duplicate_index glue << A end (duplicate_index + 2) * BLOCKSIZE - glue.length end # XXXXXXXXAAAAAAAA|AAAAAAAAAAAAAAAY|YYYYYYYYYYYYYYYY # ^ ^ ^ # prefix glue | # cutoff ----' def find_matching_byte(input, target, cutoff, size) (0..255).each do |byte| output = encryption_oracle(input + [byte]) return byte if output.slice(cutoff, size) == target end end def decrypt_byte(prefix_length, known) glue_length = BLOCKSIZE - prefix_length % BLOCKSIZE glue = Array.new(glue_length, A) size = (known.length / BLOCKSIZE + 1) * BLOCKSIZE prefix = Array.new(size - known.length - 1, A) cutoff = glue_length + prefix_length target = encryption_oracle(glue + prefix).slice(cutoff, size) find_matching_byte(glue + prefix + known, target, cutoff, size) end prefix_length = find_prefix_length info("Prefix length: #{prefix_length}") info("Actual prefix length: #{PREFIX.length}") known = [] UNKNOWN.length.times { known << decrypt_byte(prefix_length, known) } puts str(known)