3
\$\begingroup\$

I am looking to build a message encryption/decription method that would allow messages to be encrypted in a timely fashion on board an Arduino Portenta H7, using as much of the onboard hardware acceleration as possible, and decrypted using python. This will be used to encrypt and sign an MQTT payload. The Server public cert and the client private key would be available to the encrypt function (on the arduino), and the client public key, and server private key to the decrypt function.

My question is: Would this be an acceptable method to transfer data securely over the internet, am I missing anything.

"""Test encrypt and decrypt routines
"""
import datetime
import os
import uuid

from cryptography import x509
from cryptography.hazmat.primitives import hashes, serialization
from cryptography.hazmat.primitives.asymmetric import padding, rsa
from cryptography.hazmat.primitives.asymmetric.utils import Prehashed
from cryptography.hazmat.primitives.ciphers.aead import AESGCM as ENCRYPTIONCLASS
from cryptography.x509.oid import NameOID

# hash class to use
chosen_hash = hashes.SHA256

nonce_length = 96
key_length = 256
hash_length = chosen_hash.digest_size

# length of ciphertext resulting from P-key encryption
# todo, find way to determine this prgrammatically by cipher used.
ciphertext_length = key_length
# options to pass to generate_key method of encryption object
key_options = {"bit_length": key_length, }
# pre-shared key or identifier such as serial or preferably UUID
UUID = uuid.uuid4().bytes

def decrypt(msg:bytes) -> bytes:
    """Decrypt a message (bytes)
    message is in the format(length): encrypted_message(<2^31-1) + encrypted_key_and_nonce(256) + signature(256)
    """
    with open("client_cert.pem", "rb") as certFile:
        public_key = x509.load_pem_x509_certificate(certFile.read()).public_key()

    with open("server_key.pem", "rb") as keyFile:
        private_key = serialization.load_pem_private_key(keyFile.read(), password=None)
        
    encrypted_message, encrypted_keys, signature = msg[:-ciphertext_length * 2], msg[-ciphertext_length * 2:-ciphertext_length], msg[-ciphertext_length:]
    plain_key_and_nonce = private_key.decrypt(encrypted_keys, padding.OAEP(mgf=padding.MGF1(algorithm=chosen_hash()), algorithm=chosen_hash(), label=None))
    nonce = plain_key_and_nonce[-nonce_length:]
    plain_key = plain_key_and_nonce[:-nonce_length]
    sym_cipher_object = ENCRYPTIONCLASS(plain_key)
    message = sym_cipher_object.decrypt(nonce, encrypted_message, UUID)
    hasher = hashes.Hash(chosen_hash())
    hasher.update(plain_key)
    digest = hasher.finalize()
    public_key.verify( signature, digest, padding.PSS( mgf=padding.MGF1(chosen_hash()), salt_length=padding.PSS.MAX_LENGTH), Prehashed(chosen_hash()))
    return message


def encrypt(msg: bytes) -> bytes:
    """Encrypt and sign a message (bytes)
    returns message in the format(length): encrypted_message(<2^31-1) + encrypted_key_and_nonce(256) + signature(256)
    """
    with open("server_cert.pem", "rb") as certFile:
        public_key = x509.load_pem_x509_certificate(certFile.read()).public_key()
    with open("client_key.pem", "rb") as keyFile:
        private_key = serialization.load_pem_private_key(keyFile.read(), password=None)

    plain_key = ENCRYPTIONCLASS.generate_key(**key_options)
    sym_cipher_object = ENCRYPTIONCLASS(plain_key)
    nonce = os.urandom(nonce_length)
    encrypted_message = sym_cipher_object.encrypt(nonce, msg, UUID)
    hasher = hashes.Hash(chosen_hash())
    hasher.update(plain_key)
    digest = hasher.finalize()
    signature = private_key.sign(digest,padding.PSS(mgf=padding.MGF1(chosen_hash()), salt_length=padding.PSS.MAX_LENGTH),Prehashed(chosen_hash()))
    plain_key_and_nonce = plain_key + nonce
    ciphertext = public_key.encrypt(plain_key_and_nonce, padding.OAEP(mgf=padding.MGF1(algorithm=chosen_hash()), algorithm=chosen_hash(),label=None))
    return encrypted_message + ciphertext + signature


if __name__ == "__main__":
    # create certs locally for testing
    # create()
    # load a file as a test message

    with open(r"en_decrypt_demo.py", "rb") as message_file:
        data = message_file.read()

    # encrypt file contents
    cipher = encrypt(data)
    # print(f"Cipher text: {cipher}")
    # decrypt the contents
    decrypted = decrypt(cipher)
    # print(f"Decrypted Text: {decrypted.decode()}")
    assert data == decrypted
    rand_data = os.urandom(500_000)
    assert rand_data == decrypt(encrypt(rand_data))
    rand_data = os.urandom(500_000_000)
    assert rand_data == decrypt(encrypt(rand_data))
\$\endgroup\$
2
  • \$\begingroup\$ I don't see any references to Arduino hardware drivers. Are you using MicroPython? \$\endgroup\$
    – Reinderien
    Commented Mar 22, 2022 at 19:36
  • \$\begingroup\$ @Reinderien I am not actually looking to implement the Arduino side, but only the server side the embedded code will be developed at a later date. This question is more of 'is this approach correct and would it work on a portenta with the available crypto capabilities' \$\endgroup\$
    – L Selter
    Commented Mar 22, 2022 at 23:01

0

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Browse other questions tagged or ask your own question.