blob: 4bd26bc3ab37dc419a50b9989ab87dd561a188a6 [file] [log] [blame]
#
# DSA.py : Stupid name. Should really be called qNEW.py or something.
# Suggestions for a better name would be welcome.
#
# Maintained by A.M. Kuchling (amk@magnet.com)
# Date: 1997/09/03
#
# Distribute and use freely; there are no restrictions on further
# dissemination and usage except those imposed by the laws of your
# country of residence.
#
# TODO :
# Change the name
# Add more comments and docstrings
# Write documentation
# Add better RNG (?)
import types, md5
error = 'DSA module error'
def RandomNumber(N, randfunc):
"Get an N-bit random number"
str=randfunc(N/8)
char=ord(randfunc(1))>>(8-(N%8))
return Str2Int(chr(char)+str)
def Int2Str(n):
"Convert an integer to a string form"
s=''
while n>0:
s=chr(n & 255)+s
n=n>>8
return s
def Str2Int(s):
"Convert a string to a long integer"
if type(s)!=types.StringType: return s # Integers will be left alone
return reduce(lambda x,y : x*256+ord(y), s, 0L)
def getPrime(N, randfunc):
"Find a prime number measuring N bits"
number=RandomNumber(N, randfunc) | 1
while (not isPrime(number)):
number=number+2
return number
sieve=[2,3,5,7,11,13,17,19,23,29,31,37,41]
def isPrime(N):
"""Test if a number N is prime, using a simple sieve check,
followed by a more elaborate Rabin-Miller test."""
for i in sieve:
if (N % i)==0: return 0
N1=N - 1L ; n=1L
while (n<N): n=n<<1L # Compute number of bits in N
for j in sieve:
a=long(j) ; d=1L ; t=n
while (t): # Iterate over the bits in N1
x=(d*d) % N
if x==1L and d!=1L and d!=N1: return 0 # Square root of 1 found
if N1 & t: d=(x*a) % N
else: d=x
t=t>>1L
if d!=1L: return 0
return 1
class DSAobj:
def size(self):
"Return the max. number of bits that can be handled by this key"
bits, power = 0,1L
while (power<self.p): bits, power = bits+1, power<<1
return bits-1
def hasprivate(self):
"""Return a Boolean denoting whether the object contains private components"""
if hasattr(self, 'x'): return 1
else: return 0
def cansign(self):
return self.hasprivate()
def canencrypt(self):
return 0
def publickey(self):
new=DSAobj()
for i in 'pqgy': setattr(new, i, getattr(self, i))
return new
def _sign(self, M, K):
if (self.q<=K):
raise error, 'K is greater than q'
r=pow(self.g, K, self.p) % self.q
s=(K- (r*M*self.x % self.q)) % self.q
return (r,s)
def _verify(self, M, sig):
r, s = sig
if r<=0 or r>=self.q or s<=0 or s>=self.q: return 0
v1=pow(self.g, s, self.p)
v2=pow(self.y, M*r, self.p)
v=((v1*v2) % self.p)
v=v % self.q
if v==r: return 1
return 0
def sign(self, M, K):
if (not self.hasprivate()):
raise error, 'Private key not available in this object'
if type(M)==types.StringType: M=Str2Int(M)
if type(K)==types.StringType: K=Str2Int(K)
return self._sign(M, K)
def verify(self, M, signature):
if type(M)==types.StringType: M=Str2Int(M)
return self._verify(M, signature)
validate=verify
def generate(self, L, randfunc, progress_func=None):
"""Generate a private key with L bits"""
HASHBITS=128 # Number of bits in the hashing algorithm used
# (128 for MD5; change to 160 for SHA)
if L<512: raise error, 'Key length <512 bits'
# Generate string S and prime q
if progress_func: apply(progress_func, ('p,q\n',))
while (1):
self.q = getPrime(160, randfunc)
S = Int2Str(self.q)
n=(L-1)/HASHBITS
C, N, V = 0, 2, {}
# b=(self.q >> 5) & 15
b= (L-1) % HASHBITS
powb=pow(long(2), b)
powL1=pow(long(2), L-1)
while C<4096:
for k in range(0, n+1):
V[k]=Str2Int(md5.new(S+str(N)+str(k)).digest())
W=V[n] % powb
for k in range(n-1, -1, -1):
W=(W<< long(HASHBITS) )+V[k]
X=W+powL1
p=X-(X%(2*self.q)-1)
if powL1<=p and isPrime(p): break
C, N = C+1, N+n+1
if C<4096: break
if progress_func: apply(progress_func, ('4096 multiples failed\n',) )
self.p = p
power=(p-1)/self.q
if progress_func: apply(progress_func, ('h,g\n',))
while (1):
h=Str2Int(randfunc(L)) % (p-1)
g=pow(h, power, p)
if 1<h<p-1 and g>1: break
self.g=g
if progress_func: apply(progress_func, ('x,y\n',))
while (1):
x=Str2Int(randfunc(20))
if 0<x<self.q: break
self.x, self.y=x, pow(g, x, p)
return self
object=DSAobj
# XXX this random number generation function sucks, since it isn't
# cryptographically strong! But it'll do for this first release...
def randfunc(N):
import os, string
if string.lower(os.uname()[0])=='linux':
# On Linux, use /dev/urandom
f=open('/dev/urandom', 'r')
return f.read(N)
else:
import time
s=""
while len(s)<N:
rand=md5.new(str(time.time())).digest()
s=s+rand
return s[0:N]
if __name__=='__main__':
import sys, string
BITS=512
if len(sys.argv)>1:
BITS=string.atoi(sys.argv[1])
print ' Generating', BITS, 'bit key'
key=DSAobj()
key.generate(BITS, randfunc, sys.stdout.write)
print ' Key data: (the private key is x)'
for i in 'xygqp': print '\t', i, ':', hex(getattr(key, i))
plaintext="Hello"
if key.cansign():
print ' Signature test'
print "Plaintext:", plaintext
K=getPrime(30, randfunc)
signature=key.sign(plaintext, K)
print "Signature:", signature
result=key.verify(plaintext, signature)
if not result:
print " Sig. verification failed when it should have succeeded"
else: print 'Signature verified'
# Test on a mangled plaintext
result=key.verify(plaintext[:-1], signature)
if result:
print " Sig. verification succeeded when it should have failed"
# Change a single bit in the plaintext
badtext=plaintext[:-3]+chr( 1 ^ ord(plaintext[-3]) )+plaintext[-3:]
result=key.verify(badtext, signature)
if result:
print " Sig. verification succeeded when it should have failed"
print 'Removing private key data'
pubonly=key.publickey()
result=pubonly.verify(plaintext, signature)
if not result:
print " Sig. verification failed when it should have succeeded"
else:
print 'Signature verified'