Skip to content

Instantly share code, notes, and snippets.

@gsakkis
Last active January 31, 2024 03:00
Show Gist options
  • Star 8 You must be signed in to star a gist
  • Fork 2 You must be signed in to fork a gist
  • Save gsakkis/4546068 to your computer and use it in GitHub Desktop.
Save gsakkis/4546068 to your computer and use it in GitHub Desktop.
Porting AES from M2Crypto to Crypto
from binascii import hexlify, unhexlify
from hashlib import md5
from Crypto.Cipher import AES
try:
from M2Crypto import EVP
except ImportError:
EVP = None
def m2_encrypt(plaintext, key, iv, key_as_bytes=False, padding=True):
cipher = EVP.Cipher(alg="aes_256_cbc", key=key, iv=iv,
key_as_bytes=key_as_bytes, padding=padding, op=1)
return cipher.update(plaintext) + cipher.final()
def m2_decrypt(ciphertext, key, iv, key_as_bytes=False, padding=True):
cipher = EVP.Cipher(alg="aes_256_cbc", key=key, iv=iv,
key_as_bytes=key_as_bytes, padding=padding, op=0)
return cipher.update(ciphertext) + cipher.final()
def py_encrypt(plaintext, key, iv, key_as_bytes=False, padding=True):
if key_as_bytes:
key = bytes_to_key(key)
cipher = AES.new(key, AES.MODE_CBC, iv)
if padding:
plaintext = pkcs7_encode(plaintext)
return cipher.encrypt(plaintext)
def py_decrypt(ciphertext, key, iv, key_as_bytes=False, padding=True):
if key_as_bytes:
key = bytes_to_key(key)
cipher = AES.new(key, AES.MODE_CBC, iv)
plaintext = cipher.decrypt(ciphertext)
if padding:
plaintext = pkcs7_decode(plaintext)
return plaintext
def pkcs7_encode(text, k=16):
n = k - (len(text) % k)
return text + unhexlify(n * ("%02x" % n))
def pkcs7_decode(text, k=16):
n = int(hexlify(text[-1]), 16)
if n > k:
raise ValueError("Input is not padded or padding is corrupt")
return text[:-n]
def bytes_to_key(data, salt="12345678"):
# Simplified version of M2Crypto.m2.bytes_to_key(). Based on:
# https://github.com/ajmirsky/M2Crypto/blob/master/M2Crypto/EVP.py#L105
# http://stackoverflow.com/questions/8008253/c-sharp-version-of-openssl-evp-bytestokey-method
assert len(salt) == 8, len(salt)
data += salt
key = md5(data).digest()
key += md5(key + data).digest()
return key
def test_pycrypto(plaintext, key, iv, key_as_bytes, padding):
ciphertext = py_encrypt(plaintext, key, iv, key_as_bytes, padding)
assert py_decrypt(ciphertext, key, iv, key_as_bytes, padding) == plaintext
return ciphertext
def test_m2crypto(plaintext, key, iv, key_as_bytes, padding):
ciphertext = m2_encrypt(plaintext, key, iv, key_as_bytes, padding)
assert m2_decrypt(ciphertext, key, iv, key_as_bytes, padding) == plaintext
return ciphertext
if EVP:
def test(*args):
py_ciphertext = test_pycrypto(*args)
m2_ciphertext = test_m2crypto(*args)
assert py_ciphertext == m2_ciphertext
else:
test = test_pycrypto
def gen_test_inputs():
from os import urandom
from itertools import product
keys = [urandom(32) for _ in xrange(2)]
ivs = [urandom(16) for _ in xrange(2)]
key_as_bytes = [False, True]
# padding=False
plaintexts = [urandom(16 * n) for n in xrange(1025)]
for combo in product(plaintexts, keys, ivs, key_as_bytes, [False]):
yield combo
# padding=True
plaintexts = [urandom(n) for n in xrange(1025)]
for combo in product(plaintexts, keys, ivs, key_as_bytes, [True]):
yield combo
def benchmark(test_func):
from time import time
t = time()
for combo in gen_test_inputs():
test_func(*combo)
return time() - t
if __name__ == "__main__":
for combo in gen_test_inputs():
test(*combo)
print "pycrypto: %.2fs" % benchmark(test_pycrypto)
if EVP:
print "M2Crypto: %.2fs" % benchmark(test_m2crypto)
@diyan
Copy link

diyan commented Feb 4, 2013

Could you please share benchmark results between PyCrypto and M2Crypto?

@diyan
Copy link

diyan commented Jan 6, 2014

I've got following results on Ubuntu 13.10 64 bit, Intel i5 650 @ 3.20GHz

$ seq 1 3 | xargs -Iz python aes.py
pycrypto: 2.05s
M2Crypto: 0.92s
pycrypto: 2.04s
M2Crypto: 0.92s
pycrypto: 2.03s
M2Crypto: 0.90s

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment