# HOWTO: Migrating from M2Crypto to PyCA/cryptography

## Introduction

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 by using the `PKCS7` API, particularly the `PKCS7SignatureBuilder` class. Below is an example migration showcasing the equivalent APIs and parameters in PyCA/cryptography.

#### M2Crypto

    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())

#### PyCA/cryptography

    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)

## RSA

Following are migration examples for common operations with RSA key pairs. The documentation for the relevant `PyCA/cryptography` APIs can be found here.

### Signing and verifying

#### M2Crypto

    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())

#### PyCA/cryptography

    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())

### Encrypting and decrypting

#### M2Crypto

    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

    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.

### Loading and examining certificates

#### M2Crypto

    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())

#### PyCA/cryptography

    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)

### Signature verification

Note that this example only checks that the signature of a certificate matches a public key, as described in the OpenSSL documentation. For complete X.509 path validation see PyCA/cryptography’s X.509 verification module.

#### M2Crypto

    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

    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)