2023-04-21 22:54:21 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
|
|
|
Dirty hash we were covered in an excercise for Week 16 of 2023 in cryptography
|
2023-04-22 10:42:29 +02:00
|
|
|
|
|
|
|
author: Christoph J. Scherr <software@cscherr.de>
|
|
|
|
version control at: https://git.cscherr.de/PlexSheep/python-dhbw/src/branch/master/src/trash-hash.py
|
|
|
|
License: MIT
|
2023-04-21 22:54:21 +02:00
|
|
|
"""
|
|
|
|
import math
|
|
|
|
import random
|
|
|
|
|
2023-04-22 00:48:06 +02:00
|
|
|
DEFINED_INITIAL = bytearray(b'\xa5\xa5\xa5\xa5\x5a\x5a\x5a\x5a\x55\x55\x55\x55\xaa\xaa\xaa\xaa')
|
2023-04-21 22:54:21 +02:00
|
|
|
|
|
|
|
def trash_hash(input: bytearray) -> bytearray:
|
2023-04-21 23:48:36 +02:00
|
|
|
#print("original len is %s" % len(input))
|
2023-04-21 22:54:21 +02:00
|
|
|
# extend with 0xff if length is not multiple of 16
|
|
|
|
while len(input) % 16 != 0:
|
|
|
|
input.append(0xff)
|
|
|
|
|
|
|
|
# set n
|
|
|
|
n: int = math.ceil(len(input)/16)
|
2023-04-21 23:48:36 +02:00
|
|
|
#print("len is %s" % len(input))
|
|
|
|
#print("n is %s" % n)
|
2023-04-21 22:54:21 +02:00
|
|
|
|
|
|
|
# cut input into blocks with size 16
|
|
|
|
blocks = [bytearray(16)] * n # initializes with 0x00s
|
|
|
|
# print the empty blocks
|
|
|
|
#for block in blocks:
|
|
|
|
# print("block: %s" % block.hex())
|
|
|
|
#print('='*80)
|
|
|
|
|
|
|
|
for i in range(0, n):
|
|
|
|
blocks[i] = input[i*16:i*16 + 16]
|
|
|
|
|
|
|
|
# print the filled blocks
|
|
|
|
#for block in blocks:
|
|
|
|
# print("block: %s" % block.hex())
|
|
|
|
|
|
|
|
# initilaize accumulator A_0 with the following constant values:
|
2023-04-21 23:13:04 +02:00
|
|
|
A = DEFINED_INITIAL
|
2023-04-21 22:54:21 +02:00
|
|
|
|
|
|
|
# iterate over blocks
|
|
|
|
for index, block in enumerate(blocks):
|
|
|
|
if index == 0:
|
|
|
|
pass
|
2023-04-21 23:13:04 +02:00
|
|
|
thing = bytes(by0 ^ by1 for by0, by1 in zip(A, block))
|
|
|
|
A = bytearray(thing)
|
2023-04-21 22:54:21 +02:00
|
|
|
|
2023-04-21 23:13:04 +02:00
|
|
|
return A
|
2023-04-21 22:54:21 +02:00
|
|
|
|
2023-04-21 23:13:04 +02:00
|
|
|
def use():
|
|
|
|
payload_a = bytearray(b"AAAA")
|
2023-04-21 23:48:36 +02:00
|
|
|
# works, but is too cheap
|
|
|
|
#payload_b = bytearray(b"AAAA\xff\xff")
|
|
|
|
payload_b = bytearray(b'\xb2\xef\x82t<~<\xbe\x8d\xca\xe2\t\xdc7E\x10')
|
|
|
|
print("a: %s\nb: %s" % (trash_hash(payload_a).hex(), trash_hash(payload_b).hex()))
|
|
|
|
print("identical: %s" % test_collision(payload_a, payload_b))
|
|
|
|
|
2023-04-22 00:48:06 +02:00
|
|
|
def test_collision(a: bytearray, b: bytearray) -> bool:
|
|
|
|
return trash_hash(a) == trash_hash(b)
|
|
|
|
|
|
|
|
def test_against_hash(input: bytearray, target_hash: bytearray) -> bool:
|
2023-04-22 10:20:13 +02:00
|
|
|
print("given input:\t%s" % input.hex())
|
|
|
|
print("given input (repr):\t%s" % input.decode(errors="ignore"))
|
2023-04-22 00:48:06 +02:00
|
|
|
hashed = trash_hash(input)
|
|
|
|
print("hashed variant:\t%s" % hashed.hex())
|
|
|
|
print("should be:\t%s" % THE_HASH.hex())
|
|
|
|
return trash_hash(input) == target_hash
|
|
|
|
|
2023-04-22 10:28:18 +02:00
|
|
|
# the hash we want to find in the preimage attacks
|
|
|
|
# comes from 'AAAA'
|
2023-04-22 10:42:29 +02:00
|
|
|
#THE_HASH_ORIGIN = bytearray(b'AAAA')
|
2023-04-22 10:28:18 +02:00
|
|
|
#THE_HASH = bytearray(b'\xe4\xe4\xe4\xe4\xa5\xa5\xa5\xa5\xaa\xaa\xaa\xaa\x55\x55\x55\x55')
|
|
|
|
# any custom hash you want to find a collision for:
|
|
|
|
# needs to be 16 bytes long
|
|
|
|
# TODO fill with padding if not long enough
|
2023-04-22 10:42:29 +02:00
|
|
|
THE_HASH_ORIGIN = bytearray(b'1249239473289754927513214 21421 4124 214 21')
|
2023-04-22 10:28:18 +02:00
|
|
|
THE_HASH = trash_hash(THE_HASH_ORIGIN)
|
|
|
|
|
2023-04-22 00:48:06 +02:00
|
|
|
def first_preimage():
|
|
|
|
print("Trying to find a message that produces %s" % THE_HASH.hex())
|
2023-04-22 01:05:00 +02:00
|
|
|
target = bytearray(b'\00' * 16)
|
2023-04-22 00:48:06 +02:00
|
|
|
input = bytearray(b'\00' * 16)
|
|
|
|
between = bytearray(16)
|
|
|
|
# this is an arbituary target
|
|
|
|
# should work for anything
|
|
|
|
target[0] = ord('A')
|
|
|
|
|
|
|
|
for i in range(0, 16):
|
2023-04-22 01:05:00 +02:00
|
|
|
|
2023-04-22 10:20:13 +02:00
|
|
|
between[i] = THE_HASH[i] ^ DEFINED_INITIAL[i]
|
2023-04-22 10:42:29 +02:00
|
|
|
#print("%d:\tbtw %s\ttar %s\thash %s\n\tini %s\tinp %s" % (i, between.hex(), target.hex(), THE_HASH.hex(), DEFINED_INITIAL.hex(), input.hex()))
|
2023-04-22 10:20:13 +02:00
|
|
|
input[i] = target[i] ^ between[i]
|
2023-04-22 10:42:29 +02:00
|
|
|
#print("%d:\tbtw %s\ttar %s\thash %s\n\tini %s\tinp %s" % (i, between.hex(), target.hex(), THE_HASH.hex(), DEFINED_INITIAL.hex(), input.hex()))
|
2023-04-22 10:20:13 +02:00
|
|
|
assert THE_HASH[i] == DEFINED_INITIAL[i] ^ between[i], "xor circle is broken: %s vs %s" % (hex(THE_HASH[i]), hex(input[i] ^ between[i]))
|
2023-04-22 00:48:06 +02:00
|
|
|
|
2023-04-22 10:20:13 +02:00
|
|
|
input: bytearray = bytearray(bytes(a ^ b for a, b in zip(input, target)))
|
|
|
|
print("for input '%s':\n %s" % (input, test_against_hash(input, THE_HASH)))
|
2023-04-22 00:48:06 +02:00
|
|
|
|
2023-04-22 10:28:18 +02:00
|
|
|
assert test_collision(input, THE_HASH_ORIGIN), "not the same thing: %s and %s" % (
|
|
|
|
trash_hash(input).hex(), trash_hash(THE_HASH_ORIGIN).hex())
|
|
|
|
|
2023-04-22 00:48:06 +02:00
|
|
|
def main():
|
|
|
|
first_preimage()
|
|
|
|
|
2023-04-21 23:48:36 +02:00
|
|
|
def bruteForce() -> bool:
|
|
|
|
payload_a = bytearray(b"AAAA")
|
|
|
|
foundCollision = False
|
|
|
|
while not foundCollision:
|
|
|
|
current = bytearray(random.randbytes(16))
|
|
|
|
foundCollision = test_collision(payload_a, current)
|
|
|
|
if random.randint(1, 65535) % 65535 == 0:
|
|
|
|
print(current)
|
|
|
|
print("found one!")
|
|
|
|
print(current)
|
|
|
|
return True
|
2023-04-21 23:13:04 +02:00
|
|
|
|
2023-04-21 22:54:21 +02:00
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|