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 prepare_fft(v1, v2): v1_len = len(v1) v2_len = len(v2) # Pad vectors for radix-2 FFT max_len = max(v1_len, v2_len) vec_len = 2**math.ceil(math.log(max_len * 2, 2)) v1 = v1 + [0] * (vec_len - v1_len) v2 = v2 + [0] * (vec_len - v2_len) # Prepare NTT twiddle factors root, modulus = generate_ntt_params(v1, v2) twiddle_factors = [] inv_twid_factors = [] twiddle = 1 inv_twid = 1 inv_root = mod_inv(root, modulus) for _ in range(vec_len // 2): twiddle_factors.append(twiddle) twiddle = (twiddle * root) % modulus inv_twid_factors.append(inv_twid) inv_twid = (inv_twid * inv_root) % modulus return (v1, v2), root, modulus, twiddle_factors, inv_twid_factors
def decrypt(self, sums: list) -> Bytes: """ Decrypts `sums` back into plaintext. Parameters: sums (list): List of ciphertext sums. Returns: Bytes: Decrypted plaintext. """ r_inv = mod_inv(self.r, self.q) inv_sums = [(byte_sum * r_inv) % self.q for byte_sum in sums] plaintext = Bytes(b'') for inv_sum in inv_sums: curr = inv_sum bin_string = '' for i in range(len(self.pub) - 1, -1, -1): if self.priv[i] <= curr: curr -= self.priv[i] bin_string += '1' else: bin_string += '0' plaintext += int.to_bytes(int(bin_string[::-1], 2), len(self.pub) // 8, 'big') return plaintext
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 fft_op(v1, v2, operation: FFTOp = FFTOp.CONVOLVE): (v1, v2), _root, modulus, twiddle_factors, inv_twid_factors = prepare_fft( v1, v2) v1_t = fft(v1, modulus, twiddle_factors) v2_t = fft(v2, modulus, twiddle_factors) v3_t = [operation(a, b, modulus) for a, b in zip(v1_t, v2_t)] v3 = fft(v3_t, modulus, inv_twid_factors) scaler = mod_inv(len(v1), modulus) return [val * scaler % modulus for val in v3]
def get_challenge(self) -> (int, int): """ Gets the challenge. Returns: (int, int): The challenge. """ sA = self.a + self.A PE = self.H(self.key).int() eA = mod_inv(pow(PE, self.A, self.q), self.q) return pow(PE, sA, self.q), eA
def assert_correct(self, c_b: int) -> bool: """ Processes the final challenge and asserts its correctness. Parameters: c_b (int): Peer's final challenge. Returns: bool: Whether or not the challenge was correct. """ c = pow(c_b, self.exp2, self.p) return c == ((self.P * mod_inv(self.P_b, self.p)) % self.p)
def __init__(self, p: int=None, q: int=None): """ Parameters: p (int): Large prime. q (int): Large prime. """ self.p = p or find_prime(512) self.q = q or find_prime(512) self.n = self.p * self.q self.phi = (self.p - 1) * (self.q - 1) self.g = self.n + 1 self.priv = mod_inv(self.phi, self.n)
def decrypt(self, key_and_ciphertext: (int, int)) -> Bytes: """ Decrypts `key_and_ciphertext`. Parameters: key_and_ciphertext ((int, int)): Ephemeral key and ciphertext. Returns: Bytes: Plaintext. """ c_1, ciphertext = key_and_ciphertext s = pow(c_1, self.key, self.p) return Bytes((mod_inv(s, self.p) * ciphertext) % self.p)
def test_import_openssh(self): for key, passphrase in [ TEST_OPENSSH0, TEST_OPENSSH1, TEST_OPENSSH2, TEST_OPENSSH3 ]: if passphrase: with self.assertRaises(ValueError): RSA.import_key(key) rsa = RSA.import_key(key, passphrase=passphrase) self.assertEqual(rsa.p * rsa.q, rsa.n) self.assertEqual(rsa.alt_d, mod_inv(rsa.e, (rsa.p - 1) * (rsa.q - 1))) self.assertTrue(is_prime(rsa.p)) self.assertTrue(is_prime(rsa.q))
def derive_x_from_k(self, message: bytes, k: int, sig: (int, int)) -> int: """ Derives `x` from a known `k`. Parameters: message (bytes): Message. k (int): `k` used in `message`'s signature. sig ((int, int)): Signature of `message`. Returns: int: Derived `x`. """ (r, s) = sig return ((s * k) - self.hash_obj.hash(message).int()) * mod_inv(r, self.q) % self.q
def crack(states: list, multiplier: int = None, increment: int = None, modulus: int = None, sanity_check: bool = True): """ Given a few full states (probably under ten) and any (or even none) of the parameters of an LCG, returns a replica LCG. Parameters: states (list): List of full-state outputs (in order). multiplier (int): (Optional) The LCG's multiplier. increment (int): (Optional) The LCG's increment. modulus (int): (Optional) The LCG's modulus. sanity_check (bool): Whether to tests the generated LCG against the provided states. Returns: LCG: Replica LCG that predicts all future outputs of the original. References: https://tailcall.net/blog/cracking-randomness-lcgs/ """ if not modulus: diffs = [ state1 - state0 for state0, state1 in zip(states, states[1:]) ] congruences = [ t2 * t0 - t1 * t1 for t0, t1, t2 in zip(diffs, diffs[1:], diffs[2:]) ] modulus = abs(functools.reduce(gcd, congruences)) if not multiplier: multiplier = (states[2] - states[1]) * mod_inv( states[1] - states[0], modulus) % modulus if not increment: increment = (states[1] - states[0] * multiplier) % modulus # Sanity test lcg = LCG(states[0], multiplier, increment, modulus) num_tests = min(3, len(states) - 1) if sanity_check and [lcg.generate() for _ in range(num_tests) ] != states[1:1 + num_tests]: raise RuntimeError( "Generated LCG does not match 'states'. Are you sure this came from an untruncated LCG?" ) return LCG(states[-1], multiplier, increment, modulus)
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 get_final_challenge(self, challenge: (int, int)) -> int: """ Uses its peer's P and Q values to generate the final challenge. Parameters: challenge ((int, int)): P and Q values of peer's challenge. Returns: int: The final challenge. """ self.P_b, Q_b = challenge if self.validate: assert self.P != self.P_b assert self.Q != Q_b return pow(Q_b * mod_inv(self.Q, self.p), self.exp2, self.p)
def verify(self, message: bytes, sig: (int, int)) -> bool: """ Verifies a `message` against a `sig`. Parameters: message (bytes): Message. sig ((int, int)): Signature of `message`. Returns: bool: Whether the signature is valid or not. """ (r, s) = sig w = mod_inv(s, self.q) u_1 = (self.hash_obj.hash(message).int() * w) % self.q u_2 = (r * w) % self.q v = (pow(self.g, u_1, self.p) * pow(self.y, u_2, self.p) % self.p) % self.q return v == r
def generate_backdoor( curve: WeierstrassCurve ) -> (WeierstrassPoint, WeierstrassPoint, int): """ Generates backdoored parameters. Parameters: curve (WeierstrassCurve): Curve to use. Returns: (WeierstrassPoint, WeierstrassPoint, int): Result formatted as (P, backdoored Q, backdoor d) """ P = curve.G d = random.randint(2, curve.q) e = mod_inv(d, curve.q) Q = e * P return P, Q, d
def derive_k_from_sigs(self, msg_a: bytes, sig_a: (int, int), msg_b: bytes, sig_b: (int, int)) -> int: """ Derives `k` from signatures that share an `r` value. Parameters: msg_a (bytes): Message A. msg_b (bytes): Message B. sig_a ((int, int)): Signature of `msg_a`. sig_b ((int, int)): Signature of `msg_b`. Returns: int: Derived `k`. """ (r_a, s_a) = sig_a (r_b, s_b) = sig_b assert r_a == r_b s = (s_a - s_b) % self.q m = (self.hash_obj.hash(msg_a).int() - self.hash_obj.hash(msg_b).int()) % self.q return mod_inv(s, self.q) * m % self.q
def verify(self, message: bytes, sig: (int, int)) -> bool: """ Verifies a `message` against a `sig`. Parameters: message (bytes): Message. sig ((int, int)): Signature of `message`. Returns: bool: Whether the signature is valid or not. """ (r, s) = sig w = mod_inv(s, self.q) z = self.hash_obj.hash(message).int() z >>= max(self.hash_obj.digest_size * 8 - self.q.bit_length(), 0) u_1 = (z * w) % self.q u_2 = (r * w) % self.q v = u_1 * self.G + u_2 * self.Q return v.x == r
def invert_poly(f_poly: Polynomial, R_poly: Polynomial, p: int) -> Polynomial: """ Inverts a polynomial `f_poly` over `R_poly` in GF(p). Parameters: f_poly (Polynomial): Polynomial to be inverted. R_poly (Polynomial): Polynomial to be inverted _over_. p (int): Integer modulus. Returns: Polynomial: Inverted polynomial. """ power_of_two = is_power_of_two(p) if is_prime(p) or power_of_two: if power_of_two: Z_p = ZZ / ZZ(2) else: Z_p = ZZ / ZZ(p) f_poly_p = Polynomial([(idx, Z_p[coeff]) for idx, coeff in f_poly.coeffs], Z_p) R_poly_p = Polynomial([(idx, Z_p[coeff]) for idx, coeff in R_poly.coeffs], Z_p) inv_poly = mod_inv(f_poly_p, R_poly_p) inv_poly = Polynomial([(idx, ZZ[int(coeff)]) for idx, coeff in inv_poly.coeffs], ZZ) if power_of_two: for _ in range(int(math.log(p, 2))): inv_poly = (2 * inv_poly) - (f_poly * (inv_poly**2)) inv_poly = (inv_poly % R_poly).trunc(p) else: raise Exception( f"Polynomial not invertible in Z_{p}. NTRU: p and q must be prime or power of two." ) return inv_poly
def __init__(self, a: int, b: int, alphabet: str = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ ,.'): """ Parameters: a (int): Multiplier. b (int): Linear shift. alphabet (str): Alphabet (in order) to encrypt over. Input must also be in this alphabet. """ self.a = a self.b = b self.alphabet = alphabet self.char_map = {} for i in range(len(alphabet)): self.char_map[alphabet[i]] = alphabet[(a * i + b) % len(alphabet)] inv_a = mod_inv(a, len(alphabet)) self.inv_char_map = {} for i in range(len(alphabet)): self.inv_char_map[alphabet[i]] = alphabet[(inv_a * (i - b)) % len(alphabet)]
def encode(rsa_key: object, **kwargs): user = kwargs.get('user') if user and type(user) is str: user = user.encode('utf-8') public_key = RSAPublicKey('public_key', rsa_key.n, rsa_key.e) private_key = RSAPrivateKey('private_key', check_bytes=None, n=rsa_key.n, e=rsa_key.e, d=rsa_key.alt_d, q_mod_p=mod_inv(rsa_key.q, rsa_key.p), p=rsa_key.p, q=rsa_key.q, host=user or b'nohost@localhost') encoded = generate_openssh_private_key(public_key, private_key, kwargs.get('encode_pem'), kwargs.get('marker'), kwargs.get('encryption'), kwargs.get('iv'), kwargs.get('passphrase')) return encoded
class FFTOp(Enum): CONVOLVE = lambda a, b, mod: a * b DECONVOLVE = lambda a, b, mod: a * mod_inv(b, mod)
def __invert__(self) -> object: return QuotientElement(mod_inv(self.val, self.ring.quotient), self.ring)
def __init__(self, bits: int = None, p: int = None, q: int = None, e: int = 65537, n: int = None): """ Parameters: bits (int): Number of bits for strength and capacity. p (int): Secret prime modulus. q (int): Secret prime modulus. e (int): Public exponent. n (int): Public modulus. """ Primitive.__init__(self) self.e = e phi = 0 if p and q: phi = lcm(p - 1, q - 1) self.n = p * q if gcd(self.e, phi) != 1: raise Exception("Invalid 'p' and 'q': GCD(e, phi) != 1") bits = p.bit_length() + q.bit_length() elif n: self.n = n else: next_p = p next_q = q # Take into account the bits needed to complete `bits` if `p` or `q` are already defined if p: q_bits = bits - p.bit_length() else: q_bits = bits // 2 if q: p_bits = bits - q.bit_length() else: p_bits = bits // 2 # Find the primes while gcd(self.e, phi) != 1 or next_p == next_q: if not p: next_p = find_prime(p_bits) if not q: next_q = find_prime(q_bits) phi = lcm(next_p - 1, next_q - 1) p = next_p q = next_q self.n = p * q self.p = p self.q = q self.phi = phi self.bits = bits if self.p and self.q: self.d = mod_inv(self.e, phi) self.alt_d = mod_inv(self.e, (self.p - 1) * (self.q - 1)) self.dP = self.d % (self.p - 1) self.dQ = self.d % (self.q - 1) self.Qi = mod_inv(self.q, self.p) else: self.d = None self.alt_d = None self.pub = (self.e, self.n) self.priv = (self.d, self.n)
def ntt_inv(vec_t: list, root: int, modulus: int): vec = ntt(vec_t, mod_inv(root, modulus), modulus) scaler = mod_inv(len(vec_t), modulus) return [val * scaler % modulus for val in vec]