def main_DSA(): a,A = ec.gen_key(G, Go) # Alice's key msg = b'Everybody knows that the boat is leaking' sig = ec.dsa_sign(msg, a) assert ec.dsa_verify(msg, sig, A) # assume Eve can pick the base point she wants (as if it was # part of her public key), then it is trivial to forge a perfectly # valid key that verifies the signature r,s = sig # compute the constants sinv = invmodp(s,Go) u1 = (ec.H(msg) * sinv) % Go u2 = (r * sinv) % Go R = u1*G + u2*A # forge a key e = secrets.randbelow(Go) Gforged = invmodp(u1 + u2*e, Go)*R E = e*Gforged # verify print('Verifying with forged ECDSA key...', end=' ', flush=True) ec.set_base_point(Gforged, Go) assert (Go*Gforged).is_zero() assert ec.dsa_verify(msg, sig, E) print('ok.\n')
def __add__(self, B): if self.is_zero(): return copy(B) if B.is_zero(): return copy(self) if self.x == B.x: if (self.y + B.y) % P == 0: # inverse case return Zero() else: m = ((3 * self.x * self.x + _a) * invmodp(2 * self.y, P)) % P else: m = ((B.y - self.y) * invmodp(B.x - self.x, P)) % P x = (m * m - self.x - B.x) % P y = (m * (self.x - x) - self.y) % P return Point(x, y)
def dsa_verify(msg, sig, Pub): r, s = sig sinv = invmodp(s, _Go) u1 = (H(msg) * sinv) % _Go u2 = (r * sinv) % _Go R = u1 * _G + u2 * Pub return r == R.x % _Go
def attack(): # consider DSA (mod q the order of G): # random k (nonce / ephemeral DH private key) # r = (k*G).x (ephemeral DH public key) # s = ((H(msg) + d*r) * k^-1 (d the private key to guess) # and assume the last l bits (say l=8) of k are constant, say 0, # then k = b*2^l for some random b ~ q/2^l # s*b*2^l = H(msg) + d*r # b = -u + d*t for u = -H(msg)*s^-1*2^-l and t = r*s^-1*2^-l # with u, d*t ~ q >> q/2^l ~ b (for large enough l) # hence we can approx. # 0 ~ u - d*t (mod q) # 0 ~ i + m*q - d*t for some integer m # say we capture n signatures, for i = 1..n # 0 ~ ui + mi*q - d*ti # where ui,ti,q are known, mi,d are unknown # let B = [q 0 0 .. 0 t1 u1] # [0 q 0 .. 0 t2 u2] # [0 0 q .. 0 t3 u3] # .. # [0 0 0 .. q tn un] # [0 0 0 .. 0 ct 0] <-- added to squarify the matrix # [0 0 0 .. 0 0 cu] <-- for some constants ct,cu # then B * [m1 .. mn -d 1] ~ [0 .. 0 -d*ct cu] q = ec._Go l = 8 # <-- the less bits known n = 20 # <-- the more signatures needed _d, _ = ec.gen_key(ec._G, ec._Go) B = [] T = [] U = [] for i in range(n): msg = os.urandom(10) r, s = biased_sign(msg, _d, l) h = ec.H(msg) s2l_inv = invmodp(s * (1 << l), q) u = (-h * s2l_inv) % q t = (r * s2l_inv) % q T.append(t) U.append(u) B.append(Vec([q if j == i else 0 for j in range(n + 2)])) ct = Decimal(1) / Decimal(1 << l) cu = Decimal(q) / Decimal(1 << l) T += [ct, 0] U += [0, cu] B += [Vec(T), Vec(U)] R = LLL(B) for v in R: if v[-1] == cu: d = -v[-2] / ct break print(d) print(_d) assert d == _d
def montgomery_ladder(u: int, k: int) -> int: # we should check that the coordinate u is on the curve # (otherwise it would be on the twist) # but we do not do it here to allow some of the attacks #assert montgomery_is_valid(u) u2, w2 = 1, 0 u3, w3 = u, 1 for i in range(P.bit_length() - 1, -1, -1): b = 1 & (k >> i) if b: u2, u3 = u3, u2 w2, w3 = w3, w2 u3, w3 = pow(u2 * u3 - w2 * w3, 2, P), (u * pow(u2 * w3 - w2 * u3, 2, P)) % P u2, w2 = pow(u2 * u2 - w2 * w2, 2, P), (4 * u2 * w2 * (u2 * u2 + _A * u2 * w2 + w2 * w2)) % P if b: u2, u3 = u3, u2 w2, w3 = w3, w2 return (u2 * invmodp(w2, P)) % P
## Practical attack # generate Bob's key _b = secrets.randbelow(q) # private < q B = pow(g, _b, p) # public # PH attack F = [r for r, m in small_factors((p - 1) // q)] X = 0 R = 1 for r in F: # we skip to the important part: Eve has deduced _b%r x = _b % r X, R = CRT_combine(X, R, x, r) # we know b = X mod R # b = X + K*R mod q # Rewrite the problem: # B = g^(X + K*R) # B*g^(-X) = (g^R)^K Bp = (B * invmodp(pow(g, X, p), p)) % p gp = pow(g, R, p) # New DL problem: # B' = g'^K with K in [0, (q-1)/R] K = pollard_lambda(Bp, 0, (q - 1) // R, g=gp) b_ = X + K * R print(b_) assert b_ == _b
def biased_sign(msg, d, l=8): k = secrets.randbelow(ec._Go >> l) << l r = (k * ec._G).x % ec._Go s = ((ec.H(msg) + d * r) * invmodp(k, ec._Go)) % ec._Go return (r, s)
def montgomery_v_from_u(u): # output is None if no solution # if output is v, -v is also solution Bv2 = (pow(u, 3, P) + _A * u * u + u) % P v2 = (Bv2 * invmodp(_B, P)) % P return shanks_tonelli(v2, P)
def montgomery_is_valid(u, v=None): if v is None: # single-coordinate check v2 = (invmodp(_B, P) * (pow(u, 3, P) + _A * u * u + u)) % P return legendre(v2, P) == 1 # point check return (_B * v * v - pow(u, 3, P) - _A * u * u - u) % P == 0
def dsa_sign(msg, priv): k = 1 + secrets.randbelow(_Go - 1) r = (k * _G).x % _Go s = ((H(msg) + priv * r) * invmodp(k, _Go)) % _Go return (r, s)