Giter Site home page Giter Site logo

python-smime's Introduction

Python S/MIME Toolkit

This library implements a S/MIME handler. It supports only S/MIME messages encryption using a public RSA key, in AES128-CBC, AES192-CBC or AES256-CBC modes.

The ASN.1 implementation does not depend on pyasn1, as it showed too broken for creating and reading CMS (Cryptographic Message Syntax). Instead, the asn1crypto was used in this project.

This implementation does not use the deprecated pycrypto anymore; instead it was switched to the more modern cryptography library. It is not 'pure python' anymore (because of the latter dependency), but at least works.

Requirements

  • Python 2.7 or Python 3.5+
  • cryptography
  • asn1crypto

Example

The code below loads Alice's public key in PEM format and uses it to encrypt the e-mail in S/MIME format:

import sys
import smime

message = [
    'To: "Alice" <[email protected]>',
    'From: "Bob" <[email protected]>',
    'Subject: A message from python',
    '',
    'Now you see me.'
]

with open('alice-public-key.pem', 'rb') as pem:
    print(smime.encrypt('\n'.join(message), pem.read()))

Output:

To: "Alice" <[email protected]>
From: "Bob" <[email protected]>
Subject: A message from python
MIME-Version: 1.0
Content-Type: application/pkcs7-mime; smime-type=enveloped-data; name=smime.p7m
Content-Transfer-Encoding: base64
Content-Disposition: attachment; filename=smime.p7m

<base64-enveloped-data>

The same can be decrypted using OpenSSL from the command line:

$ openssl smime -decrypt -in smime.p7m -inkey alice-private-key.pem

OpenSSL expects that the smime.p7m file above should be in DER or PEM format. The latter should be enclosed in -----BEGIN PKCS7----- and -----END PKCS7----- and the content should be in base64 encoding, just like the output of the command above. Example:

-----BEGIN PKCS7-----
MIIBdgYJKoZIhvcNAQcDoIIBZzCCAWMCAQAxgb4wgbsCAQAwJjASMRAwDgYDVQQD
EwdDYXJsUlNBAhBGNGvHgABWvBHTbi7EELOwMAsGCSqGSIb3DQEBAQSBgCVAQwNg
LmJ5ESYxOM1YbOLz2gvzWY1Fk+LZZiylYe7+o1/e/MjtzNwhnu+8vziFwHbXEH1Y
jndIbUxiLyXb3omtNDunRICQin5bdo6BI7oE0MufUSqMjk0YUk8UQeNCiUfK89PR
RfDclb1/sM3XZ7mUJa2OzpnuQIWec3MuJ3k4MIGcBgkqhkiG9w0BBwEwHQYJYIZI
AWUDBAEqBBCVZVOt2lxSzmd+Ti1M372xgHDR0+ToLk1MJeTTtmJdnnNNH6631PN0
i3NJeJBKDDs4onI8xywqFtJP0of6GPoTGV/7D2vkgO2+jhCBTrzjYczbdOhh6Z5X
o0i/81NPSoaLhrfwKMQvT7sXX7c9YdbTjyglyGqhXUN8h+mIRlP9IStD
-----END PKCS7-----

Remember that the above formatting serves only for the purpose of testing the encryption with OpenSSL. Do not make such enclosing in e-mails.

License

This software is licensed under the Apache License 2.0. See the LICENSE file in the top distribution directory for the full license text.

Versioning

This software follows Semantic Versioning

python-smime's People

Contributors

balena avatar ruffsl avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar

python-smime's Issues

Project support

@balena Is this project still being maintained?

If not, would you consider moving it to a group so that other people could maintain it?

ValueError: The length of the provided data is not a multiple of the block length.

The problem is with special characters. When I trying to encrypt any non utf-8 characters, it fails with the following exception:

---------------------------------------------------------------------------
ValueError                                Traceback (most recent call last)
<ipython-input-82-46ba27e8d637> in <module>
     13 # with open('smime.cer', 'rb') as pem:
     14 key = obj.get()["Body"].read()
---> 15 message = smime.encrypt(message, key)
     16 
     17 # message = '\n'.join(message)

~/dev/jupyter/venv/lib/python3.8/site-packages/smime/encrypt.py in encrypt(message, certs, algorithm)
     62 
     63     # Encode the content
---> 64     encrypted_content_info = block_cipher.encrypt(content)
     65 
     66     # Build the enveloped data and encode in base64

~/dev/jupyter/venv/lib/python3.8/site-packages/smime/block.py in encrypt(self, data)
     49     def encrypt(self, data):
     50         padded_data = self._pad(data, self.block_size)
---> 51         encrypted_content = self._encryptor.update(padded_data.encode('utf-8')) + self._encryptor.finalize()
     52         return {
     53             'content_type': 'data',

~/dev/jupyter/venv/lib/python3.8/site-packages/cryptography/hazmat/primitives/ciphers/base.py in finalize(self)
    157         if self._ctx is None:
    158             raise AlreadyFinalized("Context was already finalized.")
--> 159         data = self._ctx.finalize()
    160         self._ctx = None
    161         return data

~/dev/jupyter/venv/lib/python3.8/site-packages/cryptography/hazmat/backends/openssl/ciphers.py in finalize(self)
    176                 )
    177             )
--> 178             raise ValueError(
    179                 "The length of the provided data is not a multiple of "
    180                 "the block length."

ValueError: The length of the provided data is not a multiple of the block length.
message = "Beste Grüße,\nIhr Team von Team\n£619"
message = smime.encrypt(message, key)

Any ideas, how to encrypt strings like this?

Support for S/MIME signing

Could you consider implementing S/MIME signing - pretty please..?! :-D

I would be willing to look into starting a PR for this.

Multipart messages

Multipart messages give an error message:

    to_encode = MIMEText(x)
  File "/usr/local/Cellar/python/3.7.0/Frameworks/Python.framework/Versions/3.7/lib/python3.7/email/mime/text.py", line 34, in __init__
    _text.encode('us-ascii')
AttributeError: 'list' object has no attribute 'encode'

I think the code currently assumes a single part, that can be converted into text (and base64 encoded so it fits the block cipher). Is there a more sophisticated way to 'flatten' a multipart message ready for encryption?
This would help towards supporting Content-Type multipart/signed also.

I think I found how Thunderbird is doing it, but I'm not good at reading Perl: https://dxr.mozilla.org/mozilla-esr45/source/security/nss/cmd/smimetools/smime

I'm happy to try and work on this, but if you have some hints on how to start that would be great.

ValueError: Unknown block algorithm

Attempting to use the smime.encrypt.encrypt() function generates this error when passing in a public key or a cert. Not sure if this is a bug or not, but any suggestions would be much appreciated.

What makes this more confusing is that when I import the blocks.py code and execute get_cipher, it seems to work fine and correctly return a cipher, but when executed from within the encrypt function, this no longer seems to work.

Multipart Emails

Having issues encrypting multipart emails with Python 2.7

Traceback (most recent call last):
File "buildExampleDjangoEmail.py", line 57, in
encrypted_email = smime.encrypt(email.message().as_string(), pem_key)
File "c:\Python27\lib\site-packages\smime\encrypt.py", line 42, in encrypt
to_encode = MIMEText(msg.get_payload())
File "c:\Python27\lib\email\mime\text.py", line 30, in init
self.set_payload(_text, _charset)
File "c:\Python27\lib\email\message.py", line 226, in set_payload
self.set_charset(charset)
File "c:\Python27\lib\email\message.py", line 268, in set_charset
cte(self)
File "c:\Python27\lib\email\encoders.py", line 73, in encode_7or8bit
orig.encode('ascii')
AttributeError: 'list' object has no attribute 'encode'

How to run the tests

I have seen that the are tests - but how are they supposed to run?

Running python setup.py test gives me:

[...]
=================================================================================================== ERRORS ===================================================================================================
_______________________________________________________________________________________ ERROR collecting test session ________________________________________________________________________________________
Defining 'pytest_plugins' in a non-top-level conftest is no longer supported:
It affects the entire test suite instead of just below the conftest as expected.
  /home/robbie/work/python-smime/smime/tests/conftest.py
Please move it to a top level conftest file at the rootdir:
  /home/robbie/work/python-smime
For more information, visit:
  https://docs.pytest.org/en/latest/deprecations.html#pytest-plugins-in-non-top-level-conftest-files
========================================================================================== short test summary info ===========================================================================================
ERROR
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!! Interrupted: 1 error during collection !!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
============================================================================================== 1 error in 0.15s ==================================================================

And python -m pytest smime/encrypt_test.py

[...]
$: python -m pytest smime/encrypt_test.py -v
============================================================================================ test session starts =============================================================================================
platform linux -- Python 3.5.2, pytest-5.4.0, py-1.8.1, pluggy-0.13.1 -- /home/user/work/python-smime/venv/bin/python
cachedir: .pytest_cache
rootdir: /home/robbie/work/python-smime, inifile: setup.cfg
collected 3 items

smime/encrypt_test.py::EncryptTest::test_message_to_carl_aes128_cbc FAILED                                                                                                                             [ 33%]
smime/encrypt_test.py::EncryptTest::test_message_to_carl_aes192_cbc FAILED                                                                                                                             [ 66%]
smime/encrypt_test.py::EncryptTest::test_message_to_carl_aes256_cbc FAILED                                                                                                                             [100%]

================================================================================================== FAILURES ==================================================================================================
________________________________________________________________________________ EncryptTest.test_message_to_carl_aes128_cbc _________________________________________________________________________________

self = <smime.encrypt_test.EncryptTest testMethod=test_message_to_carl_aes128_cbc>
[...]

pypi.org release 0.0.4 doesn't match git hub's release

Installing smime 0.0.4 using pip install smime==0.0.4 results in an error. It downloads the file from https://pypi.org/project/smime/0.0.4/#files but doesn't install.
Download location.
https://files.pythonhosted.org/packages/ef/24/53ff1e620c4a3da28c65697310d8275498c4b5c763c38706a01a356ca720/smime-0.0.4.tar.gz

When I installed the git hub release from home there was not an issue with Python 2.7. Good file location. https://github.com/balena/python-smime/archive/refs/tags/0.0.4.tar.gz

I compared the two zip files and for example smime/encrypt.py differences were:

diff -r .\encrypt_git.py .\encrypt_PyPI.py
14a15
> from email.message import EmailMessage
31c32
<     Takes the contents of the message parameter, formatted as in RFC 2822, and encrypts them,
---
>     Takes the contents of the message parameter, formatted as in RFC 2822 (type str or message), and encrypts them,
33c34
<     :return: string containing the new encrypted message.
---
>     :return: the new encrypted message (type str or message, as per input).
40,43c41,55
<     # Get the message content
<     msg = message_from_string(message)
<     to_encode = MIMEText(msg.get_payload())
<     content = to_encode.as_string()
---
>     # Get the message content. This could be a string, or a message object
>     passed_as_str = isinstance(message, str)
>     if passed_as_str:
>         msg = message_from_string(message)
>     else:
>         msg = message
>     # Extract the message payload without conversion, & the outermost MIME header / Content headers. This allows
>     # the MIME content to be rendered for any outermost MIME type incl. multipart
>     pl = EmailMessage()
>     for i in msg.items():
>         hname = i[0].lower()
>         if hname == 'mime-version' or hname.startswith('content-'):
>             pl.add_header(i[0], i[1])
>     pl._payload = msg._payload
>     content = pl.as_string()
45d56
<     # Generate the recipient infos
85c96,100
<     return result_msg.as_string()
---
>     # return the same type as was passed in
>     if passed_as_str:
>         return result_msg.as_string()
>     else:
>         return result_msg

My organization can only use the PyPI source, this makes using the smime 0.0.4 version very difficult if not impossible from work.

StringIO error during pip3 installation

I'm not sure if the release on pip is python3 compatible:
smime-0.0.1 md5: 4ee2f26429865f398767c2a07f83352c

$ sudo pip3 install smime
The directory '/home/ubuntu/.cache/pip/http' or its parent directory is not owned by the current user and the cache has been disabled. Please check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
The directory '/home/ubuntu/.cache/pip' or its parent directory is not owned by the current user and caching wheels has been disabled. check the permissions and owner of that directory. If executing pip with sudo, you may want sudo's -H flag.
Collecting smime
  Downloading smime-0.0.1.tar.gz (62kB)
    100% |████████████████████████████████| 71kB 4.4MB/s 
    Complete output from command python setup.py egg_info:
    Traceback (most recent call last):
      File "<string>", line 1, in <module>
      File "/tmp/pip-build-jpt8pz6l/smime/setup.py", line 16, in <module>
        version=__import__('smime').__version__,
      File "/tmp/pip-build-jpt8pz6l/smime/smime/__init__.py", line 7, in <module>
        from .encrypt import encrypt
      File "/tmp/pip-build-jpt8pz6l/smime/smime/encrypt.py", line 8, in <module>
        from .rsa import RSAPublicKey
      File "/tmp/pip-build-jpt8pz6l/smime/smime/rsa.py", line 4, in <module>
        from smime.crypto import cert
      File "/tmp/pip-build-jpt8pz6l/smime/smime/crypto/cert.py", line 9, in <module>
        from smime.crypto import pem
      File "/tmp/pip-build-jpt8pz6l/smime/smime/crypto/pem.py", line 4, in <module>
        import StringIO
    ImportError: No module named 'StringIO'
    
    ----------------------------------------
Command "python setup.py egg_info" failed with error code 1 in /tmp/pip-build-jpt8pz6l/smime/

AttributeError

I've tried the following, both with my native interpreter installation and a fresh VirtualEnv. I'm getting the following with the example code from the README

$ python3 sparkySecure.py 
Traceback (most recent call last):
  File "sparkySecure.py", line 12, in <module>
    print(smime.encrypt('\n'.join(message), pem.read()))
AttributeError: module 'smime' has no attribute 'encrypt'

Checking the package:

$ pip3 show smime
Name: smime
Version: 0.0.3
Summary: Python S/MIME Toolkit
Home-page: https://github.com/balena/python-smime
Author: G. B. Versiani
Author-email: [email protected]
License: Apache License (2.0)
Location: /usr/local/lib/python3.7/site-packages
Requires: asn1crypto, pycrypto
Required-by: 

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.