def __init__(self, key: bytes = None, H: FunctionType = SHA256().hash, q: int = DiffieHellman.MODP_2048): """ Parameters: key (bytes): Bytes-like object shared by both parties to authenticate each other. H (func): Cryptographic hash function. Takes in bytes and returns the hash digest. q (int): Modulus. """ Primitive.__init__(self) self.key = key or Bytes(random_int_between(1, q)) self.q = q self.A = random_int_between(1, q) self.a = random_int_between(1, q) self.H = H
def sign(self, message: bytes, k: int = None) -> (int, int): """ Signs a `message`. Parameters: message (bytes): Message to sign. k (int): (Optional) Ephemeral key. Returns: (int, int): Signature formatted as (r, s). """ r = 0 s = 0 while s == 0 or r == 0: k = k or random_int_between(1, self.q) inv_k = mod_inv(k, self.q) z = self.hash_obj.hash(message).int() z >>= max(self.hash_obj.digest_size * 8 - self.q.bit_length(), 0) r = int((k * self.G).x) % self.q s = (inv_k * (z + self.d * r)) % self.q return (r, s)
def execute(self, public_key: int, max_factor_size: int = 2**16) -> int: """ Executes the attack. Parameters: public_key (int): Diffie-Hellman public key to crack. max_factor_size (int): Max factor size to prevent attempting to factor forever. Returns: int: Private key. """ # Factor as much as we can factors = [ r for r in factorint((self.p - 1) // self.order, use_rho=False, limit=max_factor_size) if r < max_factor_size ] log.debug(f'Found factors: {factors}') residues = [] # Request residues from crafted public keys for factor in RUNTIME.report_progress( factors, desc='Sending malicious public keys', unit='factor'): h = 1 while h == 1: h = pow(random_int_between(1, self.p), (self.p - 1) // factor, self.p) residue = self.oracle.request(h, factor) residues.append((residue, factor)) # Build partials using CRT n, r = crt(residues) # Oh, I guess we already found it... if r > self.order: return n g_prime = pow(self.g, r, self.p) y_prime = (public_key * mod_inv(pow(self.g, n, self.p), self.p)) % self.p log.info( f'Recovered {"%.2f"%math.log(reduce(int.__mul__, factors, 1), 2)}/{"%.2f"%math.log(self.order, 2)} bits' ) log.info(f'Found relation: x = {n} + m*{r}') log.debug(f"g' = {g_prime}") log.debug(f"y' = {y_prime}") log.info('Attempting to catch a kangaroo...') # Probabilistically solve DLP R = (ZZ / ZZ(self.p)).mul_group() m = pollards_kangaroo(R(g_prime), R(y_prime), a=0, b=(self.order - 1) // r) return n + m * r
def sign(self, message: bytes, k: int=None) -> (int, int): """ Signs a `message`. Parameters: message (bytes): Message to sign. k (int): (Optional) Ephemeral key. Returns: (int, int): Signature formatted as (r, s). """ k = k or random_int_between(1, self.q) inv_k = mod_inv(k, self.q) r = pow(self.g, k, self.p) % self.q s = (inv_k * (self.hash_obj.hash(message).int() + self.x * r)) % self.q return (r, s)
def __init__(self, G: WeierstrassCurve, hash_obj: object = SHA256(), d: int = None): """ Parameters: G (WeierstrassCurve): Generator point for a curve. hash_obj (object): Instantiated object with compatible hash interface. d (int): (Optional) Private key. """ Primitive.__init__(self) self.G = G self.q = self.G.curve.q self.d = Bytes.wrap(d).int() if d else random_int_between(1, self.q) self.Q = self.d * self.G self.hash_obj = hash_obj
def random(self, size: int = None) -> object: """ Generate a random element. Parameters: size (int): The ring-specific 'size' of the element. Returns: MontgomeryPoint: Random element of the algebra. """ while True: try: return self.clamp_to_curve( random_int_between(1, size or self.p)) except AssertionError: pass
def __init__(self, g: int = 2, p: int = DiffieHellman.MODP_2048, key: int = None): """ Parameters: g (int): Generator. p (int): Prime modulus. key (int): Key. """ Primitive.__init__(self) self.key = key or random_int_between(1, p) self.g = g self.p = p self.pub = pow(self.g, self.key, self.p)
def __init__(self, g: int = 2, p: int = MODP_2048, q: int = None, key: int = None): """ Parameters: key (int): Secret key. g (int): Exponent base. p (int): Modulus. q (int): Order. """ Primitive.__init__(self) self.key = key or random_int_between(2, q or p) self.g = g self.p = p self.q = q
def random(self, size: WeierstrassPoint = None) -> WeierstrassPoint: """ Generate a random element. Parameters: size (int): The ring-specific 'size' of the element. Returns: WeierstrassPoint: Random element of the algebra. """ while True: try: return self.recover_point_from_x( random_int_between(1, int(size.x) if size else self.p)) except AssertionError: pass
def __init__(self, hash_obj: object=SHA256(), p: int=None, q: int=None, g: int=None, x: int=None, L: int=2048, N: int=256): """ Parameters: hash_obj (object): Instantiated object with compatible hash interface. p (int): (Optional) Prime modulus. q (int): (Optional) Prime modulus. g (int): (Optional) Generator. x (int): (Optional) Private key. L (int): (Optional) Bit length of `p`. N (int): (Optional) Bit length of `q`. """ Primitive.__init__(self) # Parameter generation # https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.186-4.pdf if not q: q = find_prime(N) # Start somewhere in 2**(L-1); ensure it's even i = Bytes.random((L-1) // 8).int() // 2 * 2 # Construct the base as an even multiple of `q` base = 2**(L-1) // (2*q) * 2 while not is_prime((base + i) * q + 1): i += 2 p = (base + i) * q + 1 assert (p-1) % q == 0 # Construct `g` while True: h = Bytes.random(N // 8).int() % (p-1) g = pow(h, (p-1) // q, p) if h > 1 and h < (p-1) and g > 1: break self.p = p self.q = q self.g = g self.x = x or random_int_between(1, self.q) self.y = pow(self.g, self.x, self.p) self.hash_obj = hash_obj
def encrypt(self, plaintext: bytes, k: int = None) -> (int, int): """ Encrypts `plaintext`. Parameters: plaintext (bytes): Message to encrypt. k (int): (Optional) Ephemeral key. Returns: (int, int): Formatted as (ephemeral key, ciphertext). References: https://en.wikipedia.org/wiki/ElGamal_encryption """ K_e = k or random_int_between(1, self.p) c_1 = pow(self.g, K_e, self.p) s = pow(self.pub, K_e, self.p) plaintext = Bytes.wrap(plaintext) return c_1, (s * plaintext.int()) % self.p
def __init__(self, d: int = None, pub: int = None, base: int = None, curve: MontgomeryCurve = Curve25519): """ Parameters: d (int): Secret key that will be clamped to the curve. base (int): Base multiplier used in generating the challenge. curve (MontgomeryCurve): The curve used. """ Primitive.__init__(self) self.d = Bytes.wrap(d or random_int_between(1, curve.ring.order)).int() self.curve = curve self.key = curve.clamp_to_curve(self.d) self.base = base or curve.U self.pub = pub if not pub: self.recompute_public()