2023-04-24 19:24:28 +02:00
|
|
|
#!/usr/bin/env python3
|
|
|
|
"""
|
|
|
|
A custom authentication hash function aswell as an automated extension attack for it.
|
|
|
|
|
|
|
|
Since this (auth) hash did not have a name before, I gave it the name 'authur1'
|
|
|
|
|
|
|
|
@author Christoph J. Scherr <software@cscherr.de>
|
|
|
|
@license MIT
|
|
|
|
@source: https://git.cscherr.de/PlexSheep/python-dhbw/src/branch/master/src/authur1.py
|
|
|
|
"""
|
|
|
|
import argparse
|
2023-04-25 19:02:51 +02:00
|
|
|
from sre_constants import IN_UNI_IGNORE
|
2023-04-24 19:24:28 +02:00
|
|
|
|
2023-04-25 18:55:33 +02:00
|
|
|
# FIXME make proper pyi Implementation for the rust part
|
2023-04-25 18:09:04 +02:00
|
|
|
from plexcryptool import binary
|
|
|
|
|
2023-04-24 20:37:43 +02:00
|
|
|
# constants for authur1
|
|
|
|
SHIFT_LENGTH = 17
|
2023-04-24 21:19:25 +02:00
|
|
|
DEFINED_INITIAL = bytearray([0x52, 0x4f, 0x46, 0x4c])
|
|
|
|
PADDING = 0xff
|
2023-04-24 19:24:28 +02:00
|
|
|
|
2023-04-24 21:19:25 +02:00
|
|
|
# constants for Circular shifting
|
|
|
|
# constant value defined in limits.h, it's 8 (bit) on my machine, on yours probably too.
|
|
|
|
CHAR_BIT = 8
|
|
|
|
# python is being a dynamic dumbass, do a 32 bit shift ~ 4 byte
|
|
|
|
VALUE_SIZE = 4
|
2023-04-24 20:37:43 +02:00
|
|
|
|
2023-04-24 21:19:25 +02:00
|
|
|
def inner_authur1(input: int) -> int:
|
2023-04-25 18:09:04 +02:00
|
|
|
"""
|
|
|
|
passes all tests
|
|
|
|
"""
|
2023-04-24 21:47:10 +02:00
|
|
|
assert input.bit_length() <= 32, "input length is <= 32: %d" % input.bit_length()
|
2023-04-24 21:19:25 +02:00
|
|
|
output: int
|
2023-04-24 20:37:43 +02:00
|
|
|
|
2023-04-25 18:09:04 +02:00
|
|
|
# plexcryptool.binary uses u32 for shifting
|
|
|
|
output = input ^ (binary.rotl32(input, SHIFT_LENGTH))
|
2023-04-24 20:37:43 +02:00
|
|
|
|
2023-04-24 21:47:10 +02:00
|
|
|
assert output.bit_length() <= 32, "output length is <= 32: %d" % output.bit_length()
|
|
|
|
|
2023-04-24 20:37:43 +02:00
|
|
|
return output
|
2023-04-24 19:24:28 +02:00
|
|
|
|
2023-04-25 18:55:33 +02:00
|
|
|
def authur1(input: bytearray, verbose: bool = False) -> bytearray:
|
2023-04-25 19:02:51 +02:00
|
|
|
if verbose:
|
|
|
|
print("input: %s" % input)
|
2023-04-24 21:19:25 +02:00
|
|
|
internal_buffer: bytearray = bytearray()
|
|
|
|
accumulator: bytearray = DEFINED_INITIAL
|
|
|
|
for in_byte in input:
|
2023-04-25 18:55:33 +02:00
|
|
|
if verbose:
|
|
|
|
print("current in_byte: %s" % chr(in_byte))
|
|
|
|
print("current buffer: %s" % internal_buffer.hex())
|
|
|
|
if len(internal_buffer) < 4:
|
|
|
|
if verbose:
|
|
|
|
print("loading buffer")
|
2023-04-24 21:19:25 +02:00
|
|
|
internal_buffer.append(in_byte)
|
|
|
|
continue
|
|
|
|
# else
|
|
|
|
assert len(internal_buffer) == 4, "internal buffer of authur1 not 4 byte long"
|
2023-04-25 18:09:04 +02:00
|
|
|
accuint = int.from_bytes(accumulator)
|
|
|
|
accuint = inner_authur1(accuint ^ int.from_bytes(internal_buffer))
|
|
|
|
accumulator = bytearray(accuint.to_bytes(4))
|
2023-04-25 18:55:33 +02:00
|
|
|
internal_buffer.clear()
|
|
|
|
assert len(internal_buffer) == 0
|
|
|
|
internal_buffer.append(in_byte)
|
2023-04-25 18:09:04 +02:00
|
|
|
assert len(accumulator) == 4, "accumulator too long: %d bytes" % len(accumulator)
|
2023-04-25 18:55:33 +02:00
|
|
|
if verbose:
|
|
|
|
print("internal state after the bytes were read: %s" % accumulator.hex())
|
2023-04-24 21:19:25 +02:00
|
|
|
# finished loading input bytes into the hash, fill with padding and do it one last time
|
2023-04-25 18:55:33 +02:00
|
|
|
if verbose:
|
|
|
|
print("buffer pre last fill: %s" % internal_buffer.hex())
|
|
|
|
while len(internal_buffer) < 4:
|
2023-04-24 21:19:25 +02:00
|
|
|
internal_buffer.append(PADDING)
|
2023-04-25 18:55:33 +02:00
|
|
|
if verbose:
|
|
|
|
print("buffer after last fill: %s" % internal_buffer.hex())
|
2023-04-24 21:19:25 +02:00
|
|
|
assert len(internal_buffer) == 4, "internal buffer of authur1 not 4 byte long"
|
|
|
|
# same as above, one last time
|
2023-04-25 18:09:04 +02:00
|
|
|
assert len(accumulator) == 4, "accumulator too long: %d bytes" % len(accumulator)
|
|
|
|
accuint = int.from_bytes(accumulator)
|
|
|
|
accuint = inner_authur1(accuint ^ int.from_bytes(internal_buffer))
|
|
|
|
accumulator = bytearray(accuint.to_bytes(4))
|
2023-04-24 21:19:25 +02:00
|
|
|
|
2023-04-25 18:09:04 +02:00
|
|
|
assert len(accumulator) == 4, "accumulator too long: %d bytes" % len(accumulator)
|
2023-04-25 18:55:33 +02:00
|
|
|
if verbose:
|
|
|
|
print("returning state: %s" % accumulator.hex())
|
|
|
|
# now Q the accumulator and return
|
2023-04-25 19:02:51 +02:00
|
|
|
# if input = "" this step breaks things, just remove it.
|
|
|
|
if len(input) != 0:
|
|
|
|
accuint = int.from_bytes(accumulator)
|
|
|
|
accuint = inner_authur1(accuint)
|
|
|
|
accumulator = bytearray(accuint.to_bytes(4))
|
2023-04-24 21:19:25 +02:00
|
|
|
return accumulator
|
2023-04-24 19:24:28 +02:00
|
|
|
|
2023-04-25 18:09:04 +02:00
|
|
|
def test():
|
|
|
|
init = int.from_bytes(DEFINED_INITIAL)
|
|
|
|
a = inner_authur1(init)
|
|
|
|
b = inner_authur1(a)
|
|
|
|
c = inner_authur1(b)
|
|
|
|
assert a == 0xded7e2d2, "Q(S0) returns wrong value: %s" % hex(a)
|
|
|
|
assert b == 0x1b725f7d, "Q(Q(S0)) returns wrong value: %s" % hex(b)
|
|
|
|
assert c == 0xa5886999, "Q(Q(Q(S0))) returns wrong value: %s" % hex(c)
|
|
|
|
|
|
|
|
print("Q aka inner_authur1 passed the test")
|
|
|
|
|
2023-04-25 19:02:51 +02:00
|
|
|
ha = authur1(bytearray(0), True)
|
2023-04-25 18:09:04 +02:00
|
|
|
hb = authur1(bytearray(b'A'))
|
|
|
|
hc = authur1(bytearray(b'AB'))
|
|
|
|
hd = authur1(bytearray(b'ABC'))
|
|
|
|
he = authur1(bytearray(b'ABCD'))
|
|
|
|
hf = authur1(bytearray(b'ABCDE'))
|
|
|
|
assert int.from_bytes(ha) == 0xded7e2d2, "H(\"\") returns wrong value: %s" % ha.hex()
|
|
|
|
assert int.from_bytes(hb) == 0x5d725f7f, "H(\"A\") returns wrong value: %s" % hb.hex()
|
|
|
|
assert int.from_bytes(hc) == 0x5f3b5f7f, "H(\"AB\") returns wrong value: %s" % hc.hex()
|
|
|
|
assert int.from_bytes(hd) == 0x5f39137f, "H(\"ABC\") returns wrong value: %s" % hd.hex()
|
|
|
|
assert int.from_bytes(he) == 0x5f391128, "H(\"ABCD\") returns wrong value: %s" % he.hex()
|
|
|
|
assert int.from_bytes(hf) == 0x2f69af58, "H(\"ABCDE\") returns wrong value: %s" % hf.hex()
|
|
|
|
|
|
|
|
print("H aka authur1 passed the test")
|
|
|
|
print("All tests passed!")
|
|
|
|
|
2023-04-24 19:24:28 +02:00
|
|
|
def main():
|
2023-04-24 21:19:25 +02:00
|
|
|
parser = argparse.ArgumentParser(prog="authur1 authentication hash", description='Implementation and attack for the custom authur1 hash')
|
2023-04-24 21:47:10 +02:00
|
|
|
parser.add_argument('-i', '--hash', type=str,
|
2023-04-24 21:19:25 +02:00
|
|
|
help='an input that should be hashed')
|
2023-04-25 18:09:04 +02:00
|
|
|
parser.add_argument('-t', '--test', action="store_true",
|
|
|
|
help='perform tests')
|
2023-04-25 18:55:33 +02:00
|
|
|
parser.add_argument('-v', '--verbose', action="store_true",
|
|
|
|
help='print many things')
|
2023-04-24 21:19:25 +02:00
|
|
|
args = parser.parse_args()
|
|
|
|
|
|
|
|
if args.hash:
|
|
|
|
my_bytes: bytearray = bytearray(str.encode(args.hash))
|
2023-04-25 18:55:33 +02:00
|
|
|
hashed = authur1(my_bytes, args.verbose)
|
2023-04-24 21:19:25 +02:00
|
|
|
print("hash for \"%s\" is:\n%s" % (args.hash, hashed.hex()))
|
2023-04-25 18:09:04 +02:00
|
|
|
exit()
|
|
|
|
elif args.test:
|
|
|
|
test()
|
|
|
|
exit()
|
2023-04-24 21:19:25 +02:00
|
|
|
parser.print_help()
|
2023-04-24 19:24:28 +02:00
|
|
|
|
|
|
|
if __name__ == "__main__":
|
|
|
|
main()
|