blob: 4bd26bc3ab37dc419a50b9989ab87dd561a188a6 [file] [log] [blame]
Guido van Rossuma11cccc1997-10-06 20:19:59 +00001#
2# DSA.py : Stupid name. Should really be called qNEW.py or something.
3# Suggestions for a better name would be welcome.
4#
5# Maintained by A.M. Kuchling (amk@magnet.com)
6# Date: 1997/09/03
7#
8# Distribute and use freely; there are no restrictions on further
9# dissemination and usage except those imposed by the laws of your
10# country of residence.
11#
12
13# TODO :
14# Change the name
15# Add more comments and docstrings
16# Write documentation
17# Add better RNG (?)
18
19import types, md5
20
21error = 'DSA module error'
22
23def RandomNumber(N, randfunc):
24 "Get an N-bit random number"
25 str=randfunc(N/8)
26 char=ord(randfunc(1))>>(8-(N%8))
27 return Str2Int(chr(char)+str)
28
29def Int2Str(n):
30 "Convert an integer to a string form"
31 s=''
32 while n>0:
33 s=chr(n & 255)+s
34 n=n>>8
35 return s
36
37def Str2Int(s):
38 "Convert a string to a long integer"
39 if type(s)!=types.StringType: return s # Integers will be left alone
40 return reduce(lambda x,y : x*256+ord(y), s, 0L)
41
42
43def getPrime(N, randfunc):
44 "Find a prime number measuring N bits"
45 number=RandomNumber(N, randfunc) | 1
46 while (not isPrime(number)):
47 number=number+2
48 return number
49
50sieve=[2,3,5,7,11,13,17,19,23,29,31,37,41]
51def isPrime(N):
52 """Test if a number N is prime, using a simple sieve check,
53 followed by a more elaborate Rabin-Miller test."""
54 for i in sieve:
55 if (N % i)==0: return 0
56 N1=N - 1L ; n=1L
57 while (n<N): n=n<<1L # Compute number of bits in N
58 for j in sieve:
59 a=long(j) ; d=1L ; t=n
60 while (t): # Iterate over the bits in N1
61 x=(d*d) % N
62 if x==1L and d!=1L and d!=N1: return 0 # Square root of 1 found
63 if N1 & t: d=(x*a) % N
64 else: d=x
65 t=t>>1L
66 if d!=1L: return 0
67 return 1
68
69class DSAobj:
70 def size(self):
71 "Return the max. number of bits that can be handled by this key"
72 bits, power = 0,1L
73 while (power<self.p): bits, power = bits+1, power<<1
74 return bits-1
75
76 def hasprivate(self):
77 """Return a Boolean denoting whether the object contains private components"""
78 if hasattr(self, 'x'): return 1
79 else: return 0
80
81 def cansign(self):
82 return self.hasprivate()
83 def canencrypt(self):
84 return 0
85
86 def publickey(self):
87 new=DSAobj()
88 for i in 'pqgy': setattr(new, i, getattr(self, i))
89 return new
90
91 def _sign(self, M, K):
92 if (self.q<=K):
93 raise error, 'K is greater than q'
94 r=pow(self.g, K, self.p) % self.q
95 s=(K- (r*M*self.x % self.q)) % self.q
96 return (r,s)
97 def _verify(self, M, sig):
98 r, s = sig
99 if r<=0 or r>=self.q or s<=0 or s>=self.q: return 0
100 v1=pow(self.g, s, self.p)
101 v2=pow(self.y, M*r, self.p)
102 v=((v1*v2) % self.p)
103 v=v % self.q
104 if v==r: return 1
105 return 0
106
107 def sign(self, M, K):
108 if (not self.hasprivate()):
109 raise error, 'Private key not available in this object'
110 if type(M)==types.StringType: M=Str2Int(M)
111 if type(K)==types.StringType: K=Str2Int(K)
112 return self._sign(M, K)
113 def verify(self, M, signature):
114 if type(M)==types.StringType: M=Str2Int(M)
115 return self._verify(M, signature)
116 validate=verify
117
118 def generate(self, L, randfunc, progress_func=None):
119 """Generate a private key with L bits"""
120 HASHBITS=128 # Number of bits in the hashing algorithm used
121 # (128 for MD5; change to 160 for SHA)
122
123 if L<512: raise error, 'Key length <512 bits'
124 # Generate string S and prime q
125 if progress_func: apply(progress_func, ('p,q\n',))
126 while (1):
127 self.q = getPrime(160, randfunc)
128 S = Int2Str(self.q)
129 n=(L-1)/HASHBITS
130 C, N, V = 0, 2, {}
131# b=(self.q >> 5) & 15
132 b= (L-1) % HASHBITS
133 powb=pow(long(2), b)
134 powL1=pow(long(2), L-1)
135 while C<4096:
136 for k in range(0, n+1):
137 V[k]=Str2Int(md5.new(S+str(N)+str(k)).digest())
138 W=V[n] % powb
139 for k in range(n-1, -1, -1):
140 W=(W<< long(HASHBITS) )+V[k]
141 X=W+powL1
142 p=X-(X%(2*self.q)-1)
143 if powL1<=p and isPrime(p): break
144 C, N = C+1, N+n+1
145 if C<4096: break
146 if progress_func: apply(progress_func, ('4096 multiples failed\n',) )
147 self.p = p
148 power=(p-1)/self.q
149 if progress_func: apply(progress_func, ('h,g\n',))
150 while (1):
151 h=Str2Int(randfunc(L)) % (p-1)
152 g=pow(h, power, p)
153 if 1<h<p-1 and g>1: break
154 self.g=g
155 if progress_func: apply(progress_func, ('x,y\n',))
156 while (1):
157 x=Str2Int(randfunc(20))
158 if 0<x<self.q: break
159 self.x, self.y=x, pow(g, x, p)
160 return self
161
162object=DSAobj
163
164# XXX this random number generation function sucks, since it isn't
165# cryptographically strong! But it'll do for this first release...
166
167def randfunc(N):
168 import os, string
169 if string.lower(os.uname()[0])=='linux':
170 # On Linux, use /dev/urandom
171 f=open('/dev/urandom', 'r')
172 return f.read(N)
173 else:
174 import time
175 s=""
176 while len(s)<N:
177 rand=md5.new(str(time.time())).digest()
178 s=s+rand
179 return s[0:N]
180
181if __name__=='__main__':
182 import sys, string
183 BITS=512
184 if len(sys.argv)>1:
185 BITS=string.atoi(sys.argv[1])
186 print ' Generating', BITS, 'bit key'
187 key=DSAobj()
188 key.generate(BITS, randfunc, sys.stdout.write)
189 print ' Key data: (the private key is x)'
190 for i in 'xygqp': print '\t', i, ':', hex(getattr(key, i))
191 plaintext="Hello"
192
193 if key.cansign():
194 print ' Signature test'
195 print "Plaintext:", plaintext
196 K=getPrime(30, randfunc)
197 signature=key.sign(plaintext, K)
198 print "Signature:", signature
199 result=key.verify(plaintext, signature)
200 if not result:
201 print " Sig. verification failed when it should have succeeded"
202 else: print 'Signature verified'
203
204 # Test on a mangled plaintext
205 result=key.verify(plaintext[:-1], signature)
206 if result:
207 print " Sig. verification succeeded when it should have failed"
208
209 # Change a single bit in the plaintext
210 badtext=plaintext[:-3]+chr( 1 ^ ord(plaintext[-3]) )+plaintext[-3:]
211 result=key.verify(badtext, signature)
212 if result:
213 print " Sig. verification succeeded when it should have failed"
214
215 print 'Removing private key data'
216 pubonly=key.publickey()
217 result=pubonly.verify(plaintext, signature)
218 if not result:
219 print " Sig. verification failed when it should have succeeded"
220 else:
221 print 'Signature verified'