formalised project structure
This commit is contained in:
parent
897272d7c1
commit
6f8a7322f2
14 changed files with 177 additions and 71 deletions
0
bcrypt/__init__.py
Normal file
0
bcrypt/__init__.py
Normal file
0
bcrypt/cli/__init__.py
Normal file
0
bcrypt/cli/__init__.py
Normal file
|
|
@ -1,54 +1,6 @@
|
||||||
from math import gcd
|
from math import gcd
|
||||||
from random import randint, randbytes
|
from random import randint, randbytes
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
'''
|
|
||||||
Returns a hashmap (python dictionary) of b -> R(b)
|
|
||||||
for all b such that: min < b < max
|
|
||||||
'''
|
|
||||||
def precompute_R(min: int,
|
|
||||||
max: int) -> dict[int, int]:
|
|
||||||
b = min
|
|
||||||
R_table = {}
|
|
||||||
while b <= max:
|
|
||||||
R_table[b] = Rb(b)
|
|
||||||
b += 1
|
|
||||||
return R_table
|
|
||||||
|
|
||||||
|
|
||||||
def hashfn(x: bytes, R_table: dict[int, int]) -> int:
|
|
||||||
h = -2
|
|
||||||
for i in range(len((x))):
|
|
||||||
b = x[i]
|
|
||||||
h = h**2 * (b+1) + ((K * (i+1)) ^ R_table[b])
|
|
||||||
h %= M
|
|
||||||
return h % M
|
|
||||||
|
|
||||||
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
|
|
||||||
|
|
||||||
def debug_hashes_eq(x: bytes, R_table: dict[int, int]) -> bool:
|
def debug_hashes_eq(x: bytes, R_table: dict[int, int]) -> bool:
|
||||||
return hashfn(x, R_table) == bcrypt(x)
|
return hashfn(x, R_table) == bcrypt(x)
|
||||||
|
|
||||||
0
bcrypt/debug/__init__.py
Normal file
0
bcrypt/debug/__init__.py
Normal file
1
bcrypt/debug/constants.py
Normal file
1
bcrypt/debug/constants.py
Normal file
|
|
@ -0,0 +1 @@
|
||||||
|
|
||||||
18
bcrypt/debug/constituents.py
Normal file
18
bcrypt/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
bcrypt/debug/hasheq.py
Normal file
39
bcrypt/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
|
||||||
0
bcrypt/hash/__init__.py
Normal file
0
bcrypt/hash/__init__.py
Normal file
15
bcrypt/hash/og.py
Normal file
15
bcrypt/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
bcrypt/hash/rev.py
Normal file
59
bcrypt/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
bcrypt/lib/format.py
Normal file
17
bcrypt/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
bcrypt/lib/math.py
Normal file
16
bcrypt/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
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
from typing import Any
|
|
||||||
|
|
||||||
def clamp_pos(x: int):
|
|
||||||
return x if x > 0 else 0
|
|
||||||
|
|
||||||
def lpad(x: Any, n: int, pad: chr = ' '):
|
|
||||||
x = str(x)
|
|
||||||
return clamp_pos(n - len(x))*pad + x
|
|
||||||
|
|
||||||
def debug_R(B: int):
|
|
||||||
# B = int('1'*7, 2)
|
|
||||||
for j in range(8):
|
|
||||||
j_sq = j**2
|
|
||||||
R_j = B << j_sq
|
|
||||||
lpadbin_R_j = lpad(bin(R_j)[2:], 64, pad='0')
|
|
||||||
print(f'{lpad(j_sq, 2)}: {lpadbin_R_j} {R_j}')
|
|
||||||
|
|
||||||
def main():
|
|
||||||
B = int(input('B: '), 2)
|
|
||||||
debug_R(B)
|
|
||||||
|
|
||||||
if __name__ == '__main__':
|
|
||||||
main()
|
|
||||||
12
bcrypter.py
Normal file
12
bcrypter.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')
|
||||||
Loading…
Add table
Add a link
Reference in a new issue