Friday, 23 March 2012

M2Crypto for Python X509 certificates and RSA encryption

Don't you hate it when you cannot find a proper guide to use a certain programming language? I had this problem over the last week when I was trying to use encryption in python.

My task was to simulate 2 players playing a game of tic tac toe over a secure connection. By secure connection I mean, they had to get a certificate from a Certificate Authority, check its authenticity and extract a public key, with which they encoded data and sent to the other player. So If a certain nosy person decided to listen into the conversation he will not be able to decrypt the messages with a private key.

I coded a tic tac toe game using sockets and threads no problem. The main issue came in when I wanted to encode data and create a X509 certificate. A very commonly used library for certificate creation is called OpenSSL. Since I rather code on my windows machine, I was having a lot of trouble trying to install it. The alternative I looked at is called pyOpenSSL, it is python wrapper to call certain methods in OpenSSL using a few commands. This library was very limited and did not provide encryption and decryption. Which means I would have to import other libraries such as RSA. After looking helplessly I found a lovely thing called M2Crypto. The only problem with it is that It has very little documentation or sample code online on how to exactly execute certain things. I was mostly looking at Stack Overflow questions people made about problems. After a couple of days of struggling and messing around with code, I have done it.

I decided to create an easy readable manual on M2Crypto for people who decide to work on it. This will provide a set of commands which you might require to implement a secure connection.


M2Crypto download:

To import M2Crypto into code:
import M2Crypto

Importing certain libraries:
from M2Crypto import RSA, EVP

Generating a Random RSA key pair (public and private):
key= RSA.gen_key(512, 65537)
512 is the size of the key
65537 encryption standard use it as a default

Saving the generated public and private key:
key.save_key ('Alice-private.pem', None)
key.save_pub_key ('Alice-public.pem')
None is a specified password for testing purposes there is none
The file can be created in a specific directory ‘/local/temp/Alice-private.pem’

Converting the RSA key into a PKey() which is stored in a certificate:
pkey = EVP.PKey()

To create a X509 certificate using M2Crypto:
# time for certificate to stay valid
cur_time = M2Crypto.ASN1.ASN1_UTCTIME()
cur_time.set_time(int(time.time()) - 60*60*24)
expire_time = M2Crypto.ASN1.ASN1_UTCTIME()
# Expire certs in 1 hour.
expire_time.set_time(int(time.time()) + 60 * 60 * 24)
# creating a certificate
cert = M2Crypto.X509.X509()
cs_name = M2Crypto.X509.X509_Name()
cs_name.C = "US"
cs_name.CN = “8080”
cs_name.Email = ""
# self signing a certificate
cert.sign(pkey, md="sha256")

This will create a “cert” file in your directory which can be loaded later on. Or you can use the cert object to convert into a string to send it through a socket EG:
message = cert.as_pem()
print message

Printing the certificate as a pem will look something like this, which is stored in a cert file if you open it with notepad:

Once you receive the certificate as a string the way to convert it to a certificate object is:
m2cert = M2Crypto.X509.load_cert_string(message)

you can print the certificate object as a more readable string with this line:
print m2cert.as_text()
       Version: 1 (0x0)
       Serial Number: 0 (0x0)
       Signature Algorithm: sha256WithRSAEncryption
       Issuer: C=US, CN=8081/
           Not Before: Mar 21 22:16:06 2012 GMT
           Not After : Mar 23 22:16:06 2012 GMT
       Subject: C=US, CN=8081/
       Subject Public Key Info:
           Public Key Algorithm: rsaEncryption
           RSA Public Key: (512 bit)
               Modulus (512 bit):
               Exponent: 65537 (0x10001)
   Signature Algorithm: sha256WithRSAEncryption

To extract an RSA key which can be used for encoding you use this:
pkey= m2cert.get_pubkey()
rsa_key_pub = pkey.get_rsa()

to encrypt a message using the public key above:
crypto = rsa_key_pub.public_encrypt(message, M2Crypto.RSA.pkcs1_oaep_padding)
M2Crypto.RSA.pkcs1_oaep_padding this is a RSA encryption type, there are different ones, but you should stick to one if you do not want to bother having to extract the type from the RSA key.

to decrypt a message using the private key, which you saved earlier, you need to load it first:
rsa_key_pri= M2Crypto.RSA.load_key(‘Alice-public.pem’)

once loaded use the command to decrypt:
decyphered = rsa_key.private_decrypt(message, M2Crypto.RSA.pkcs1_oaep_padding)

If you have any of the following errors:

RSAError: data too large for modulus

Check to make sure the data you are encrypting does not exceed the formula:
(sizeofkey/8) - 11 bytes
If it is then you will have to cut the message into slices and encrypt it OR encrease the size of the RSAKey
512 - moderate secure
1024 - really secure
2048 - military security
The bigger size of the key, the longer it will take to generate it.

RSAError: no start line

You are using the wrong file name or format for example if you open the .pem file for one of your private or public keys the header will say something like this:
you need to make sure the file you are using is correct and you are not trying to encrypt using a private key or vice versa

RSAError: oaep decoding error

This means that during the method decrypt, you have used the wrong private key. The error means that the decryption that you get from decrypting the message is not the same as original one.
Make sure you are using correct keys it is easy to mix them up