|
#!/usr/bin/env python3.8 |
|
|
|
from os import urandom |
|
from math import gcd |
|
from random import getrandbits, randrange |
|
from hashlib import sha256 |
|
from collections import namedtuple |
|
|
|
from gmpy2 import is_prime, next_prime |
|
|
|
|
|
def H(x): |
|
hash_ = sha256(x).digest() |
|
return int.from_bytes(hash_, 'big') |
|
|
|
|
|
def random_prime(bits): |
|
msb = 0b11 << (bits - 2) |
|
rand = getrandbits(bits) |
|
return int(next_prime(msb | rand)) |
|
|
|
|
|
DSAParams = namedtuple('DSAParams', ('q', 'p', 'g')) |
|
DSASignature = namedtuple('DSASignature', ('r', 's')) |
|
DSAPublicKey = namedtuple('DSAPublicKey', ('y')) |
|
DSAPrivateKey = namedtuple('DSAPrivateKey', ('x')) |
|
|
|
|
|
class DSA: |
|
@staticmethod |
|
def params(q_bits, p_bits): |
|
q = random_prime(q_bits) |
|
|
|
while True: |
|
t = getrandbits(p_bits - q_bits - 1) |
|
p = 2 * t * q + 1 |
|
|
|
if is_prime(p) and p.bit_length() == p_bits: |
|
break |
|
|
|
while True: |
|
h = randrange(2, p - 1) |
|
g = pow(h, 2 * t, p) |
|
|
|
if pow(g, q, p) == 1: |
|
break |
|
|
|
return DSAParams(q, p, g) |
|
|
|
@staticmethod |
|
def keypair(params): |
|
x = randrange(1, params.q) |
|
y = pow(params.g, x, params.p) |
|
|
|
return DSAPublicKey(y), DSAPrivateKey(x) |
|
|
|
@staticmethod |
|
def sign(params, private, message): |
|
while True: |
|
while True: |
|
k = randrange(1, params.q) |
|
r = pow(params.g, k, params.p) % params.q |
|
|
|
if r != 0: |
|
break |
|
|
|
s = pow(k, -1, params.q) * (H(message) + private.x * r) % params.q |
|
|
|
if s != 0: |
|
break |
|
|
|
return DSASignature(r, s) |
|
|
|
@staticmethod |
|
def verify(params, public, message, signature): |
|
if not 0 < signature.r < params.q or not 0 < signature.s < params.q: |
|
return False |
|
|
|
w = pow(signature.s, -1, params.q) |
|
u1 = H(message) * w % params.q |
|
u2 = signature.r * w % params.q |
|
v = pow(params.g, u1, params.p) * pow(public.y, u2, params.p) % params.p % params.q |
|
|
|
return v == signature.r |
|
|
|
|
|
def DSA_duplicate(params, public, message, signature): |
|
w = pow(signature.s, -1, params.q) |
|
u1 = H(message) * w % params.q |
|
u2 = signature.r * w % params.q |
|
z = gcd(u1, u2) |
|
v = pow(params.g, u1 // z, params.p) * pow(public.y, u2 // z, params.p) % params.p |
|
|
|
while True: |
|
fake_x = randrange(1, params.q) |
|
t = (u1 + u2 * fake_x) // z |
|
|
|
if gcd(t, params.p - 1) == 1: |
|
break |
|
|
|
i = pow(t, -1, params.p - 1) |
|
fake_g = pow(v, i, params.p) |
|
fake_y = pow(fake_g, fake_x, params.p) |
|
|
|
fake_params = DSAParams(params.q, params.p, fake_g) |
|
fake_public = DSAPublicKey(fake_y) |
|
|
|
return fake_params, fake_public |
|
|
|
|
|
def main(): |
|
q_bits, p_bits = 160, 1024 |
|
message_length = 1000 |
|
tries = 100 |
|
|
|
passed = 0 |
|
|
|
for i in range(tries): |
|
print(f'DSA TEST {i}') |
|
message = urandom(message_length) |
|
|
|
params = DSA.params(q_bits, p_bits) |
|
public, private = DSA.keypair(params) |
|
signature = DSA.sign(params, private, message) |
|
|
|
if not DSA.verify(params, public, message, signature): |
|
print('sign+verify error!') |
|
continue |
|
|
|
fake_params, fake_public = DSA_duplicate(params, public, message, signature) |
|
|
|
if DSA.verify(fake_params, fake_public, message, signature): |
|
print('PASSED') |
|
passed += 1 |
|
else: |
|
print('FAILED') |
|
|
|
print(f'PASSED {passed} / {tries} tests') |
|
|
|
|
|
if __name__ == '__main__': |
|
main() |