known last state extension attack test works
This commit is contained in:
parent
6129b1f9e8
commit
afc7f1b76c
|
@ -0,0 +1,46 @@
|
||||||
|
current buffer: 5441
|
||||||
|
loading buffer
|
||||||
|
current in_byte: K
|
||||||
|
current buffer: 544143
|
||||||
|
loading buffer
|
||||||
|
internal state after the bytes were read: c2b50599
|
||||||
|
buffer pre last fill: 5441434b
|
||||||
|
buffer after last fill: 5441434b
|
||||||
|
last internal state: 1b516b3a
|
||||||
|
Hacked MIC: cd255d98
|
||||||
|
current in_byte:
|
||||||
|
current buffer: 4e
|
||||||
|
loading buffer
|
||||||
|
current in_byte: A
|
||||||
|
current buffer: 4e20
|
||||||
|
loading buffer
|
||||||
|
current in_byte: T
|
||||||
|
current buffer: 4e2041
|
||||||
|
loading buffer
|
||||||
|
current in_byte: T
|
||||||
|
current buffer: 4e204154
|
||||||
|
current in_byte: A
|
||||||
|
current buffer: 54
|
||||||
|
loading buffer
|
||||||
|
current in_byte: C
|
||||||
|
current buffer: 5441
|
||||||
|
loading buffer
|
||||||
|
current in_byte: K
|
||||||
|
current buffer: 544143
|
||||||
|
loading buffer
|
||||||
|
internal state after the bytes were read: 0e986369
|
||||||
|
buffer pre last fill: 5441434b
|
||||||
|
buffer after last fill: 5441434b
|
||||||
|
last internal state: 1a9d9590
|
||||||
|
Hacked MIC: 31bda0ab
|
||||||
|
|
||||||
|
|
||||||
|
=========
|
||||||
|
working extension attack against a basic mic:
|
||||||
|
=========
|
||||||
|
|
||||||
|
>>> authur1.authur1(bytearray(4), False, bytearray(0x33a9cfff.to_bytes(4))).hex()
|
||||||
|
'fd0ef003'
|
||||||
|
>>> authur1.keyed_hash(bytearray(8), bytearray(16), False).hex()
|
||||||
|
'fd0ef003'
|
||||||
|
>>>
|
|
@ -41,11 +41,15 @@ def inner_authur1(input: int) -> int:
|
||||||
|
|
||||||
return output
|
return output
|
||||||
|
|
||||||
def authur1(input: bytearray, verbose: bool = False) -> bytearray:
|
def authur1(input: bytearray,
|
||||||
if verbose:
|
verbose: bool = False,
|
||||||
print("input: %s" % input)
|
first_state: bytearray = DEFINED_INITIAL,
|
||||||
|
return_last_state: bool = False) -> bytearray:
|
||||||
internal_buffer: bytearray = bytearray()
|
internal_buffer: bytearray = bytearray()
|
||||||
accumulator: bytearray = DEFINED_INITIAL
|
accumulator: bytearray = first_state
|
||||||
|
if verbose:
|
||||||
|
print("input: %s" % input.hex())
|
||||||
|
print("first internal state: %s" % accumulator.hex())
|
||||||
for in_byte in input:
|
for in_byte in input:
|
||||||
if verbose:
|
if verbose:
|
||||||
print("current in_byte: %s" % chr(in_byte))
|
print("current in_byte: %s" % chr(in_byte))
|
||||||
|
@ -85,38 +89,17 @@ def authur1(input: bytearray, verbose: bool = False) -> bytearray:
|
||||||
print("last internal state: %s" % accumulator.hex())
|
print("last internal state: %s" % accumulator.hex())
|
||||||
# now Q the accumulator and return
|
# now Q the accumulator and return
|
||||||
# if input = "" this step breaks things, just remove it.
|
# if input = "" this step breaks things, just remove it.
|
||||||
if len(input) != 0:
|
if len(input) != 0 and not return_last_state:
|
||||||
accuint: int = int.from_bytes(accumulator)
|
accuint: int = int.from_bytes(accumulator)
|
||||||
accuint: int = inner_authur1(accuint)
|
accuint: int = inner_authur1(accuint)
|
||||||
accumulator: bytearray = bytearray(accuint.to_bytes(4))
|
accumulator: bytearray = bytearray(accuint.to_bytes(4))
|
||||||
return accumulator
|
return accumulator
|
||||||
|
|
||||||
def keyed_hash(message: bytearray, key: bytearray) -> bytearray:
|
def keyed_hash(message: bytearray, key: bytearray, verbose: bool = False, return_last_state: bool = False) -> bytearray:
|
||||||
assert len(key) == 16, "key is not 16 Byte long: %s" % len(key)
|
assert len(key) == 16, "key is not 16 Byte long: %s" % len(key)
|
||||||
# prepend only
|
# prepend only
|
||||||
input: bytearray = key + message
|
input: bytearray = key + message
|
||||||
mic: bytearray = authur1(input)
|
mic: bytearray = authur1(input, verbose, return_last_state=return_last_state)
|
||||||
return mic
|
|
||||||
|
|
||||||
def reverse_inner_authur1(output: int) -> int:
|
|
||||||
assert output.bit_length() <= 32, "output length is <= 32: %d" % output.bit_length()
|
|
||||||
|
|
||||||
# plexcryptool.binary uses u32 for shifting
|
|
||||||
#output: int = input ^ (binary.rotl32(input, SHIFT_LENGTH))
|
|
||||||
# -> output ^ input = binary.rotl32(input, SHIFT_LENGTH)
|
|
||||||
# -> input = binary.rotl32(input, SHIFT_LENGTH) ^ output
|
|
||||||
input: int = output ^ (binary.rotr32(output, SHIFT_LENGTH))
|
|
||||||
|
|
||||||
assert False, "inner_authur1 can not be reversed!"
|
|
||||||
assert input.bit_length() <= 32, "input length is <= 32: %d" % input.bit_length()
|
|
||||||
|
|
||||||
return input
|
|
||||||
|
|
||||||
def find_last_internal_state(mic: bytearray) -> bytearray:
|
|
||||||
# reverse the mic to get the last internal state
|
|
||||||
# TODO
|
|
||||||
raise NotImplementedError("find last internal state is still TODO")
|
|
||||||
|
|
||||||
return mic
|
return mic
|
||||||
|
|
||||||
def extension_attack(valid_pairs: list):
|
def extension_attack(valid_pairs: list):
|
||||||
|
@ -175,8 +158,37 @@ def extension_attack(valid_pairs: list):
|
||||||
print("The given originals were not sufficient to perform an extension attack.\n"+
|
print("The given originals were not sufficient to perform an extension attack.\n"+
|
||||||
"We need a message, which has a length that is a multiple of 16 (Bytes).")
|
"We need a message, which has a length that is a multiple of 16 (Bytes).")
|
||||||
return
|
return
|
||||||
last_internal: bytearray = find_last_internal_state(target_pair[1])
|
|
||||||
print("Found a fitting target pair: (%s,%s)" % target_pair)
|
# now find the last internal state.
|
||||||
|
# inner_authur1 cannot be reversed, so we need to brute force it
|
||||||
|
# the key space is only 2**32, so it should be possible
|
||||||
|
# we need to check everyone of these 2**32 against the valid hashes to find a working one
|
||||||
|
|
||||||
|
found_the_state = False
|
||||||
|
KEY_SPACE = 2**32
|
||||||
|
extension_msg = bytearray("ef".encode())
|
||||||
|
# given mic for "abcdef" 0f6b8802
|
||||||
|
looking_for = bytearray(0x0f6b8802.to_bytes(4))
|
||||||
|
last_internal_state = None
|
||||||
|
print("=" * 120)
|
||||||
|
print("Bruteforcing the internal state, this might take a while")
|
||||||
|
print("=" * 120)
|
||||||
|
for i in range(0, KEY_SPACE):
|
||||||
|
mic = authur1(extension_msg, False, bytearray(i.to_bytes(4)))
|
||||||
|
if found_the_state:
|
||||||
|
break
|
||||||
|
if not mic is None and mic == looking_for:
|
||||||
|
print("=" * 120)
|
||||||
|
print("FOUND THE THING")
|
||||||
|
found_the_state = True
|
||||||
|
last_internal_state = i
|
||||||
|
print("IT IS %s" % hex(last_internal_state))
|
||||||
|
|
||||||
|
extension_text = "EXTENSION ATTACK"
|
||||||
|
|
||||||
|
hacked_mic = authur1(bytearray(extension_text.encode()), True, bytearray(last_internal_state.to_bytes(4)))
|
||||||
|
print("Hacked MIC: %s" % hacked_mic.hex())
|
||||||
|
|
||||||
|
|
||||||
def test():
|
def test():
|
||||||
init: int = int.from_bytes(DEFINED_INITIAL)
|
init: int = int.from_bytes(DEFINED_INITIAL)
|
||||||
|
@ -204,25 +216,28 @@ def test():
|
||||||
|
|
||||||
print("H aka authur1 passed the test")
|
print("H aka authur1 passed the test")
|
||||||
|
|
||||||
test_reverse_inner_authur1()
|
#test_reverse_inner_authur1()
|
||||||
test_extension_attack()
|
test_extension_attack()
|
||||||
|
|
||||||
print("All tests passed!")
|
print("All tests passed!")
|
||||||
|
|
||||||
def test_extension_attack():
|
def test_extension_attack():
|
||||||
"""
|
"""
|
||||||
Test the attack against a known key
|
Test the attack against a known last state
|
||||||
"""
|
"""
|
||||||
# TODO
|
key = bytearray(0x13377331.to_bytes(16))
|
||||||
raise(NotImplementedError("Extension attack is still TODO"))
|
message = bytearray("1234".encode())
|
||||||
|
ext_message = bytearray("EXT".encode())
|
||||||
|
# we need to bruteforce this, skip for the test
|
||||||
|
mic = keyed_hash(message, key)
|
||||||
|
last_internal_state = keyed_hash(message, key, return_last_state=True)
|
||||||
|
forged_mic = authur1(ext_message, False, last_internal_state)
|
||||||
|
message.extend(ext_message)
|
||||||
|
validated_mic = keyed_hash(message, key)
|
||||||
|
assert validated_mic == forged_mic, "forged mic\n%sis not valid\n%s" % ( forged_mic.hex(), validated_mic.hex() )
|
||||||
|
print("Manual extension attack with known last internal state works\n(%s == %s)" % (forged_mic.hex(), validated_mic.hex()))
|
||||||
|
|
||||||
|
|
||||||
def test_reverse_inner_authur1():
|
|
||||||
a = 0x1337
|
|
||||||
aq = inner_authur1(a)
|
|
||||||
assert reverse_inner_authur1(aq) == a, "reverse_inner_authur1 does not work:\n%s\nis not\n%s" % (
|
|
||||||
bin(a),
|
|
||||||
bin(reverse_inner_authur1(a))
|
|
||||||
)
|
|
||||||
|
|
||||||
def main():
|
def main():
|
||||||
parser = argparse.ArgumentParser(prog="authur1", description='Implementation and attack for the custom authur1 hash. Don\'t actually use this hash!')
|
parser = argparse.ArgumentParser(prog="authur1", description='Implementation and attack for the custom authur1 hash. Don\'t actually use this hash!')
|
||||||
|
|
Loading…
Reference in New Issue