root / trunk / pyopencoin / oc / crypto.py

Revision 114, 19.1 kB (checked in by ocmathew, 4 years ago)

Create a static CDD and CDD_private in tests.py. This is to allow exact matches of containers.

Line 
1"""A Crypto library.
2    >>> private, public = createRSAKeyPair(1024)
3
4    >>> public.hasPrivate()
5    False
6   
7    >>> hash = SHA256HashingAlgorithm()
8 
9    >>> text = 'Foobar'
10    >>> digest = hash.update(text).digest()
11
12    >>> blinder = RSABlindingAlgorithm(public)
13    >>> blinded, factor = blinder.blind(digest)
14
15    >>> signer = RSASigningAlgorithm(private)
16    >>> blinded_sig = signer.sign(blinded)
17   
18    >>> unblinded_sig = blinder.unblind(blinded_sig)
19
20    >>> sig_verifier = RSASigningAlgorithm(public)
21    >>> sig_verifier.verify(digest, unblinded_sig)
22    True
23
24    We can test encryption using the SHA256 digest as well
25    >>> encrypt = RSAEncryptionAlgorithm(public)
26    >>> decrypt = RSAEncryptionAlgorithm(private)
27    >>> digest == decrypt.decrypt(encrypt.encrypt(digest))
28    True
29    """
30import types
31from Crypto.Util import number
32import base64
33
34class CryptoContainer:
35    def __init__(self, signing=None, blinding=None, hashing=None):
36        self.signing = signing
37        self.blinding = blinding
38        self.hashing = hashing
39
40        import types
41
42        if self.signing and not isinstance(self.signing, types.ClassType):
43            raise CryptoError('Tried to add something to the container that was not a class!')
44        if self.hashing and not isinstance(self.hashing, types.ClassType):
45            raise CryptoError('Tried to add something to the container that was not a class!')
46        if self.blinding and not isinstance(self.blinding, types.ClassType):
47            raise CryptoError('Tried to add something to the container that was not a class!')
48
49    def __str__(self):
50        include = []
51        if self.signing:
52            include.append(self.signing.ALGNAME)
53        if self.blinding:
54            include.append(self.blinding.ALGNAME)
55        if self.hashing:
56            include.append(self.hashing.ALGNAME)
57
58        # the order is always SIGN-ALG, BLINDING-ALG, HASH-ALG
59
60        return '[' + ', '.join(include) + ']'
61
62    def __repr__(self):
63        return str(self)
64
65    def __eq__(self, other):
66        if not isinstance(other, CryptoContainer):
67            raise NotImplementedError
68        return self.signing.ALGNAME == other.signing.ALGNAME and \
69               self.hashing.ALGNAME == other.hashing.ALGNAME and \
70               self.blinding.ALGNAME == other.blinding.ALGNAME
71       
72
73def decodeCryptoContainer(container):
74    # raise NotImplementedError, str(container)
75    return CryptoContainer(signing=RSASigningAlgorithm,
76                           blinding=RSABlindingAlgorithm,
77                           hashing=SHA256HashingAlgorithm)
78
79def encodeCryptoContainer(container):
80    include = []
81    if container.signing:
82        include.append(container.signing.ALGNAME)
83    if container.blinding:
84        include.append(container.blinding.ALGNAME)
85    if container.hashing:
86        include.append(container.hashing.ALGNAME)
87
88    return include
89   
90class SigningAlgorithm:
91    def __init__(self, key):
92        self.key = key
93
94    def sign(self, message):
95        """returns the signature of the hash of the data."""
96        raise NotImplementedError
97
98    def verify(self, message):
99        """returns if the signature inputted is valid."""
100        raise NotImplementedError
101
102    def __str__(self):
103        """returns only the name of the signing algorithm in accordance with the SIGN-ALG name."""
104        return self.ALGNAME
105
106class BlindingAlgorithm:
107    def __init__(self, key):
108        self.key = key
109
110    def blind(self, message, blinding_factor):
111        """returns the blinded value of the message."""
112        raise NotImplementedError
113
114    def unblind(self, message, blinding_factor):
115        """returns the unblinded value of the message."""
116
117    def __str__(self):
118        """returns only the name of the blinding function in accordance with the HASH-ALG name."""
119        return self.ALGNAME
120
121
122class EncryptionAlgorithm:
123    def __init__(self, key):
124        self.key = key
125
126    def encrypt(self, message):
127        """returns the encryption of the plaintext."""
128        raise NotImplementedError
129
130    def decrypt(self, message):
131        """returns the decryption of the ciphertext."""
132        raise NotImplementedError
133   
134    def __str__(self):
135        """returns only the name of the encryption algorithm in accordance with the ENCRYPTION-ALG name."""
136        return self.ALGNAME
137
138   
139
140class HashingAlgorithm:
141    def __init__(self, input=None):
142        if input:
143            self.update(input)
144
145    def update(self, input):
146        """updates the hash with more hashing information."""
147        raise NotImplementedError
148
149    def digest(self):
150        """returns the digest of the hash. This does not automatically reset the hash."""
151        raise NotImplementedError
152
153    def reset(self):
154        """resets the hash to it's default value."""
155        raise NotImplementedError
156
157    def __str__(self):
158        """returns only the name of the hash function in accordance with the HASH-ALG name."""
159        return self.ALGNAME
160
161
162class KeyPair:
163    def __init__(self, public, private):
164        self.public = public
165        self.private = private
166
167    def hasPrivate(self):
168        """hasPrivate returns whether the KeyPair has the private key."""
169        return self.private
170   
171class RSAKeyPair(KeyPair):
172    r"""An instance of the KeyPair which does RSA. It builds from the pycrypto RSA.
173
174    First, create a public key and private key copy with known values.
175    (p=61, q=53, n=pq=3233, e=17, d=2753)
176
177    >>> simple_public_key = RSAKeyPair(n=3233L, e=17L)
178
179    >>> simple_private_key = RSAKeyPair(n=3233L, e=17L, d=2753L)
180
181    >>> simple_public_key.hasPrivate()
182    False
183   
184    >>> simple_public_key.public()
185    <Crypto.PublicKey.RSA.RSAobj... instance at 0x...>
186   
187    >>> simple_public_key.private()
188    Traceback (most recent call last):
189    CryptoError: Do not have private key
190
191    >>> simple_public_key.size()
192    11
193
194    Print out the base64 value of the public key (n and e)
195    >>> print simple_public_key
196    DKE=,EQ==
197
198    stringPrivate should return an empty string for a public key
199    >>> simple_public_key.stringPrivate()
200    ''
201
202    >>> simple_public_key.toJson()
203    '{"public":"DKE=,EQ==",\n"private":""}'
204
205
206    Now, compare the public key and private key together knowing the internals of the key
207    >>> simple_public_key.key.n == simple_private_key.key.n
208    True
209
210    >>> simple_public_key.key.e == simple_private_key.key.e
211    True
212
213    Test the newPublicKeyPair function against simple_public_key
214    >>> new_public_key = simple_public_key.newPublicKeyPair()
215    >>> new_public_key.key.n == simple_public_key.key.n
216    True
217    >>> new_public_key.key.e == simple_public_key.key.e
218    True
219
220    Check to make sure we don't have the private key
221    >>> new_public_key.hasPrivate()
222    False
223
224    Check the private key values. pycrypto's RSA only requires n, e, and d.
225    >>> simple_private_key.key.n
226    3233L
227    >>> simple_private_key.key.e
228    17L
229    >>> simple_private_key.key.d
230    2753L
231
232    >>> print simple_private_key
233    DKE=,EQ==
234   
235    >>> simple_private_key.stringPrivate()
236    'DKE=,EQ==,CsE='
237
238    >>> simple_private_key.toJson()
239    '{"public":"DKE=,EQ==",\n"private":"DKE=,EQ==,CsE="}'
240
241    Test the other generation methods for private keys
242    >>> key_2 = RSAKeyPair(n=3233L, e=17L, d=2753L, p=61L, q=53L)
243    >>> key_3 = RSAKeyPair(n=3233L, e=17L, d=2753L, p=61L, q=53L, u=20L)
244    >>> key_4 = RSAKeyPair(key=key_3.key)
245    >>> key_5 = RSAKeyPair(key=simple_private_key.key)
246   
247    >>> simple_private_key.key.n == key_2.key.n == key_3.key.n == key_4.key.n
248    True
249
250    >>> simple_private_key.key.d == key_2.key.d == key_3.key.d == key_4.key.d
251    True
252
253    >>> simple_private_key.key.e == key_2.key.e == key_3.key.e == key_4.key.e
254    True
255
256    Test p, q, and u. When pycrypto makes the key, if we supply a p and q, u is created
257    automatically.
258    >>> key_2.key.p == key_3.key.p == key_4.key.p
259    True
260    >>> key_2.key.q == key_3.key.q == key_4.key.q
261    True
262    >>> key_2.key.u == key_3.key.u == key_4.key.u
263    True
264   
265    """ 
266    def __init__(self, key=None, p=None, q=None, e=None, d=None, n=None, u=None, input=None):
267        self.key = key
268
269        if not self.key: 
270            li = []
271            if input:
272                li = input
273                if type(input[0]) == type(''):
274                    li = [number.bytes_to_long(i) for i in li]
275            else:
276                #this function builds just the parts of the tuple contstruct uses
277                li.extend([n, e])
278                if d:
279                    li.append(d)
280                if p and q:
281                    li.extend((p, q))
282                if u:
283                    li.append(u)
284               
285            from Crypto.PublicKey import RSA
286            self.key = RSA.construct(tuple(li))
287
288
289    def hasPrivate(self):
290        return self.key.has_private()
291
292    def public(self):
293        return self.key.publickey()
294
295    def private(self):
296        if not self.hasPrivate():
297            raise CryptoError('Do not have private key')
298
299        return self.key
300
301    def keytype(self):
302        def encode(key):
303            return str(key)
304        def decode(key):
305            import base64
306            l = key.split(',')
307            try:
308                n = base64.b64decode(l[0])
309            except TypeError:
310                raise TypeError(l[0])
311            e = base64.b64decode(l[1])
312            return RSAKeyPair(n=n, e=e)
313        class RSAKeyType:
314            pass
315        RSAKeyType.encode = encode
316        RSAKeyType.decode = decode
317
318
319    def size(self):
320        return self.key.size()
321
322    def __str__(self):
323        """The string representation of the key. Always the public key."""
324        values = [base64.b64encode(number.long_to_bytes(self.key.n)), base64.b64encode(number.long_to_bytes(self.key.e))]
325        return ','.join(values)
326
327    def stringPrivate(self):
328        if self.hasPrivate():
329            key = self.key
330            try:
331                return ','.join([base64.b64encode(number.long_to_bytes(i)) for i in [key.n,key.e,key.d,key.p,key.q]])
332            except AttributeError:
333                return ','.join([base64.b64encode(number.long_to_bytes(i)) for i in [key.n,key.e,key.d]])
334        else: 
335            return ''
336   
337    def toPython(self):
338        return str(self) 
339
340    def toJson(self):
341        import json
342        return json.write(dict(public=str(self),private=self.stringPrivate()))
343
344    def key_id(self, digestAlgorithm):
345        return digestAlgorithm().update(str(self)).digest()
346
347    def newPublicKeyPair(self):
348        return RSAKeyPair(self.key.publickey())
349
350    def __eq__(self,other):
351        return self.key == other.key
352
353def decodeRSAKeyPair(key):
354    import base64
355
356    l = key.split(',')
357    n = number.bytes_to_long(base64.b64decode(l[0]))
358    e = number.bytes_to_long(base64.b64decode(l[1]))
359    return RSAKeyPair(n=n, e=e)
360
361def createRSAKeyPair(N, public=True):
362    """Creates an RSA keypair of size N.
363    Returns a tuple of (private, public) keypairs if public is true.
364    """
365    from Crypto.PublicKey import RSA
366
367    _r.verifyEntropy(N)
368    rsaKey = RSA.generate(N, _r.get_bytes)
369    private = RSAKeyPair(key=rsaKey)
370    if public:
371        return (private, private.newPublicKeyPair())
372    else:
373        return private
374
375
376class RSAEncryptionAlgorithm(EncryptionAlgorithm):
377    r"""Performs RSA encryption
378    >>> private = RSAKeyPair(n=3233L, e=17L, d=2753L, p=61L, q=53L)
379    >>> public = private.newPublicKeyPair()
380
381    >>> public.hasPrivate()
382    False
383
384    >>> priv_enc = RSAEncryptionAlgorithm(private)
385    >>> pub_enc = RSAEncryptionAlgorithm(public)
386
387    >>> print priv_enc
388    RSAEncryptionAlgorithm
389    >>> print pub_enc
390    RSAEncryptionAlgorithm
391
392    >>> pub_enc.encrypt(14L)
393    2549L
394
395    >>> priv_enc.decrypt(2549L)
396    14L
397
398    >>> pub_enc.decrypt(2549L)
399    Traceback (most recent call last):
400    ...
401    CryptoError: Do not have private key
402
403    >>> pub_enc.encrypt('0')
404    '\x02p'
405
406    >>> priv_enc.decrypt('\x02p')
407    '0'
408    """
409    from Crypto.PublicKey import RSA
410
411    ALGNAME = 'RSAEncryptionAlgorithm'
412
413    def __init__(self, key):
414        if not isinstance(key, RSAKeyPair):
415            raise CryptoError('key not RSAKeyPair type!. key.__class__: %s' % key.__class__)
416       
417        EncryptionAlgorithm.__init__(self, key)
418       
419    def encrypt(self, message):
420        try:
421            return self.key.public().encrypt(message, '')[0]
422        except PyCryptoRSAError, reason:
423            raise CryptoError(reason)
424   
425    def decrypt(self, message):
426        try:
427            return self.key.private().decrypt(message)
428        except PyCryptoRSAError, reason:
429            raise CryptoError(reason)
430       
431
432class RSABlindingAlgorithm(BlindingAlgorithm):
433    r"""Performs RSA blinding
434    >>> private = RSAKeyPair(n=3233L, e=17L)
435
436    >>> blind = RSABlindingAlgorithm(private)
437
438    >>> print blind
439    RSABlindingAlgorithm
440
441    Test a blinding. This does not work for some reason. The answer is almost certainly wrong as well.
442    >>> blinded, factor = blind.blind(154L, 1001L)
443
444    >>> blinded
445    '\tC'
446
447    >>> factor == 1001L
448    True
449
450    >>> blind.unblind(blinded, factor)
451    '\x0bw'
452
453    >>> blinded, factor = blind.blind('\x9a', '\x03\xe9')
454
455    >>> blinded
456    '\tC'
457   
458    >>> blind.unblind(blinded)
459    '\x0bw'
460    """
461
462    ALGNAME = 'RSABlindingAlgorithm'
463
464    def __init__(self, key, blinding_factor=None):
465        BlindingAlgorithm.__init__(self, key)
466        self.blinding_factor = blinding_factor
467
468    def blind(self, message, blinding_factor=None):
469        """returns the blinding of the input with the key and the blinding factor."""
470        if blinding_factor:
471            self.blinding_factor = blinding_factor
472           
473        elif not self.blinding_factor:
474            # self.key.size returns the size of the key - 1, which is acceptable
475            self.blinding_factor = _r.getRandomNumber(self.key.size())
476
477        else:
478            pass #self.blinding_factor is already set
479
480        if isinstance(message, types.LongType):
481            message = number.long_to_bytes(message)
482
483        try:
484            return self.key.public().blind(message, self.blinding_factor), self.blinding_factor
485        except PyCryptoRSAError, reason:
486            raise CryptoError(reason)
487
488    def unblind(self, message, blinding_factor=None):
489        """returns the unblinded value of the input."""
490        if blinding_factor:
491            self.blinding_factor = blinding_factor
492
493        # change the input to a bytestream if a long number
494        if isinstance(message, types.LongType):
495            message = number.long_to_bytes(message)
496
497        try:
498            return self.key.public().unblind(message, self.blinding_factor)
499        except PyCryptoRSAError, reason:
500            raise CryptoError(reason)
501       
502
503   
504class RSASigningAlgorithm(SigningAlgorithm):
505    """An implementation of the RSA signing algorithm.
506    >>> private = createRSAKeyPair(1024)
507    >>> public = private.newPublicKeyPair()
508
509    >>> public.hasPrivate()
510    False
511
512    >>> message = _r.getRandomString(1022)
513   
514    >>> signer = RSASigningAlgorithm(private)
515    >>> verifier = RSASigningAlgorithm(public)
516
517    >>> print signer
518    RSASigningAlgorithm
519    >>> print verifier
520    RSASigningAlgorithm
521
522    >>> signature = signer.sign(message)
523
524    >>> verifier.verify(message, signature)
525    True
526
527    If the signature is missing the first byte, it cannot pass
528    >>> signature = signature[1:]
529
530    >>> verifier.verify(message, signature)
531    False
532
533    >>> message = _r.getRandomString(1025)
534
535    >>> signer.sign(message)
536    Traceback (most recent call last):
537      ...
538    CryptoError: Ciphertext too large
539
540    >>> verifier.verify(message, signature)
541    False
542    """
543
544    ALGNAME = 'RSASigningAlgorithm'
545
546    def __init__(self, key):
547        SigningAlgorithm.__init__(self, key)
548
549    def sign(self, message):
550        """returns the signature of the message with the key."""
551        try:
552            key = self.key.private()
553            result = key.sign(message, '')[0]
554
555            # if the message was a bytestream, return a bytestream
556            if isinstance(message, types.StringType):
557                return number.long_to_bytes(result)
558
559            return result
560        except PyCryptoRSAError, reason:
561            raise CryptoError(reason)
562
563    def verify(self, message, signature):
564        """returns if the signature of the input is valid."""
565        try:
566            return self.key.public().verify(message, (number.bytes_to_long(signature),))
567        except PyCryptoRSAError, reason:
568            raise CryptoError(reason)
569
570class SHA256HashingAlgorithm(HashingAlgorithm):
571
572    ALGNAME='SHA256HashingAlgorithm'
573
574    def __init__(self, input=None):
575        self.reset() # setup self.hash. We reset empty since HashingAlgorithm.__init__ will update!
576
577        HashingAlgorithm.__init__(self, input)
578
579    def update(self, input):
580        self.hash.update(input) 
581        return self
582
583    def digest(self):
584        return self.hash.digest()
585
586    def reset(self, input=''):
587        from Crypto.Hash import SHA256
588        self.hash = SHA256.new(input)
589        return self
590       
591class CryptoError(Exception): pass
592
593class Random:
594    def __init__(self):
595        from Crypto.Util.randpool import RandomPool
596        self.RandomPool = RandomPool()
597
598    def getRandomString(self, N):
599        """Returns a N-bit length random string."""
600        r = self.getRandomNumber(N)
601
602
603        return number.long_to_bytes(r)
604   
605    def getRandomNumber(self, N):
606        """Returns an N-bit length random number."""
607        if self.RandomPool.entropy < 2 * N:
608            self.RandomPool.randomize(4 * N)
609           
610        self.RandomPool.add_event('')
611        self.RandomPool.stir()
612
613        random = number.getRandomNumber(N, self.RandomPool.get_bytes)
614
615        self.RandomPool.stir()
616
617        return random
618
619    def getPrime(self, N):
620        """Returns a N-bit length prime."""
621        if self.RandomPool.entropy < 2 * N:
622            self.RandomPool.randomize(4 * N)
623
624        self.RandomPool.add_event('')
625        self.RandomPool.stir()
626
627        prime = number.getPrime(N, self.RandomPool.get_bytes)
628
629        self.RandomPool.stir()
630       
631        return prime
632
633    def addEvent(self, text):
634        """Adds a bit of random text to the pool as additional entropy. Use caution.
635        The curreny implementation of this function just XORs the text over the entropy, probably
636        giving it bias if we just roll through our messages. I'm not sure.
637        """
638        self.RandomPool.add_event(text)
639        self.RandomPool.stir()
640
641    def verifyEntropy(self, N):
642        """Verifies enough entropy is in the RandomPool. If we are close to no entropy, attempt to add some."""
643        if self.RandomPool.entropy < 2 * N:
644            self.RandomPool.randomize(4 * N)
645
646        self.RandomPool.add_event('')
647        self.RandomPool.stir()
648
649        if self.RandomPool.entropy < N: # if the stirring got rid of entropy, seed with more entropy
650            self.verifyEntropy(2 * N)
651
652    def get_bytes(self, num):
653        """Get num bytes of randomness from the RandomPool."""
654        return self.RandomPool.get_bytes(num)
655           
656_r = Random()   
657
658# We need a copy of the error from Crypto.PublicKey.RSA, so import, make a copy, and delete the module
659import Crypto.PublicKey.RSA as quickRSA
660PyCryptoRSAError = quickRSA.error
661del quickRSA
662
663if __name__=='__main__':
664    import doctest
665    doctest.testmod(optionflags=doctest.ELLIPSIS)
Note: See TracBrowser for help on using the browser.