Guido van Rossum | a11cccc | 1997-10-06 20:19:59 +0000 | [diff] [blame^] | 1 | # |
| 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 | |
| 19 | import types, md5 |
| 20 | |
| 21 | error = 'DSA module error' |
| 22 | |
| 23 | def 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 | |
| 29 | def 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 | |
| 37 | def 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 | |
| 43 | def 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 | |
| 50 | sieve=[2,3,5,7,11,13,17,19,23,29,31,37,41] |
| 51 | def 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 | |
| 69 | class 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 | |
| 162 | object=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 | |
| 167 | def 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 | |
| 181 | if __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' |