--- orphan: true --- (howto-migration)= # HOWTO: Migrating from M2Crypto to PyCA/cryptography ## Introduction [PyCA/cryptography](https://github.com/pyca/cryptography) is a package which provides cryptographic recipes and primitives to Python developers. This document has instructions on how to migrate from M2Crypto to PyCA/cryptography for features that are currently supported. ## S/MIME ### Signing If your application does S/MIME signing, it can be migrated to [PyCA/cryptography](https://github.com/pyca/cryptography) by using the `PKCS7` API, particularly the `PKCS7SignatureBuilder` class. Below is an example migration showcasing the equivalent APIs and parameters in [PyCA/cryptography](https://github.com/pyca/cryptography). #### M2Crypto ```{eval-rst} .. testcode:: from M2Crypto import BIO, SMIME s = SMIME.SMIME() s.load_key('../tests/signer_key.pem', '../tests/signer.pem') data = b'data' buf = BIO.MemoryBuffer(data) p7 = s.sign(buf, SMIME.PKCS7_DETACHED) out = BIO.MemoryBuffer() buf = BIO.MemoryBuffer(data) s.write(out, p7, buf) print(out.read()) ``` ```{eval-rst} .. testoutput:: :hide: b'MIME-Version: 1.0\nContent-Type: multipart/signed;... ``` #### PyCA/cryptography ```{eval-rst} .. testcode:: from cryptography.hazmat.primitives.serialization import load_pem_private_key, pkcs7, Encoding from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes from cryptography.x509 import load_pem_x509_certificate with open('../tests/signer_key.pem', 'rb') as key_data: key = load_pem_private_key(key_data.read(), password=None) with open('../tests/signer.pem', 'rb') as cert_data: cert = load_pem_x509_certificate(cert_data.read()) output = pkcs7.PKCS7SignatureBuilder().set_data( b"data" ).add_signer( cert, key, hashes.SHA512(), rsa_padding=padding.PKCS1v15() ).sign( Encoding.SMIME, [pkcs7.PKCS7Options.DetachedSignature] ) print(output) ``` ```{eval-rst} .. testoutput:: :hide: b'MIME-Version: 1.0\r\nContent-Type: multipart/signed;... ``` ## RSA Following are migration examples for common operations with RSA key pairs. The documentation for the relevant `PyCA/cryptography` APIs can be found [here](https://cryptography.io/en/latest/hazmat/primitives/asymmetric/rsa/). ### Signing and verifying #### M2Crypto ```{eval-rst} .. testcode:: from M2Crypto import RSA import hashlib message = b"This is the message string" digest = hashlib.sha1(message).digest() key = RSA.load_key('../tests/rsa.priv.pem') signature = key.sign(digest, algo='sha1') assert key.verify(digest, signature, algo='sha1') == 1 print(signature.hex()) ``` ```{eval-rst} .. testoutput:: :hide: 12068af2140bb2907fc0086872ae... ``` #### PyCA/cryptography ```{eval-rst} .. testcode:: from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import hashes from cryptography.hazmat.primitives import serialization with open('../tests/rsa.priv.pem', 'rb') as key_data: key = load_pem_private_key(key_data.read(), password=None) message = b"This is the message string" signature = key.sign(message, padding.PKCS1v15(), hashes.SHA1()) public_key = key.public_key() public_key.verify(signature, message, padding.PKCS1v15(), hashes.SHA1()) print(signature.hex()) ``` ```{eval-rst} .. testoutput:: :hide: 12068af2140bb2907fc0086872ae... ``` ### Encrypting and decrypting #### M2Crypto ```{eval-rst} .. testcode:: from M2Crypto import RSA message = b"This is the message string" key = RSA.load_key('../tests/rsa.priv.pem') cipher_text = key.public_encrypt(message, RSA.pkcs1_padding) plain_text = key.private_decrypt(cipher_text, RSA.pkcs1_padding) assert plain_text == message ``` #### PyCA/cryptography ```{eval-rst} .. testcode:: from cryptography.hazmat.primitives.asymmetric import padding from cryptography.hazmat.primitives import serialization with open('../tests/rsa.priv.pem', 'rb') as key_data: key = load_pem_private_key(key_data.read(), password=None) message = b"This is the message string" public_key = key.public_key() cipher_text = public_key.encrypt(message, padding.PKCS1v15()) plain_text = key.decrypt(cipher_text, padding.PKCS1v15()) assert plain_text == message ``` ## X.509 certificates Following are migration examples for common operations with X.509 certificates. The documentation for the relevant `PyCA/cryptography` APIs can be found [here](https://cryptography.io/en/latest/x509/). ### Loading and examining certificates #### M2Crypto ```{eval-rst} .. testcode:: from M2Crypto import X509 cert = X509.load_cert('../tests/x509.pem') print(cert.get_issuer()) print(cert.get_subject()) print(cert.get_not_before()) print(cert.get_not_after()) ``` ```{eval-rst} .. testoutput:: :hide: /C=US/ST=California/O=M2Crypto/CN=Heikki Toivonen /C=US/ST=California/O=M2Crypto/CN=X509 Apr 22 14:50:27 2025 GMT Apr 20 14:50:27 2035 GMT ``` #### PyCA/cryptography ```{eval-rst} .. testcode:: from cryptography import x509 with open('../tests/x509.pem', 'rb') as cert_data: cert = x509.load_pem_x509_certificate(cert_data.read()) print(cert.issuer) print(cert.subject) print(cert.not_valid_before_utc) print(cert.not_valid_after_utc) ``` ```{eval-rst} .. testoutput:: :hide: 2025-04-22 14:50:27+00:00 2035-04-20 14:50:27+00:00 ``` ### Signature verification Note that this example only checks that the signature of a certificate matches a public key, as described in [the OpenSSL documentation](https://www.openssl.org/docs/man3.2/man3/X509_verify.html). For complete X.509 path validation see [PyCA/cryptography's X.509 verification module](https://cryptography.io/en/latest/x509/verification). #### M2Crypto ```{eval-rst} .. testcode:: from M2Crypto import X509 cert = X509.load_cert('../tests/x509.pem') cacert = X509.load_cert("../tests/ca.pem") assert cert.verify(cacert.get_pubkey()) == 1 ``` #### PyCA/cryptography ```{eval-rst} .. testcode:: from cryptography.x509 import load_pem_x509_certificate with open('../tests/ca.pem', 'rb') as cacert_data: cacert = load_pem_x509_certificate(cacert_data.read()) with open('../tests/x509.pem', 'rb') as cert_data: cert = load_pem_x509_certificate(cert_data.read()) cert.verify_directly_issued_by(cacert) ```