Search code examples
pythonpython-3.xencryption-asymmetric

Private/Public Encryption in Python with Standard Library


Is there a module that has my searching has been unable to discover that would allow writing code like the following? The reason for wanting to write code like this is unimportant. All I am after is some code that has a simple API to generate public and private byte keys and to easily encode and decode data with those keys.

import module, os

method, bits, data = 'RSA', 1024, os.urandom(1024)
public, private = module.generate_keys(method, bits)

assert isinstance(public, bytes) and isinstance(private, bytes)
assert module.decode(module.encode(data, private), public) == data
assert module.decode(module.encode(data, public), private) == data

Most of what appears to be available requires downloading a package and only runs on Python 2.x. It is also quite common to find libraries that work with PEM files or other types of certificates. I would like to avoid having to deal with such files, to generate public and private keys on the fly, and quickly work with data in memory.


Solution

  • Public key encryption is not in the standard library. There are some third party libraries on PyPi for it though:

    If you're interested in the math behind it, Python makes it easy to experiment:

    code = pow(msg, 65537, 5551201688147)               # encode using a public key
    plaintext = pow(code, 109182490673, 5551201688147)  # decode using a private key
    

    The key generation is a little more involved. Here is a simplified example of how to do key generation in-memory using urandom as the source of entropy. The code runs under both Py2.6 and Py3.x:

    import random
    
    def gen_prime(N=10**8, bases=range(2,20000)):
        # XXX replace with a more sophisticated algorithm
        p = 1
        while any(pow(base, p-1, p) != 1 for base in bases):
            p = random.SystemRandom().randrange(N)
        return p
    
    def multinv(modulus, value):
        '''Multiplicative inverse in a given modulus
    
            >>> multinv(191, 138)
            18
            >>> 18 * 138 % 191
            1
    
        '''
        # http://en.wikipedia.org/wiki/Extended_Euclidean_algorithm
        x, lastx = 0, 1
        a, b = modulus, value
        while b:
            a, q, b = b, a // b, a % b
            x, lastx = lastx - q * x, x
        result = (1 - lastx * modulus) // value
        return result + modulus if result < 0 else result
    
    def keygen(N):
        '''Generate public and private keys from primes up to N.
    
            >>> pubkey, privkey = keygen(2**64)
            >>> msg = 123456789012345
            >>> coded = pow(msg, 65537, pubkey)
            >>> plain = pow(coded, privkey, pubkey)
            >>> assert msg == plain
    
        '''
        # http://en.wikipedia.org/wiki/RSA
        prime1 = gen_prime(N)
        prime2 = gen_prime(N)
        totient = (prime1 - 1) * (prime2 - 1)
        return prime1 * prime2, multinv(totient, 65537)