Skip to content

April 6, 2011

10

How to use m2crypto (Tutorial)

by e1ven

I’ve been looking over various Python engines over the last few weeks, and M2Crypto seems to be the best combination of portability and power, but the docs on how to use it were somewhat (read: Very) lacking.

What I work best with is a simple set of examples that explore the functions, and get me started with the parts I need. H

I asked an experienced M2crypto developer to some examples together with me, and wanted to post them here, in case any one else should need it.

#Since I ask this of people before using their code samples, anyone can use this under BSD.
import os
import M2Crypto

def empty_callback ():
 return
 
#------------------------------------------------------------------------------------------------#

#Seed the random number generator with 1024 random bytes (8192 bits)
M2Crypto.Rand.rand_seed (os.urandom (1024))

#Generate public/private key pair for Alice
print "Generating a 1024 bit private/public key pair for Alice..."
#If you don't like the default M2Crypto ASCII "progress" bar it makes when generating keys, you can use:
# Alice = M2Crypto.RSA.gen_key (1024, 65537, empty_callback)
#You can change the key size, though key lengths < 1024 are considered insecure
#The larger the key size the longer it will take to generate the key and the larger the signature will be when signing
#You should probably leave the public exponent at 65537 (http://en.wikipedia.org/wiki/Rsa#Key_generation_2)
Alice = M2Crypto.RSA.gen_key (1024, 65537)

#Save Alice's private key
#The 'None' tells it to save the private key in an unencrypted format
#For best security practices, you'd use:
# Alice.save_key ('Alice-private.pem')
#That would cause the private key to be saved in an encrypted format
#Python would ask you to enter a password to use to encrypt the key file
#For a demo script though it's easier/quicker to just use 'None' :)
Alice.save_key ('Alice-private.pem', None)

#Save Alice's public key
Alice.save_pub_key ('Alice-public.pem')


#Generate public/private key pair for Bob
print "Generating a 1024 bit private/public key pair for Bob..."
Bob = M2Crypto.RSA.gen_key (1024, 65537)
Bob.save_key ('Bob-private.pem', None)
Bob.save_pub_key ('Bob-public.pem')

#------------------------------------------------------------------------------------------------#

#Alice wants to send a message to Bob, which only Bob will be able to decrypt
#Step 1, load Bob's public key
WriteRSA = M2Crypto.RSA.load_pub_key ('Bob-public.pem')
#Step 2, encrypt the message using that public key
#Only Bob's private key can decrypt a message encrypted using Bob's public key
CipherText = WriteRSA.public_encrypt ("This is a secret message that can only be decrypted with Bob's private key", M2Crypto.RSA.pkcs1_oaep_padding)
#Step 3, print the result
print "\nAlice's encrypted message to Bob:"
print CipherText.encode ('base64')
#Step 4 (optional), sign the message so Bob knows it really was from Alice
# 1) Generate a signature
MsgDigest = M2Crypto.EVP.MessageDigest ('sha1')
MsgDigest.update (CipherText)

Signature = Alice.sign_rsassa_pss (MsgDigest.digest ())
# 2) Print the result
print "Alice's signature for this message:"
print Signature.encode ('base64')


#Bob wants to read the message he was sent
#Step 1, load Bob's private key
ReadRSA = M2Crypto.RSA.load_key ('Bob-private.pem')
#Step 2, decrypt the message using that private key
#If you use the wrong private key to try to decrypt the message it generates an exception, so this catches the exception
try:
  PlainText = ReadRSA.private_decrypt (CipherText, M2Crypto.RSA.pkcs1_oaep_padding)
except:
  print "Error: wrong key?"
  PlainText = ""

if PlainText  "":
  #Step 3, print the result of the decryption
  print "Message decrypted by Bob:"
  print PlainText
  #Step 4 (optional), verify the message was really sent by Alice
  # 1) Load Alice's public key
  VerifyRSA = M2Crypto.RSA.load_pub_key ('Alice-public.pem')
  #2 ) Verify the signature
  print "Signature verificaton:"

  MsgDigest = M2Crypto.EVP.MessageDigest ('sha1')
  MsgDigest.update (CipherText)

  if VerifyRSA.verify_rsassa_pss (MsgDigest.digest (), Signature) == 1:
   print "This message was sent by Alice.\n"
  else:
   print "This message was NOT sent by Alice!\n"

#------------------------------------------------------------------------------------------------#
 
#Generate a signature for a string
#Use Bob's private key
SignEVP = M2Crypto.EVP.load_key ('Bob-private.pem')
#Begin signing
SignEVP.sign_init ()
#Tell it to sign our string
SignEVP.sign_update ('This is an unencrypted string that will be signed by Bob')
#Get the final result
StringSignature = SignEVP.sign_final ()
#Print the final result
print "Bob's signature for the string:"
print StringSignature.encode ('base64')


#Verify the string was signed by Bob
PubKey = M2Crypto.RSA.load_pub_key ('Bob-public.pem')
#Initialize
VerifyEVP = M2Crypto.EVP.PKey()
#Assign the public key to our VerifyEVP
VerifyEVP.assign_rsa (PubKey)
#Begin verification
VerifyEVP.verify_init ()
#Tell it to verify our string, if this string is not identicial to the one that was signed, it will fail
VerifyEVP.verify_update ('This is an unencrypted string that will be signed by Bob')
#Was the string signed by Bob?
if VerifyEVP.verify_final (StringSignature) == 1:
 print "The string was successfully verified."
else:
 print "The string was NOT verified!"
Advertisements
Read more from Uncategorized
10 Comments Post a comment
  1. Kyle
    Apr 12 2011

    This looks excellent, I’ll be trying it out this evening. I also learn fastest from being given some straight forward examples to build off of. Thanks for posting this.

    Reply
  2. Manuel Barkhau
    May 6 2011

    Is it also possible to read/write the keys from strings or buffers, rather that specifying a filename?

    Reply
    • e1ven
      May 6 2011

      I think so, but you should use a combinedkey if you’re signing.

      priv = “adfsdfsdfsdfsd”
      pub = “dfdffffffffffffffff”
      combinedkey = privkey + ‘\n’ + pubkey

      // You don’t need these for this code, but they’re faster in other instances
      privkey_bio = M2Crypto.BIO.MemoryBuffer(priv.encode(‘utf8’))
      pubkey_bio = M2Crypto.BIO.MemoryBuffer(pub.encode(‘utf8’))
      combinedkey_bio = M2Crypto.BIO.MemoryBuffer(combinedkey)

      SignEVP = M2Crypto.EVP.load_key_string(combinedkey)
      SignEVP.sign_init()
      SignEVP.sign_update(signstring)
      StringSignature = SignEVP.sign_final().encode(‘base64’)

      Reply
      • Patrick
        Dec 15 2011

        One thing to remember about BIO.MemoryBuffer is that default reading from it is destructive to the MemoryBuffer data, so if you plan on keeping it around after running a load_key you need to save it back.

        You can in theory set the BIO.MemoryBuffer to be read only- which will solve this- but it isn’t well documented.

  3. Patrick
    Jun 30 2011

    This is super useful. Thanks!

    Reply
  4. Vinod
    Nov 5 2012

    Thank You for the wonderful tutorial got to know everything about M2Crypto in 15 minutes

    Reply
  5. mike bayer
    Jan 4 2013

    I was able to identify any number of ways to generate RSA signatures entirely incorrectly, until I read this and realized I wasn’t even in the correct ballpark – thanks very much for this !

    Reply
  6. zzzeek
    Jan 4 2013

    I was able to create RSA signatures in at least five different ways, all incorrect. This post showed that I wasn’t even in the right ballpark with M2Crypto’s totally undocumented patterns but now it works, thanks very much !

    Reply
  7. yoanar
    Dec 31 2013

    Really good information here, but i have to vent a little bit…
    *ahem*

    Why are you
    * switching between using ‘ and ” for encapsulating your strings?
    * using ONE space for indenting and TWO spaces in other places?
    * using upper-case in your “regular” variables? (i.e not classes or modules)
    * putting a space between a function and it’s encapsulated parameters? (funcSPAAAAACE())
    * while not bothering to put a space in between the hash tag and the beginning of the comment
    * creating a SEPARATE FUNCTION poisoning the namespace just for returning None instead \
    of using a simple “lambda: None” expression?
    * neglecting to put “!=” between PlainText and ‘””‘, breaking the code completely (do you even \
    test?)
    * not realizing that “” == False in python
    * not realizing that 1 and 0 are equal to True and False respectively in logical expressions? \
    (” == 1″ is superfluous)
    * using a separate if statement instead of putting the code directly after the “try:” \
    following the private_decrypt call?
    * Using an except statement without specifying what exceptions to make exceptions for? (ok, \
    this one is forgivable in short test code)
    * Writing the code for BSD but not putting a shebang line at the beginning?
    * and using a NON-BLOCKING read from URANDOM as a random seed for CRYPTOGRAPHY?

    Sorry if i seem a little aggressive, I just needed to get that out. But good stuff other than the above, thanks for the example.

    Reply
    • Dec 31 2013

      Thanks for the code review!
      I can’t speak to the style issues such as number of spaces and variable names.
      As I mentioned in the post, I didn’t write this particular block of code. Particularly looking back on it 2 years later, I agree there are a lot of areas it could be improved.

      As for using using urandom, I think that’s a reasonable default. The Python docs explicitly call it out as “suitable for cryptographic use.” – On Linux systems, this is querying /dev/urandom, which should be sufficient, particularly in the case of trying to learn how the library plugs together.

      That said, in general I’d suggest people stay away from low-level crypto primitives entirely. Something like KeyCzar, GPG helps to protect people from making trivial mistakes which can have significant consequences.

      Reply

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s

Note: HTML is allowed. Your email address will never be published.

Subscribe to comments

%d bloggers like this: