PhigoroDark
Blog / Implementing Hybrid Encryption in Python

Implementing Hybrid Encryption in Python

Implementing Hybrid Encryption in Python Implementing hybrid encryption in python

Theory

Encryption can be done using symmetric or asymmetric methods.

While symmetric encryption is fast, it is less secure as it uses a single key for encryption and decryption.

Symmetric encryption Symmetric encryption

On the other hand, asymmetric encryption is much safer as two mathematically connected keys are used, but it is slower in terms of speed. To get the best of both worlds, we can use hybrid encryption.

Asymmetric encryption Asymmetric encryption

In hybrid encryption, a new symmetric key is generated each time we send data, and it is encrypted with the recipient's public key. The encrypted data and the symmetric key are then sent together. When the recipient receives the data, they decrypt the symmetric key with their private key and then use it to decrypt the data.

Hybrid encryption Hybrid encryption

Practice

In practice, to send data over an insecure network without SSL, we need to install necessary packages:

pip install rsa cryptography

Then generate a public-private key pair, share the private key with the receiver, generate a symmetric key, encrypt the data with the symmetric key, encrypt the symmetric key with the public key, encode both with base64, send the data along with the encrypted symmetric key, receive, decode the data and the symmetric key from base64, decrypt the symmetric key with the private key, and finally decrypt the data with the symmetric key.

import rsa
from cryptography.fernet import Fernet
from base64 import b64encode, b64decode

# Generate public + private keypair
publicKey, privateKey = rsa.newkeys(2048)

# Generate a symmetric key
symmetricKey = Fernet.generate_key()
f = Fernet(symmetricKey)

# Sample data to encrypt
data_dict = {'user_id':1, 'name':'John', 'age':30}
data = str(data_dict)

print("\nOriginal data: ", data)

# Encrypt data with symmetric key
enc_data = f.encrypt(data.encode('utf-8'))

# Convert to base64 for sending over the network
b64_enc_data = b64encode(enc_data).decode('utf-8')

# publicKey = rsa.PublicKey.load_pkcs1(publicKey)
enc_symmetricKey = rsa.encrypt(symmetricKey, publicKey)

# Convert the symmetric key to base64 for sending
# over the network
b64_enc_symmetricKey = b64encode(enc_symmetricKey).decode('utf-8')

# Create payload
payload = { 'key':b64_enc_symmetricKey, 'data': b64_enc_data }

print("\nPayload: ", payload)

# Here we send payload over the network
# And receive it somewhere at the client side

# Decode the symmetric key from base64
enc_symmetricKey = b64decode(payload['key'])

# Decode the data from base64
enc_data = b64decode(payload['data'])

# Decrypt the symmetric key
symmetricKey = rsa.decrypt(enc_symmetricKey, privateKey)

# Decrypt the data
f = Fernet(symmetricKey)
data = f.decrypt(enc_data).decode('utf-8')

print("\nDecrypted data: ", data)

For this scenario, we utilized RSA keys of 2048 bits. However, depending on your specific use case, you can select from key sizes of 1024, 2048, or 4096 bits. The private key can be stored on the client side as either a .PEM file or as a configuration variable.