import sage.all as sage
from sage.schemes.elliptic_curves.constructor import EllipticCurve
from sage.rings.finite_rings.finite_field_constructor import FiniteField
from sage.groups.generic import order_from_multiple
import math
import random
import time
class EllipticCurveCryptanalysis:
def __init__(self, p, a=0, b=0, generator=None):
self.p = sage.Integer(p)
self.Fp = FiniteField(self.p)
self.a = self.Fp(a)
self.b = self.Fp(b)
self.E = EllipticCurve(self.Fp, [self.a, self.b])
self.order = self.E.order()
self.order_factors = self.order.factor()
print(f"Curve: y^2 = x^3 + {a}x + {b} over F_{p}")
print(f"Curve order: {self.order}")
print(f"Order factorization: {self.order_factors}")
if generator is None:
self.generator = self._find_generator()
else:
self.generator = generator
def _find_generator(self):
for P in self.E.points():
if not P.is_zero() and P.order() == self.order:
return P
for P in self.E.points():
if not P.is_zero():
return P
raise ValueError("No generator found")
def small_subgroup_attack(self, public_key, subgroup_order=None):
print("Performing small subgroup attack...")
if subgroup_order is None:
small_orders = [2, 3, 5, 7, 11, 13, 17, 19, 23]
for order in small_orders:
if self.order % order == 0:
x = self._attack_small_subgroup(public_key, order)
if x is not None:
return x
return None
else:
return self._attack_small_subgroup(public_key, subgroup_order)
def _attack_small_subgroup(self, public_key, subgroup_order):
cofactor = self.order // subgroup_order
Q_prime = cofactor * public_key
if Q_prime.is_zero():
print(f"Public key lies in subgroup of order dividing {subgroup_order}")
subgroup_points = []
current = public_key
for i in range(1, subgroup_order + 1):
if current.is_zero():
actual_order = i
break
subgroup_points.append(current)
current = current + public_key
actual_order = len(subgroup_points) + 1
print(f"Actual subgroup order: {actual_order}")
for x in range(actual_order):
if x * public_key == public_key:
return x
return None
def invalid_curve_attack(self):
print("Checking for invalid curve vulnerabilities...")
vulnerabilities = {}
discriminant = 18*self.a*self.b*self.a + 4*self.a**3 + 27*self.b**2
if discriminant == 0:
vulnerabilities['singular'] = "Curve is singular - vulnerable to certain attacks"
if len(self.order_factors) > 1:
vulnerabilities['composite_order'] = f"Curve order is composite: {self.order_factors}"
small_factors = []
for factor, _ in self.order_factors:
if factor < 1000: small_factors.append(factor)
if small_factors:
vulnerabilities['small_factors'] = f"Small prime factors in order: {small_factors}"
if self.order == self.p + 1:
vulnerabilities['supersingular'] = "Curve is supersingular"
try:
j = self.E.j_invariant()
if j == 0 or j == 1728:
vulnerabilities['special_j'] = f"Special j-invariant: {j}"
except:
pass
return vulnerabilities
def brute_force_attack(self, generator, public_key, max_tries=1000):
print("Performing brute force attack on ECDLP...")
for x in range(1, min(max_tries, self.order)):
if x * generator == public_key:
print(f"Found private key: {x}")
return x
print("Brute force attack failed to find solution")
return None
def pollard_rho_ecdlp(self, generator, public_key, max_iterations=10000):
if self.order < 1000:
return self.brute_force_attack(generator, public_key)
print("Performing Pollard rho attack on ECDLP...")
def partition_function(point):
if point.is_zero():
return 0
x_coord = int(point[0])
y_coord = int(point[1]) if not point.is_zero() else 0
return (x_coord + y_coord) % 32
def step_function(point, a, b):
partition = partition_function(point)
if partition % 3 == 0:
return point + generator, a + 1, b
elif partition % 3 == 1:
return 2 * point, 2 * a, 2 * b
else:
return point + public_key, a, b + 1
tortoise = (generator, 1, 0) hare = (generator, 1, 0)
for i in range(max_iterations):
tortoise = step_function(tortoise[0], tortoise[1], tortoise[2])
hare = step_function(hare[0], hare[1], hare[2])
hare = step_function(hare[0], hare[1], hare[2])
if tortoise[0] == hare[0]:
print(f"Cycle detected after {i} iterations")
a1, b1 = tortoise[1], tortoise[2]
a2, b2 = hare[1], hare[2]
try:
diff_b = b2 - b1
diff_a = a1 - a2
if diff_b == 0:
continue
diff_b_mod = diff_b % self.order
diff_a_mod = diff_a % self.order
inv_diff_b = sage.inverse_mod(diff_b_mod, self.order)
x = (diff_a_mod * inv_diff_b) % self.order
if x * generator == public_key:
print(f"Found private key: {x}")
return x
else:
continue
except:
continue
print("Pollard rho attack failed to find solution within iteration limit")
return None
def pohlig_hellman_attack(self, generator, public_key):
print("Performing Pohlig-Hellman attack...")
if len(self.order_factors) == 1:
print("Curve order is prime - Pohlig-Hellman not applicable")
return None
solutions = []
for p_i, e_i in self.order_factors:
print(f"Solving DLP modulo p_i^{e_i} = {p_i}^{e_i}")
subgroup_order = p_i ** e_i
cofactor = self.order // subgroup_order
G_i = cofactor * generator
Q_i = cofactor * public_key
x_i = self._baby_step_giant_step(G_i, Q_i, subgroup_order)
if x_i is None:
print(f"Failed to solve DLP modulo {p_i}^{e_i}")
return None
solutions.append((x_i, subgroup_order))
try:
x = self._chinese_remainder_theorem(solutions)
print(f"Pohlig-Hellman found private key: {x}")
if x * generator == public_key:
return x
else:
print("Combined solution verification failed")
return None
except Exception as e:
print(f"Chinese Remainder Theorem failed: {e}")
return None
def _baby_step_giant_step(self, generator, target, order):
m = math.ceil(math.sqrt(order))
baby_steps = {}
current = self.E(0) for i in range(m):
baby_steps[current] = i
current = current + generator
g_m = m * generator
for j in range(m):
giant_step = target - j * g_m
if giant_step in baby_steps:
return (baby_steps[giant_step] + j * m) % order
return None
def _chinese_remainder_theorem(self, solutions):
result = 0
modulus = 1
for x_i, m_i in solutions:
modulus *= m_i
for x_i, m_i in solutions:
m = modulus // m_i
inv_m = sage.inverse_mod(m, m_i)
result += x_i * m * inv_m
return result % modulus
def side_channel_attack_demo(self, generator, public_key):
print("Demonstrating side-channel attack vulnerability...")
def leaky_scalar_mult(k, G):
result = self.E(0)
for bit in reversed(k.bits()): start_time = time.time()
if bit == 1:
result = result + G result = 2 * result
operation_time = time.time() - start_time
if bit == 1:
operation_time += 0.001
print(f"Bit {bit}: operation time = {operation_time:.6f}s")
return result
recovered_bits = []
test_key = 42 Q_computed = leaky_scalar_mult(sage.Integer(test_key), generator)
print(f"Original key: {test_key}")
print(f"Computed Q: {Q_computed}")
print(f"Expected Q: {public_key}")
return recovered_bits
def comprehensive_vulnerability_assessment(self, generator, public_key):
assessment = {
'invalid_curve_checks': self.invalid_curve_attack(),
'small_subgroup_attack': None,
'pohlig_hellman_feasible': len(self.order_factors) > 1,
'pollard_rho_complexity': self._estimate_pollard_rho_complexity(),
'recommended_security': self._assess_security_level()
}
small_subgroup_result = self.small_subgroup_attack(public_key)
if small_subgroup_result is not None:
assessment['small_subgroup_attack'] = small_subgroup_result
return assessment
def _estimate_pollard_rho_complexity(self):
sqrt_order = math.sqrt(float(self.order))
if sqrt_order < 2**32:
return "FEASIBLE"
elif sqrt_order < 2**64:
return "DIFFICULT"
else:
return "INFEASIBLE"
def _assess_security_level(self):
bit_length = self.order.bit_length()
if bit_length >= 256:
return "HIGH_SECURITY"
elif bit_length >= 224:
return "MEDIUM_HIGH_SECURITY"
elif bit_length >= 192:
return "MEDIUM_SECURITY"
elif bit_length >= 128:
return "LOW_SECURITY"
else:
return "INSECURE"
def demo_attacks(self):
print("=" * 60)
print("ELLIPTIC CURVE CRYPTANALYSIS DEMONSTRATION")
print("=" * 60)
print("\n1. Testing with weak curve (small prime, composite order)")
private_key = 42
public_key = private_key * self.generator
print(f"Private key: {private_key}")
print(f"Public key: {public_key}")
assessment = self.comprehensive_vulnerability_assessment(self.generator, public_key)
print(f"\nVulnerability Assessment: {assessment}")
print("\n2. Attempting Small Subgroup Attack...")
small_attack_result = self.small_subgroup_attack(public_key)
if small_attack_result is not None:
print(f"SUCCESS: Recovered private key {small_attack_result}")
else:
print("No vulnerable small subgroup found")
print("\n3. Attempting Pohlig-Hellman Attack...")
if len(self.order_factors) > 1:
ph_attack_result = self.pohlig_hellman_attack(self.generator, public_key)
if ph_attack_result is not None:
print(f"SUCCESS: Recovered private key {ph_attack_result}")
else:
print("Pohlig-Hellman attack failed")
else:
print("Not applicable (prime order group)")
print("\n4. Demonstrating Pollard Rho Attack (limited iterations)...")
rho_result = self.pollard_rho_ecdlp(self.generator, public_key, max_iterations=1000)
if rho_result is not None:
print(f"SUCCESS: Recovered private key {rho_result}")
else:
print("Pollard rho attack did not find solution (expected for large groups)")
print("\n5. Side-Channel Attack Demonstration...")
self.side_channel_attack_demo(self.generator, public_key)
def test_weak_curves():
print("\n" + "=" * 60)
print("TESTING WEAK CURVES")
print("=" * 60)
p = 101 analysis = EllipticCurveCryptanalysis(p, a=0, b=1)
analysis.demo_attacks()
print("\n" + "-" * 60)
print("Testing another weak curve...")
p2 = 2**31 - 1 try:
analysis2 = EllipticCurveCryptanalysis(p2, a=0, b=1)
assessment = analysis2.comprehensive_vulnerability_assessment(
analysis2.generator, 42 * analysis2.generator)
print(f"Assessment for larger curve: {assessment}")
except Exception as e:
print(f"Failed to analyze larger curve: {e}")
if __name__ == "__main__":
test_weak_curves()