diff --git a/README b/README new file mode 100644 index 0000000..546e98b --- /dev/null +++ b/README @@ -0,0 +1,10 @@ +#### Running +The original file `bcrypt.ctf.py` can run on any version of python3. +`bcrypter.py` however is only compatible with Python 3.14.0a4 or above. +Mostly just cause I dislike `typing.Self`, and Python 3.14 now has the +wonderful `__annotate__` object method (see [PEP 649](https://peps.python.org/pep-0649/) +& [PEP 749](https://peps.python.org/pep-0749/)) which solves my issues +on type hint semantics and static type analysis. + +Beyond versioning, `bcrypter.py` has no dependencies beyond the stdlib. +Enjoy :) diff --git a/bcrypt/debug.py b/bcrypt/debug.py deleted file mode 100644 index a76e54e..0000000 --- a/bcrypt/debug.py +++ /dev/null @@ -1,61 +0,0 @@ -from math import gcd -from random import randint, randbytes - -def debug_hashes_eq(x: bytes, R_table: dict[int, int]) -> bool: - return hashfn(x, R_table) == bcrypt(x) - -def debug_test_random_hashes(trials: int, - max_bytes: int = 16, - quiet: bool = False) -> bytes | None: - R_table = precompute_R(0, 255) - for i in range(trials): - # generate random bytes - num_bytes = randint(0, max_bytes) - x = randbytes(num_bytes) - - # test the modified bcrypt with the original - hash_test = hashfn(x, R_table) - hash_bcrypt = bcrypt(x) - if hash_test != hash_bcrypt: - if not quiet: - print(f'Your hashfn sucks, big mistake bucko!! (iter: {i})') - print(hash_test) - print(hash_bcrypt) - print([str(b) for b in x]) - return x - - if not quiet: - print('Impeccable hashfn holy moly!!') - return None - - -def main() -> None: - print(f'gcd(H,K): {gcd(H,K)}') - print(f'gcd(H,M): {gcd(H,M)}') - print(f'gcd(K,M): {gcd(K,M)}') - if debug_test_random_hashes(10000) != None: - R_table = precompute_R(0, 255) - x = bytes(input('x: '), 'utf-8') - hash_test = hashfn(x, R_table) - hash_bcrypt = bcrypt(x) - print(f'hashfn: {hash_test}') - print(f'bcrypt: {hash_bcrypt}') - - # a = bytes(input("A: "), 'utf-8') - # b = bytes(input("B: "), 'utf-8') - - # if a != b and hashfn(a) == hashfn(b): - # print('*** YOU WIN ***') - # elif a == b: - # print('Idiot those are the same') - # else: - # print("Trivially false!") - - -if __name__ == '__main__': - try: - main() - except KeyboardInterrupt: - print('\n[!] Received SIGINT') - except EOFError: - print('\n[!] Reached EOF') diff --git a/bcrypt/__init__.py b/bcrypter/__init__.py similarity index 100% rename from bcrypt/__init__.py rename to bcrypter/__init__.py diff --git a/bcrypter.py b/bcrypter/__main__.py similarity index 100% rename from bcrypter.py rename to bcrypter/__main__.py diff --git a/bcrypt/debug/constants.py b/bcrypter/cli/__init__.py similarity index 100% rename from bcrypt/debug/constants.py rename to bcrypter/cli/__init__.py diff --git a/bcrypt/cli/__init__.py b/bcrypter/cli/builtins/__init__.py similarity index 100% rename from bcrypt/cli/__init__.py rename to bcrypter/cli/builtins/__init__.py diff --git a/bcrypter/cli/cmd.py b/bcrypter/cli/cmd.py new file mode 100644 index 0000000..9fee5e9 --- /dev/null +++ b/bcrypter/cli/cmd.py @@ -0,0 +1,46 @@ +from bcrypter.lib.result import Result + +class Command: + NAME = '[Abstract]Command' + ARGS = [] + FLAGS = [] + OPTIONS = [] + def __init__(self, args: list[string]) -> None: + pass + + @classmethod + def parse(cls: Command, cmd: list[str]) -> Result[Command]: + for arg in cmd[1:]: + # flag or option + if arg.startswith('--'): + match = cls._match_arg(arg[2:]) + if match + + ''' + Check whether FLAGS and OPTIONS are defined consistently + (ie no duplicate names or parsing ambiguity) + ''' + @classmethod + def _is_well_defined(cls: Command) -> Result[None]: + raise NotImplementedException('Command.is_consistent()') + + ''' + Attempt to match an arg to its flag or option + NOTE: _match_arg() assumes _is_well_defined() == True + ''' + @classmethod + def _match_arg(cls: Command, arg: str) -> Result[None]: + for opt in chain(cls.FLAGS, cls.OPTIONS): + if opt.matches(arg): + return Result.succeed(None) + return Result.fail() + + +class Builtin(Command): + self.NAME = '[Abstract]Builtin' + def __init__(self, + repl_builtins: list[Builtin], + repl_cmds: list[Command]) -> None: + super().__init__() + self._repl_builtins = repl_builtins + self._repl_cmds = repl_cmds diff --git a/bcrypt/debug/__init__.py b/bcrypter/cli/commands/__init__.py similarity index 100% rename from bcrypt/debug/__init__.py rename to bcrypter/cli/commands/__init__.py diff --git a/bcrypter/cli/opt.py b/bcrypter/cli/opt.py new file mode 100644 index 0000000..a0eb0f3 --- /dev/null +++ b/bcrypter/cli/opt.py @@ -0,0 +1,21 @@ +from enum import Enum + +class OptType(Enum): + AbstractOpt # only used by Opt + Flag + Option + +class Opt: + _TYPE: OptType = OptType.AbstractOpt + def __init__(self, *args) -> None: + pass + +class Flag(Opt): + _TYPE: OptType = OptType.Flag + def __init__(self, *args) -> None: + super().__init__(*args) + +class Option(Opt): + _TYPE: OptType = OptType.Option + def __init__(self, *args) -> None: + super().__init__(*args) diff --git a/bcrypter/cli/repl.py b/bcrypter/cli/repl.py new file mode 100644 index 0000000..a7baac9 --- /dev/null +++ b/bcrypter/cli/repl.py @@ -0,0 +1,42 @@ +import readline # GNU readline (ie allows input() history buffer) +from itertools import chain + +from bcrypter.cli.builtins import * +from bcrypter.cli.commands import * +from bcrypter.lib.result import Result +from bcrypter.exceptions import CmdDeclarationError + +class REPL: + _PROMPT = '>> ' + _DEFAULT_HISTORY_FILE = '.bcrypter_history' + + _BUILTINS = [ + BuiltinHelp(), + ] + _COMMANDS = [ + ] + + def __init__(self, history_file: str = _DEFAULT_HISTORY_FILE) -> None: + for cmd in chain(REPL._BUILTINS, REPL._COMMANDS): + result = cmd._is_consistent(): + if result.is_err(): + raise CmdDeclarationError(result.message) + self._history_file = history_file + readline.read_history_file(self._history_file) + + def __del__(self) -> None: + readline.write_history_file(self._history_file) + + def prompt(self) -> str: + return input(REPL._PROMPT) + + ''' + Parse and execute a string command + ''' + def exec(self, cmd: str) -> Result[Command]: + cmd = cmd.strip().split() + if not len(cmd): + return Result.warn('No command given') + + + diff --git a/bcrypter/debug.py b/bcrypter/debug.py new file mode 100644 index 0000000..3b6c423 --- /dev/null +++ b/bcrypter/debug.py @@ -0,0 +1,30 @@ +from math import gcd +from random import randint, randbytes + +def main() -> None: + if debug_test_random_hashes(10000) != None: + R_table = precompute_R(0, 255) + x = bytes(input('x: '), 'utf-8') + hash_test = hashfn(x, R_table) + hash_bcrypt = bcrypt(x) + print(f'hashfn: {hash_test}') + print(f'bcrypt: {hash_bcrypt}') + + # a = bytes(input("A: "), 'utf-8') + # b = bytes(input("B: "), 'utf-8') + + # if a != b and hashfn(a) == hashfn(b): + # print('*** YOU WIN ***') + # elif a == b: + # print('Idiot those are the same') + # else: + # print("Trivially false!") + + +if __name__ == '__main__': + try: + main() + except KeyboardInterrupt: + print('\n[!] Received SIGINT') + except EOFError: + print('\n[!] Reached EOF') diff --git a/bcrypt/hash/__init__.py b/bcrypter/debug/__init__.py similarity index 100% rename from bcrypt/hash/__init__.py rename to bcrypter/debug/__init__.py diff --git a/bcrypter/debug/constants.py b/bcrypter/debug/constants.py new file mode 100644 index 0000000..24d274a --- /dev/null +++ b/bcrypter/debug/constants.py @@ -0,0 +1,13 @@ +''' +This file provides various methods for determining +properties, connections, etc for bcrypt's constants. +''' + +from math import gcd + +from bcrypt.hash.rev import H, K, M + +def disint_gcds() -> None: + print(f'gcd(H,K): {gcd(H,K)}') + print(f'gcd(H,M): {gcd(H,M)}') + print(f'gcd(K,M): {gcd(K,M)}') diff --git a/bcrypt/debug/constituents.py b/bcrypter/debug/constituents.py similarity index 100% rename from bcrypt/debug/constituents.py rename to bcrypter/debug/constituents.py diff --git a/bcrypt/debug/hasheq.py b/bcrypter/debug/hasheq.py similarity index 100% rename from bcrypt/debug/hasheq.py rename to bcrypter/debug/hasheq.py diff --git a/bcrypter/exceptions.py b/bcrypter/exceptions.py new file mode 100644 index 0000000..49f6fff --- /dev/null +++ b/bcrypter/exceptions.py @@ -0,0 +1,2 @@ +class CmdDeclarationError(Exception): + pass diff --git a/bcrypter/hash/__init__.py b/bcrypter/hash/__init__.py new file mode 100644 index 0000000..e69de29 diff --git a/bcrypt/hash/og.py b/bcrypter/hash/og.py similarity index 100% rename from bcrypt/hash/og.py rename to bcrypter/hash/og.py diff --git a/bcrypt/hash/rev.py b/bcrypter/hash/rev.py similarity index 100% rename from bcrypt/hash/rev.py rename to bcrypter/hash/rev.py diff --git a/bcrypt/lib/format.py b/bcrypter/lib/format.py similarity index 100% rename from bcrypt/lib/format.py rename to bcrypter/lib/format.py diff --git a/bcrypt/lib/math.py b/bcrypter/lib/math.py similarity index 100% rename from bcrypt/lib/math.py rename to bcrypter/lib/math.py diff --git a/bcrypter/lib/result.py b/bcrypter/lib/result.py new file mode 100644 index 0000000..8c2e831 --- /dev/null +++ b/bcrypter/lib/result.py @@ -0,0 +1,26 @@ +from dataclass import dataclass +from enum import Enum + +class ResultState(Enum): + WARNING, + SUCCESS, + FAILURE, + +@dataclass +class Result[T]: + state: ResultState + value: T | None + message: str + + @classmethod + def warn(cls: Result, message: str, value: T | None = NOne) -> Result: + cls(ResultState.WARNING, value, message) + @classmethod + def succeed(cls: Result, value: T, message: str = 'Ok') -> Result: + cls(ResultState.SUCCESS, value, message) + @classmethod + def fail(cls: Result, message: str, value: T | None = None) -> Result: + cls(ResultState.WARNING, value, message) + + def is_ok(self) -> bool: return not self.is_err() + def is_err(self) -> bool: return self.state == ResultState.FAILURE