continued reorganisation
This commit is contained in:
parent
6f8a7322f2
commit
0a2d9a5694
22 changed files with 190 additions and 61 deletions
0
bcrypter/__init__.py
Normal file
0
bcrypter/__init__.py
Normal file
12
bcrypter/__main__.py
Normal file
12
bcrypter/__main__.py
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
from bcrypt.cli import repl
|
||||
|
||||
def main():
|
||||
repl()
|
||||
|
||||
if __name__ == '__main__':
|
||||
try:
|
||||
main()
|
||||
except KeyboardInterrupt:
|
||||
print('\n[!] SIGINT')
|
||||
except EOFError:
|
||||
print('\n[!] EOF')
|
||||
1
bcrypter/cli/__init__.py
Normal file
1
bcrypter/cli/__init__.py
Normal file
|
|
@ -0,0 +1 @@
|
|||
|
||||
0
bcrypter/cli/builtins/__init__.py
Normal file
0
bcrypter/cli/builtins/__init__.py
Normal file
46
bcrypter/cli/cmd.py
Normal file
46
bcrypter/cli/cmd.py
Normal file
|
|
@ -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
|
||||
0
bcrypter/cli/commands/__init__.py
Normal file
0
bcrypter/cli/commands/__init__.py
Normal file
21
bcrypter/cli/opt.py
Normal file
21
bcrypter/cli/opt.py
Normal file
|
|
@ -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)
|
||||
42
bcrypter/cli/repl.py
Normal file
42
bcrypter/cli/repl.py
Normal file
|
|
@ -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')
|
||||
|
||||
|
||||
|
||||
30
bcrypter/debug.py
Normal file
30
bcrypter/debug.py
Normal file
|
|
@ -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')
|
||||
0
bcrypter/debug/__init__.py
Normal file
0
bcrypter/debug/__init__.py
Normal file
13
bcrypter/debug/constants.py
Normal file
13
bcrypter/debug/constants.py
Normal file
|
|
@ -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)}')
|
||||
18
bcrypter/debug/constituents.py
Normal file
18
bcrypter/debug/constituents.py
Normal file
|
|
@ -0,0 +1,18 @@
|
|||
'''
|
||||
This file provides various "disint" (display internal) methods
|
||||
regarding the "constituent functions" that exist in "bcrypt".
|
||||
'''
|
||||
|
||||
from bcrypt.lib.format import lpad, lpadbin
|
||||
|
||||
'''
|
||||
Display internal calculations of Rb(b: int) for bcrypt/hashrev.py
|
||||
'''
|
||||
def disint_Rb(b: int):
|
||||
Rb = 0
|
||||
for j in range(8):
|
||||
j_sq = j**2
|
||||
Rjb = b << j_sq
|
||||
Rb ^= Rjb
|
||||
print(f'{lpad(j_sq, 2)}: {lpadbin(Rjb, 64)} {Rjb}')
|
||||
print(f'Rb: {lpadbin(Rb, 64)} {Rb}')
|
||||
39
bcrypter/debug/hasheq.py
Normal file
39
bcrypter/debug/hasheq.py
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
'''
|
||||
This file implements debug methods to check whether
|
||||
hashrev.bcrypt() operates identically to hash.bcrypt().
|
||||
TLDR: test if I made a dumb typo.
|
||||
'''
|
||||
|
||||
from random import randint, randbytes
|
||||
import bcrypt.hash
|
||||
import bcrypt.hashrev
|
||||
|
||||
def is_bcrypt_eq(x: bytes, R_table: Optional[dict[int, int]] = None) -> bool:
|
||||
return hashrev.bcrypt(x, R_table=R_table) == hash.bcrypt(x)
|
||||
|
||||
'''
|
||||
Display internals of testing bcrypt implementations
|
||||
from bcrypt/hash.py and bcrypt/hashrev.py
|
||||
NOTE: By default this runs 10000 trials for
|
||||
NOTE: random sequences of length 0-16 bytes.
|
||||
'''
|
||||
def disint_bcrypt_eq(trials: int = 10000,
|
||||
max_bytes: int = 16) -> bytes | None:
|
||||
R_table = hashrev.calc_R_array(max = 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_rev = hashrev.bcrypt(x, R_table=R_table)
|
||||
hash_expected = hash.bcrypt(x)
|
||||
if hash_rev != hash_expected:
|
||||
print(f'Your hashfn sucks, big mistake bucko!! (failed iter: {i})')
|
||||
print(f' Got: {hash_rev}')
|
||||
print(f'Expected: {hash_bcrypt}')
|
||||
print(f'Input Bytes: {[str(b) for b in x]}')
|
||||
return x
|
||||
|
||||
print('Impeccable hashfn holy moly!!')
|
||||
return None
|
||||
2
bcrypter/exceptions.py
Normal file
2
bcrypter/exceptions.py
Normal file
|
|
@ -0,0 +1,2 @@
|
|||
class CmdDeclarationError(Exception):
|
||||
pass
|
||||
0
bcrypter/hash/__init__.py
Normal file
0
bcrypter/hash/__init__.py
Normal file
15
bcrypter/hash/og.py
Normal file
15
bcrypter/hash/og.py
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
'''
|
||||
The unchanged original "bcrypt" function for the CTF (by bpaul)
|
||||
'''
|
||||
def bcrypt(x: bytes) -> int:
|
||||
h = 18446744073709551614
|
||||
|
||||
for (i, b) in enumerate(x):
|
||||
h *= h * (b + 1)
|
||||
k = 59275109328752 * (i + 1)
|
||||
for j in range(8):
|
||||
k ^= b << (j * j)
|
||||
h += k
|
||||
h %= (2 ** 64)
|
||||
|
||||
return h
|
||||
59
bcrypter/hash/rev.py
Normal file
59
bcrypter/hash/rev.py
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
'''
|
||||
This file reverse engineers and reconstructs the original
|
||||
"bcrypt" hash function for the CTF (stored in bcrypt/hash.py).
|
||||
This reconstructs implements, namely, precomputation of tables
|
||||
of values in the hashing. Allowing bulk computation of hashes
|
||||
to be significantly faster.
|
||||
'''
|
||||
|
||||
from array import array
|
||||
from typing import Optional
|
||||
|
||||
# Constants
|
||||
H = 18446744073709551614
|
||||
K = 59275109328752
|
||||
M = 2**64
|
||||
|
||||
def Rjb(j: int, b: int) -> int:
|
||||
return b << j**2
|
||||
|
||||
def Rb(b: int) -> int:
|
||||
Rb = 0
|
||||
for j in range(8):
|
||||
Rb ^= Rjb(j, b)
|
||||
return Rb
|
||||
|
||||
'''
|
||||
Reconstructed implementation of the "bcrypt" hash function by bpaul.
|
||||
NOTE: An optional precomputed R_table is permitted as an arguement.
|
||||
'''
|
||||
def bcrypt(x: bytes, R_table: Optional[dict[int, int]] = None) -> int:
|
||||
h = -2
|
||||
for i, b in enumerate(x):
|
||||
R = R_table[b] if R_table is not None else Rb(b)
|
||||
h = h**2 * (b+1) + ((K * (i+1)) ^ R)
|
||||
h %= M
|
||||
return h % M
|
||||
|
||||
'''
|
||||
Returns a hashmap (python dictionary) of b -> R(b)
|
||||
for all b such that: min <= b <= max
|
||||
'''
|
||||
def calc_R_map(min: int = 0,
|
||||
max: int = 255) -> dict[int, int]:
|
||||
R_map = {}
|
||||
# b from min to max (inclusive)
|
||||
for b in range(min, max+1):
|
||||
R_map[b] = Rb(b)
|
||||
return R_map
|
||||
|
||||
'''
|
||||
Same as calc_R_map() but using an array not a hashmap.
|
||||
'''
|
||||
def calc_R_array(min: int = 0,
|
||||
max: int = 255) -> array.array[int]:
|
||||
R_array = array('i', [0]*(max-min))
|
||||
# b from min to max (inclusive)
|
||||
for b in range(min, max+1):
|
||||
R_array[b-min] = Rb(b)
|
||||
return R_array
|
||||
17
bcrypter/lib/format.py
Normal file
17
bcrypter/lib/format.py
Normal file
|
|
@ -0,0 +1,17 @@
|
|||
from typing import Any
|
||||
from bcrypt.lib.math import clamp_min
|
||||
|
||||
'''
|
||||
Apply left padding to str(x) for parameter x: Any
|
||||
'''
|
||||
def lpad(x: Any, n: int, pad: chr = ' ') -> str:
|
||||
x = str(x)
|
||||
width = clamp_min(n - len(x), 0)
|
||||
return width * pad + x
|
||||
|
||||
'''
|
||||
Left pad an integer's binary representation with zeros
|
||||
'''
|
||||
def lpadbin(x: int, n: int) -> str:
|
||||
return lpad(bin(x)[2:], n, pad='0')
|
||||
|
||||
16
bcrypter/lib/math.py
Normal file
16
bcrypter/lib/math.py
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
'''
|
||||
Implements numeric range clamping (idk why its not in the stdlib...)
|
||||
NOTE: the upper and lower bounds for clamping are inclusive
|
||||
'''
|
||||
def clamp(x: int, min: int, max: int) -> int:
|
||||
if x < min:
|
||||
return min
|
||||
elif x > max:
|
||||
return max
|
||||
return x
|
||||
|
||||
def clamp_min(x: int, min: int) -> int:
|
||||
return x if x > min else min
|
||||
|
||||
def clamp_max(x: int, max: int) -> int:
|
||||
return x if x < max else max
|
||||
26
bcrypter/lib/result.py
Normal file
26
bcrypter/lib/result.py
Normal file
|
|
@ -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
|
||||
Loading…
Add table
Add a link
Reference in a new issue