From f4f05e8abd6d3c7a993a7ad2dfd57b138f57ec4d Mon Sep 17 00:00:00 2001 From: Mathieu Dugre Date: Fri, 28 Feb 2020 21:30:46 +0000 Subject: [PATCH] Added Python wrapper for the CryptoLW classes (Acorn and Ascon). --- .../CryptoLW/examples/Python/TestAcorn.py | 60 +++++++ libraries/CryptoLW/python/PythonWrapper.cpp | 157 ++++++++++++++++++ libraries/CryptoLW/python/README.md | 74 +++++++++ libraries/CryptoLW/python/setup.py | 45 +++++ 4 files changed, 336 insertions(+) create mode 100644 libraries/CryptoLW/examples/Python/TestAcorn.py create mode 100644 libraries/CryptoLW/python/PythonWrapper.cpp create mode 100644 libraries/CryptoLW/python/README.md create mode 100644 libraries/CryptoLW/python/setup.py diff --git a/libraries/CryptoLW/examples/Python/TestAcorn.py b/libraries/CryptoLW/examples/Python/TestAcorn.py new file mode 100644 index 00000000..ee32dab0 --- /dev/null +++ b/libraries/CryptoLW/examples/Python/TestAcorn.py @@ -0,0 +1,60 @@ +from CryptoLW import Acorn128, Ascon128 +import binascii +import secrets + +header = b"header" +data = b"secret" +key = secrets.token_bytes(16) # get_random_bytes(16) +iv = secrets.token_bytes(16) + +cipher = Acorn128() +cipher.clear() +cipher.setKey(key) +cipher.setIV(iv) +cipher.addAuthData(header) +ciphertext = cipher.encrypt(data) +tag = cipher.computeTag() + +print("Cipher Text : %s" % binascii.hexlify(ciphertext).decode('utf-8')) +print("Tag : %s" % binascii.hexlify(tag).decode('utf-8')) + +# Tester le decryptage + +cipher.clear() +cipher.setKey(key) +cipher.setIV(iv) +cipher.addAuthData(header) +cleartext = cipher.decrypt(ciphertext) + +print("Clear text : %s" % cleartext.decode('utf-8')) +cipher.checkTag(tag) +print("Tag OK!") + +cipher.clear() +cipher.setKey(key) +cipher.setKey(iv) +cipher.addAuthData(header) +cleartext = cipher.decrypt(ciphertext) +try: + cipher.checkTag(iv) # Mauvais tag (IV) + print("Une erreur aurait du etre souleve (***incorrect***)") +except ValueError: + print("Echec verification (correct)") + + +# Test avec donnees du Arduino +header = binascii.unhexlify(b"0102030405060708") +key = binascii.unhexlify(b"233952DEE4D5ED5F9B9C6D6FF80FF478") +iv = binascii.unhexlify(b"2D2B10316ABE7766AABADFEFB8E139EA") +tag = binascii.unhexlify(b"3E67F10B7FD68BAA8206C62BD0026B1B") +buffer_crypte = binascii.unhexlify(b"136A14EC1F53E0C7CB19AADC38E70D274774E3CAC147223E") + +cipher.clear() +cipher.setKey(key) +cipher.setIV(iv) +cipher.addAuthData(header) +cleartext = cipher.decrypt(buffer_crypte) + +print("Clear text : %s" % binascii.hexlify(cleartext).decode('utf-8')) +cipher.checkTag(tag) +print("Tag OK!") diff --git a/libraries/CryptoLW/python/PythonWrapper.cpp b/libraries/CryptoLW/python/PythonWrapper.cpp new file mode 100644 index 00000000..ff57bbbb --- /dev/null +++ b/libraries/CryptoLW/python/PythonWrapper.cpp @@ -0,0 +1,157 @@ +// #include +#include +#include "Acorn128.h" +#include "Ascon128.h" +namespace bp = boost::python; + +// Wrapper for Python using Boost.Python (boost.org) + +// ---------------------------- +// Shamelessly stolen from pyRF24 (https://github.com/nRF24/RF24) +void throw_ba_exception(void) +{ + PyErr_SetString(PyExc_TypeError, "buf parameter must be bytes or bytearray"); + bp::throw_error_already_set(); +}; + +char* get_bytes_or_bytearray_str(bp::object buf) +{ + PyObject* py_ba; + py_ba = buf.ptr(); + if (PyByteArray_Check(py_ba)) { + return PyByteArray_AsString(py_ba); + } else if (PyBytes_Check(py_ba)) { + return PyBytes_AsString(py_ba); + } else { + throw_ba_exception(); + } + return NULL; +}; + +int get_bytes_or_bytearray_ln(bp::object buf) +{ + PyObject* py_ba; + py_ba = buf.ptr(); + if (PyByteArray_Check(py_ba)) { + return PyByteArray_Size(py_ba); + } else if (PyBytes_Check(py_ba)) { + return PyBytes_Size(py_ba); + } else { + throw_ba_exception(); + } + return 0; +}; +// ---------------------------- + +bool setKey_wrap(AuthenticatedCipher& ref, bp::object buf) +{ + const char* buffer = get_bytes_or_bytearray_str(buf); + return ref.setKey((uint8_t*)buffer, get_bytes_or_bytearray_ln(buf)); +}; + +bool setIV_wrap(AuthenticatedCipher& ref, bp::object buf) +{ + const char* buffer = get_bytes_or_bytearray_str(buf); + return ref.setIV((uint8_t*)buffer, get_bytes_or_bytearray_ln(buf)); +}; + +void addAuthData_wrap(AuthenticatedCipher& ref, bp::object buf) +{ + const char* buffer = get_bytes_or_bytearray_str(buf); + ref.addAuthData((uint8_t*)buffer, get_bytes_or_bytearray_ln(buf)); +}; + +bp::object encrypt_wrap(AuthenticatedCipher& ref, bp::object buf) +{ + const char* inputBuffer = get_bytes_or_bytearray_str(buf); + + // Recuperer la taille du buffer, identique pour output. + int len = get_bytes_or_bytearray_ln(buf); + + // Creer un buffer d'output sur le heap et chiffrer + char* outputBuffer = new char[len + 1]; + ref.encrypt((uint8_t*) outputBuffer, (uint8_t*) inputBuffer, len); + + // Convertir le buffer d'output en objet Python + bp::object py_ba(bp::handle<>( PyByteArray_FromStringAndSize(outputBuffer, len) )); + + delete[] outputBuffer; // Cleanup heap + return py_ba; +}; + +bp::object decrypt_wrap(AuthenticatedCipher& ref, bp::object buf) +{ + const char* inputBuffer = get_bytes_or_bytearray_str(buf); + + // Recuperer la taille du buffer, identique pour output. + int len = get_bytes_or_bytearray_ln(buf); + + // Creer un buffer d'output sur le heap et dechiffrer + char* outputBuffer = new char[len + 1]; + ref.decrypt((uint8_t*) outputBuffer, (uint8_t*) inputBuffer, len); + + // Convertir le buffer d'output en objet Python + bp::object py_ba(bp::handle<>( PyByteArray_FromStringAndSize(outputBuffer, len) )); + + delete[] outputBuffer; // Cleanup heap + return py_ba; +}; + +bp::object computeTag_wrap(AuthenticatedCipher& ref) +{ + // Compute tag, sauver un buffer temporaire + char outputBuffer[16]; + ref.computeTag((uint8_t*)&outputBuffer, sizeof(outputBuffer)); + + // Convertir le buffer d'output en objet Python + bp::object py_ba(bp::handle<>( PyByteArray_FromStringAndSize(outputBuffer, sizeof(outputBuffer)) )); + + return py_ba; +}; + +void checkTag_wrap(AuthenticatedCipher& ref, bp::object buf) +{ + const char* buffer = get_bytes_or_bytearray_str(buf); + + if( ! ref.checkTag((uint8_t*)buffer, get_bytes_or_bytearray_ln(buf)) ) + { + // Comportement Python habituel, lancer une exception plutot que retourner un false + PyErr_SetString(PyExc_ValueError, "AuthenticatedCipher: invalid tag"); + bp::throw_error_already_set(); + } +}; + +BOOST_PYTHON_MODULE(CryptoLW) +{ + bp::class_("Cipher", bp::no_init); + + bp::class_, boost::noncopyable>("AuthenticatedCipher", bp::no_init); + + bp::class_>("Acorn128") + .def("keySize", &Acorn128::keySize) + .def("ivSize", &Acorn128::ivSize) + .def("tagSize", &Acorn128::tagSize) + .def("setKey", &setKey_wrap) + .def("setIV", &setIV_wrap) + .def("encrypt", &encrypt_wrap) + .def("decrypt", &decrypt_wrap) + .def("addAuthData", &addAuthData_wrap) + .def("computeTag", &computeTag_wrap) + .def("checkTag", &checkTag_wrap) + .def("clear", &Acorn128::clear) + ; + + bp::class_>("Ascon128") + .def("keySize", &Ascon128::keySize) + .def("ivSize", &Ascon128::ivSize) + .def("tagSize", &Ascon128::tagSize) + .def("setKey", &setKey_wrap) + .def("setIV", &setIV_wrap) + .def("encrypt", &encrypt_wrap) + .def("decrypt", &decrypt_wrap) + .def("addAuthData", &addAuthData_wrap) + .def("computeTag", &computeTag_wrap) + .def("checkTag", &checkTag_wrap) + .def("clear", &Ascon128::clear) + ; +}; diff --git a/libraries/CryptoLW/python/README.md b/libraries/CryptoLW/python/README.md new file mode 100644 index 00000000..4b8b3ada --- /dev/null +++ b/libraries/CryptoLW/python/README.md @@ -0,0 +1,74 @@ +Python extension for the light-weight algorithms for the rweather Arduino Cryptography Library. +https://github.com/rweather/arduinolibs + +### Installation + +To install using Python 3 on Debian/Ubuntu: first ensure you have python3-dev, pip3. +Then install the Boost-Python library and the python setup-tools. + +Example installation for Ubuntu and Debian: + +* `sudo apt install -y python3-dev python3-pip libboost-python-dev` +* `sudo pip3 install setuptools` + +From the libraries/CryptoLW/python directory, run: + +* `sudo python3 setup.py install` + +### Usage + +#### Encryption +``` +from CryptoLW import Acorn128 +import secrets +# Note: secrets is new in Python 3.6. You can use a different library +# for random bytes, e.g. pycryptodome.Random. + +# Sample data +header = b"header" +data = b"secret" +key = secrets.token_bytes(16) +iv = secrets.token_bytes(16) + +# Prepare cipher +cipher = Acorn128() +cipher.clear() # Required if reusing the cipher +cipher.setKey(key) # Shared secret key +cipher.setIV(iv) # Initialization Vector + +# Add data +cipher.addAuthData(header) +ciphertext = cipher.encrypt(data) + +# Compute tag +tag = cipher.computeTag() +``` +The initialisation vector (IV) must not be reused between encryption sessions and should +not be an easily predictable value (e.g. do not use iv++). +The header, tag, iv and ciphertext can be transmitted to the destination over an unsafe medium. +The shared secret key must not be transmitted, it must be handled prior to using this algorithm. + +#### Decryption +``` +from CryptoLW import Acorn128 + +# Prepare cipher +cipher = Acorn128() +cipher.clear() # Required if reusing the cipher +cipher.setKey(key) # Shared secret key +cipher.setIV(iv) # Initialization Vector + +# Initialise with unencrypted content +cipher.addAuthData(header) + +# Apply encrypted content to recover cleartext +cleartext = cipher.decrypt(ciphertext) + +try: + cipher.checkTag(tag) # Use tag produced by encryption process + print("The decryption process is successful") +except ValueError: + print("The process failed, header cannot be trusted and cleartext is likely invalid") +``` +A ValueError exception is thrown when the tag does not match the content that is applied +on the cipher. \ No newline at end of file diff --git a/libraries/CryptoLW/python/setup.py b/libraries/CryptoLW/python/setup.py new file mode 100644 index 00000000..db73a676 --- /dev/null +++ b/libraries/CryptoLW/python/setup.py @@ -0,0 +1,45 @@ +from distutils.core import setup, Extension +import sys +from os import path + +path_libs = '../../' + + +def main(): + + if sys.version_info >= (3,): + BOOST_LIB = 'boost_python3' + else: + BOOST_LIB = 'boost_python' + + setup(name="CryptoLW", + version="0.2.0", + description= + """ + Python extension for the light-weight algorithms for the rweather Arduino Cryptography Library. + https://github.com/rweather/arduinolibs + by Rhys Weatherley + """, + author="Mathieu Dugre", + author_email="mathieu.dugre@mdugre.info", + ext_modules=[Extension( + "CryptoLW", + [ + path.join(path_libs, "Crypto/Crypto.cpp"), + path.join(path_libs, "Crypto/Cipher.cpp"), + path.join(path_libs, "Crypto/AuthenticatedCipher.cpp"), + path.join(path_libs, "CryptoLW/src/Acorn128.cpp"), + path.join(path_libs, "CryptoLW/src/Ascon128.cpp"), + "PythonWrapper.cpp", + ], + libraries=[BOOST_LIB], + include_dirs=[ + path.join(path_libs, "Crypto"), + path.join(path_libs, "Crypto/utility"), + path.join(path_libs, "CryptoLW/src"), + ] + )]) + + +if __name__ == "__main__": + main()