Skip to content

DSA

DSA

  • Very similar to ECDSA and EdDSA but with exponents rather than multiplication.

Implementation

from Crypto.PublicKey import DSA

import secrets, hashlib
from cryptopals_lib import bytes_to_int, int_to_bytes

def gen_random(low, high):
	random = 0
	while random < low:
		random = secrets.randbelow(high)

	return random


def sign(private_key, message, hash_obj=hashlib.sha256):
	#Generate Hash of message
	message_hash = bytes_to_int(hash_obj(message).digest())

	#Generate ints
	prime_mod, subgroup_order, generator = private_key.domain()
	secret_exp = private_key.x

	#init outputs
	random_output = 0
	signature = 0

	#check if r = 0
	while random_output == 0 or signature == 0:
		#Generate a Random Integer between 1, q-1
		random_int = gen_random(1, subgroup_order-1)
		
		#Compute (g^k mod P) mod q
		random_output = pow(generator, random_int, prime_mod) % subgroup_order

		#Compute Mod Inverse of R
		random_int_inverse = pow(random_int, -1, subgroup_order)

		#Check Inverse
		#print("Inverse Check: {}".format((random_int_inverse * random_int) % subgroup_order))

		#Compute Signature
		# s:= k^-1 (H(m) + x*r)) mod q
		signature = (random_int_inverse * (message_hash + (secret_exp * random_output))) % subgroup_order

	return random_output, signature


def verify(public_key, message, random_output, signature, hash_obj=hashlib.sha256):
	#Generate ints
	prime_mod, subgroup_order, generator = public_key.domain()
	public_int = public_key.y

	#Generate Hash of message
	message_hash = bytes_to_int(hash_obj(message).digest())


	if random_output > 0 and random_output < (prime_mod-1) and signature > 0 and signature < (prime_mod-1):
		#Generate Mod inverse of signature
		inverse_signature = pow(signature, -1, subgroup_order)

		#Check Mod inverse
		#print("Inverse Check: {}".format((inverse_signature * signature) % subgroup_order))

		#Generate First part of the comparison
		exp_from_sig = (inverse_signature * message_hash) % subgroup_order
		compare_from_signature = pow(generator, exp_from_sig, prime_mod)

		#Generate Second part of Comparison
		exp_from_rand = (random_output * inverse_signature) % subgroup_order
		compare_from_rand = pow(public_int, exp_from_rand, prime_mod)

		compare_random = ((compare_from_signature * compare_from_rand) % prime_mod ) % subgroup_order

		#print(compare_random)
		#print(random_output)
		return random_output == compare_random
	else:
		raise Exception("Invalid random_output or signature")


if __name__ == '__main__':
	# Create a new DSA key
	#public_key, private_key = generate_key_pair()
	key = DSA.generate(2048)
	#print("DSA Key: {}".format(private_key))

	# Hash the message
	message = b"Hello"

	#Sign the Message 
	rand_out, signature = sign(key, message, hashlib.sha256)
	print(f"rand_out: {hex(rand_out)}")
	print(f"sign:     {hex(signature)}")

	#Verify the Signature
	valid = verify(key, message, rand_out, signature, hash_obj=hashlib.sha256)
	print("Signatures are Valid: {}".format(valid))

Security

  • Can have multiple signatures that are valid for the same message
    • Both RAW and ASN.1 versions are valid
    • While the ASN.1 encoding is supposed to be DER encoded, many libraries accept any (semi-)valid BER encoding. This means that an attacker can often use the flexibility of BER to craft an essentially infinite number of valid signatures (for the same message) once they know a single one.