Asymmetric Encryption & Decryption in Python

December 2021 · Derick Zr · 4 minutes read

I needed to send sensitive data between a client and server.

First thought: just send it. Who's going to intercept it?

Terrible idea. Anyone on the network could read it. I needed encryption.

Here's what I learned about cryptography and how to actually implement it.

What Encryption Actually Does

Encryption takes readable data and scrambles it using mathematical algorithms.

Only someone with the right key can unscramble it.

You use it every day: HTTPS, messaging apps, credit card transactions, password storage.

Three Types of Encryption

Symmetric (Secret Key): Same key encrypts and decrypts. Fast. Used for bulk data. Problem: how do you share the key securely?

Asymmetric (Public Key): Two keys. Public key encrypts, private key decrypts. Slower but solves the key-sharing problem. This is what we're implementing.

Hashing: One-way encryption. Can't be decrypted. Used for passwords and integrity checks.

Why Asymmetric Encryption Matters

I can give you my public key openly. You encrypt data with it. Only I can decrypt it with my private key.

No secret key exchange needed.

Let's build this in Python using RSA encryption.

Interactive Demo

Try encryption and decryption right in your browser using the Web Crypto API:

RSA Encryption Playground

React State

Generate keys, encrypt, and decrypt messages using RSA-OAEP in your browser

Loading encryption playground...

Now let's see how to implement the same concepts in Python.

Implementation

Install the Library

Python doesn't have built-in encryption for files. I used the cryptography module.

Terminal
python -m pip install cryptography

Generate Keys

RSA needs two keys: private and public.

encryption.py
from cryptography.hazmat.backends import default_backend
from cryptography.hazmat.primitives.asymmetric import rsa
private_key = rsa.generate_private_key(
    public_exponent=65537,
    key_size=2048,
    backend=default_backend()
)
public_key = private_key.public_key()

Storing Keys

To store the keys in a file, the keys need to be serialized and then written to a file. To store the private key, we need to use the following.

encryption.py
...
 pem = private_key.private_bytes(
    encoding=serialization.Encoding.PEM,
    format=serialization.PrivateFormat.PKCS8,
    encryption_algorithm=serialization.NoEncryption()
)
 
 with open('private_key.pem', 'wb') as f:
    f.write(pem)
 

You can protect the contents of this file using a top key serialization password find an example here

Reading Keys

To get the keys out of the files, we need to read each file and then load them. To read the private key, use the following.

encryption.py
...
with open("private_key.pem", "rb") as f:
    private_key = serialization.load_pem_private_key(
        key_file.read(),
        password=None,
        backend=default_backend()
    )

If you store the key with a password, set password to what you used.

The variable private_key will now have the private key. To read the public key, we need to use a slightly modified version. The variable public_key will now have the public key.

encryption.py
...
with open("public_key.pem", "rb") as f:
    public_key = serialization.load_pem_public_key(
        key_file.read(),
        backend=default_backend()
    )

EXAMPLE

to show this in action, here is a properly constructed example

encryption_decryption_example.py
 
from cryptography.hazmat.backends import default_backend
    from cryptography.hazmat.primitives.asymmetric import rsa
    from cryptography.hazmat.primitives import serialization
    from cryptography.hazmat.primitives.asymmetric import padding
    from cryptography.hazmat.primitives import hashes
 
    # Generating a key
 
    private_key = rsa.generate_private_key(
            public_exponent=65537,
            key_size=2048,
            backend=default_backend()
        )
    public_key = private_key.public_key()
 
    # Storing the keys
 
    pem = private_key.private_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PrivateFormat.PKCS8,
            encryption_algorithm=serialization.NoEncryption()
        )
 
    with open('Bank/private_key.pem', 'wb') as f:
        f.write(pem)
    with open('Grace/private_key.pem', 'wb') as f:
        f.write(pem)
    pem = public_key.public_bytes(
            encoding=serialization.Encoding.PEM,
            format=serialization.PublicFormat.SubjectPublicKeyInfo
        )
 
 
    with open('public_key.pem', 'wb') as f:
        f.write(pem)
 
    f = open('Bank/info.txt', 'rb')
    message = f.read()
    f.close()
 
    encrypted = public_key.encrypt(
            message,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
    f = open('Grace/info.encrypted', 'wb')
    f.write(encrypted)
    f.close()
 
    # Grace RECEIVED THE ENCRYPTED MESSAGE
 
    # AND AUTOMATICALLY THE SYSTEM WILL DECRYPTED IT USING THE PRIVATE KEY of GRACE
 
    original_message = private_key.decrypt(
            encrypted,
            padding.OAEP(
                mgf=padding.MGF1(algorithm=hashes.SHA256()),
                algorithm=hashes.SHA256(),
                label=None
            )
        )
    with open('Grace/Bank_info.txt','wb') as f:
        f.write(original_message)
 

Encrypting Files

For files, read them in binary mode:

encryption_decryption_example.py
f = open('test.txt', 'rb')
message = f.read()
f.close()

Encrypt it, write it to a file, then decrypt when needed.

encryption_decryption_example.py
f = open('test.encrypted', 'wb')
f.write(encrypted)
f.close()

The Key Insight

Asymmetric encryption solves the key distribution problem.

You can publish your public key anywhere. People encrypt data with it. Only you can decrypt with your private key.

That's how HTTPS, SSH, and secure messaging work.

Asymmetric Encryption & Decryption in Python