222 lines
6.2 KiB
Python
222 lines
6.2 KiB
Python
|
#
|
||
|
# 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'
|