In the middle of a project I’m presently working on, I needed to make use of a Symmetric encryption based on the workflow of my software. A key is required by users to encrypt data and the same key will be needed to decrypt data. My first thoughts were on PyCrypto so I went ahead doing this
from Crypto.Cipher import AES
from Crypto import Random
text = 'The quick brown fox jumped over the dog'
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CFB, iv)
ctext = iv + cipher.encrypt(text)
This was totally fine but when I tried decrypting the ctext data, I got something entirely different from what my input was.
decrypted = cipher.decrypt(ctext)
Strange behaviour from MODE_CFB made me try MODE_CBC. Just the exact same way, I went ahead to just change to MODE_CBC
from Crypto.Cipher import AES
from Crypto import Random
text = 'The quick brown fox jumped over the dog'
iv = Random.new().read(AES.block_size)
cipher = AES.new(key, AES.MODE_CBC, iv)
ctext = iv + cipher.encrypt(text)
This mode just fails right away with a warning that the message must be a multiple of 16. I expect any length of message from the users of my program and this didn’t seem like a good idea. While searching the web, I came across a solution that was nice but not what I wanted
http://www.codekoala.com/posts/aes-encryption-python-using-pycrypto/
I wrote my own little function for that
def AEScrypt(action, string):
"""
Solution by codekoala (http://www.codekoala.com/posts/aes-encryption-python-using-pycrypto/)
Requires base64, os, and AES in Crypto.Cipher
@param action string :encrypt or decrypt
@param string string :cipher or plain text
"""
BLOCK_SIZE = 32
PADDING = '{'
pad = lambda s: s + (BLOCK_SIZE - len(s) % BLOCK_SIZE) * PADDING
EncodeAES = lambda c, s: base64.b64encode(c.encrypt(pad(s)))
DecodeAES = lambda c, e: c.decrypt(base64.b64decode(e)).rstrip(PADDING)
secret = os.urandom(BLOCK_SIZE)
cipher = AES.new(secret)
result = EncodeAES(cipher, string) if action == 'encrypt' else DecodeAES(cipher, string)
return result
This seem like the best way to use PyCrypto AES encryption but it wasn’t symmetric. It’s not in a PKI format (no key is required to unlock the message).
I still keep that function but it was of no use in my case. Finally I thought of the gnupg module and installed it for my python2
sudo apt-get install python-gnupg
I was able to encrypt this way:
import gnupg,base64
gpg = gnupg.GPG()
def encrypt(passphrase, message):
cipher = gpg.encrypt(message, recipients=None, symmetric='AE256', passphrase=passphrase, armor=True)
return base64.b64encode( str(cipher) )
I had to encode with base64 because I don’t want my encrypted data to have the regular start and end of a GPG encrypted data i.e
----- BEGIN PGP KEY ----- ----- END PGP KEY -------
To decrypt the encrypted data, I use this:
def decrypt(cipher, passphrase):
deciphered = str( gpg.decrypt( base64.b64decode(cipher), passphrase ) )
return deciphered if deciphered is True else 'Incorrect passphrase'
When a wrong passphrase is used gpg.decrypt() returns an empty string ` ` which is False. If it is true we get our deciphered text.