# In order to break lazarus, you would have to # a) break AES # b) break ECC # c) break ChaCha20 # d) break prime-factorization # e) break cbc (ok, that's quite doable...) # # And still here we are: # This script is able to decrypt any ciphertext encrypted using lazarus :D # Note: Thats the idea; its not functional yet... Mostly because CBC turns out # to be a harder opponent then I throught, even for stream-ciphers... # Im probably going to need to make some changes to the implementation-specifics # in order to make breaking it possible without adding artificual correlations # in the pre-Omega ciphertext... from fastecdsa.curve import P256 from fastecdsa.point import Point from fastecdsa import util from lazarus import Lazarus class Bethany(): pass #--- e = 31415926535987932384626433832795 Q = e*P256.G class Generalised_Dual_EC_RBG(object): def __init__(self, Q, seed, curve = P256): self.curve = curve self.state = seed self.Q = Q self.P = curve.G self.tmp = None assert Q.curve == curve def gen(self): new_point = self.state * self.P sP = r = new_point.x # remember that the x value of the new point is used for the next point. rQ = r * self.P random_int_to_return = int(str(bin((rQ).x))[16:], 2) self.state = (r*self.Q).x self.lsb = str(bin((rQ).x)) self.rQ = rQ return random_int_to_return class breakEccPerm(): def __init__(self): pass def smash(omegaKey): integer = int.from_bytes(omegaKey, "big", seed, signed=False ) breakEccPerm.get_identical_generator(integer, second_output, e, curve) def get_identical_generator(output, second_output, e, curve): # make a new generator and instantiate it with one possible state out of the 65535 for lsb in range(2**16): # rudimentary progress bar if (lsb % 2048) == 0: print("{}% done checking\r".format(100*lsb/(2**16))) # bit-shift and then concat to guess most significant bits that were discarded overall_output = (lsb << (output.bit_length()) | output) # zeroth check: is the value greater than p? if overall_output > curve.p: global first_rQ # this is only used for debugging and can be removed # if it is greater, skip this number # since the most significant bits are iterated through in ascending order. # if it reaches that point that means we know something went wrong and we can break out print("""Something went wrong. debugging info: Output = {}, lsb = {}, rQ = {}""".format(output, lsb, first_rQ)) break # calculate a value of y for sol_to_y in util.mod_sqrt(overall_output**3 - 3*overall_output + curve.b, curve.p): # there are either 2 or 0 real answers to the square root. We reject those greater than p. if sol_to_y < curve.p: possible_y = sol_to_y else: possible_y = None # first check: if there were 0 solutions we can skip this iteration if possible_y == None or type(possible_y) != int: continue # second check: is point on curve? if not then skip this iteration try: possible_point = Point(overall_output, possible_y, curve=curve) except: continue # if checks were passed, exploit the relation between state to calculate the internal state possible_state = (e * possible_point).x # check if the state is correct by generating another output possible_generator = Generalised_Dual_EC_RBG(Q=Q, seed=possible_state) if possible_generator.gen() == second_output: break return possible_generator