|
| 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 ([email protected]) |
| 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' |
0 commit comments