← Back to blog
RSA Encryption & Decryption In Python: Key Creation, Storage, Algorithm, and Forward Secrecy

RSA Encryption & Decryption In Python: Key Creation, Storage, Algorithm, and Forward Secrecy

authors photo
Written by Dante Lex
Sunday, April 24th 2022

At Onboardbase, we combine TLS (HTTPS) with RSA as an extra layer of security to prevent person-in–the-middle attacks. 80% of websites use HTTPS, according to W3Tech. But what if you need to send information over a non-secure network? How do you prevent network administrators or hackers from reading your HTTP or email traffic and retrieving sensitive data? RSA encryption & decryption is one way to go about it.

In this article, you’ll find a practical guide on RSA encryption you can use in your project using Python―purposes, libraries, best practices, and pitfalls to watch out for.

Result

What’s RSA

RSA (Rivest–Shamir–Adleman) is an encryption algorithm used to securely transmit data over a communication channel.

Say you want to create an instant messaging app for example. Privacy is a key requirement―you don’t want anybody to be able to monitor HTTP requests and find out what’s being said. RSA will allow the receiver and the sender to communicate without having to fear leaking secrets, no matter the channel being used―be it a public Wi-fi network or your friend’s 3G hotspot.

Why RSA

RSA presents several benefits as an encryption method.

First, you cannot build reliable software solutions without proper data encryption. RSA is key to guaranteeing the security of your users, and understanding basic algorithms like RSA encryption helps prevent data breaches: 47% of data breaches are caused by negligence, and 37% of engineers have experienced a breach in their career!

RSA is an asymmetric encryption method, meaning it uses a public key for encryption and a private key for decryption: anyone can send encrypted messages but only the receiver will be able to decrypt them. The TLS protocol used to enable HTTPS websites makes use of similar asymmetric encryption methods, for example. This separation is a great thing because symmetric algorithms like AES rely on a single private key, making them harder to secure since you need to share the secret key to allow someone else to send you an encrypted message.

RSA encryption is slower to compute than AES and is limited to a few bytes of data, but it can be used to securely transmit short secrets, keys, and credentials. More importantly, RSA is a simple way to implement Forward Secrecy, as you will later read in this article. With forward secrecy, you create temporary encryption keys to minimize the impact of a security breach: even if an encryption key gets leaked, only a tiny portion of the system will be compromised―making your website much less attractive to potential attackers.

How To Use RSA In Python

An RSA encryption system can be broken down into 3 parts:

  • Key pair creation - generating strong keys to use
  • Key storage & retrieval - storing and retrieving the key pairs in a secure vault
  • Encryption/decryption - transmitting encrypted data over a network and decrypting it

In this article, you are going to use PKCS1 OAEP, a modern RSA implementation. Let’s dive into each step in detail.

1. Generating the RSA key pairs

First, we create a public encryption key and a private decryption key. Just like regular passwords, these keys need to be strong, meaning it won’t be realistically possible to brute force them with a software program in a reasonable amount of time. To achieve this, keys are randomly generated using methods provided by approved cryptography libraries, like pycryptodome in Python.

And the longer the keys, the harder they will be to break. For RSA, you want your keys to be at least 1024 bits in length:

from Crypto.PublicKey import RSA

key_pair = RSA.generate(1024)

public_key = key_pair.publickey().exportKey()
private_key = key_pair.exportKey()

print(public_key)
print(private_key)

An RSA public key will look like this:

-----BEGIN PUBLIC KEY-----
MIGfMA0GCSqGSIb3DQEBAQUAA4GNADCBiQKBgQCynEkwTvpiHAPYw3TgRbu0vAT3
N2H3ANhDP6V5ZiDZyVEgqTdp4MuIA+YNrBVu1vlhWq8tBLWRB+t1M0n1Gtqd3Rqt
dx9/ShXKsOAw42CJnDiWqQkxG0HBHm6FoM7NOADOM88bM49wLztePtOp7ZgNb5YJ
RmVU+qyd8ICd3bFUyQIDAQAB
-----END PUBLIC KEY-----

While an RSA private key will be similar to that:

-----BEGIN RSA PRIVATE KEY-----
MIICXgIBAAKBgQCynEkwTvpiHAPYw3TgRbu0vAT3N2H3ANhDP6V5ZiDZyVEgqTdp
4MuIA+YNrBVu1vlhWq8tBLWRB+t1M0n1Gtqd3Rqtdx9/ShXKsOAw42CJnDiWqQkx
G0HBHm6FoM7NOADOM88bM49wLztePtOp7ZgNb5YJRmVU+qyd8ICd3bFUyQIDAQAB
AoGBAK1ntFrVYpubzVRGtxMCQLHAlSWavF69kDkB8tqA/8XRgRUvlGAT4iZkepat
ioV46tbM3yPa3Zg30y6PI+92KeXTu01jb/h2YwwcjGHN2I/4WXTyBGR4X7zq7cQ4
W6iQiZQW3yhRL3TT1Kni6FXc7eD/HkJ7/D1LqhUmiMlWfAABAkEAtuqmrDQfgcvN
GnutnrwkAo2gvfsS39WMy9GIHvPsn24R+8RTK8z4c4n1uZrJti2gXwmKE9CG/RBw
Y1YmPJR0yQJBAPn5MMIeY0sdCukmIsTQ0xUrfhcEnNzyfWbmlzLPkE6pbtmmqDYk
k1ARWkNVUYs8vny45Kjxt49xRtH6rFsF4AECQQCL489OOZqzAHM+wsGYeVis3zXE
taTMrJw+MGQDEBeyIlL8kjvtbpfAfQ0BQqDrg993b2s7Fip2lcDuJ6+cewkJAkEA
sz4EwVzq1AR6O14WFkyq3wY1vqmXFTNnyi5UgqGlF1KU4sqG2XP2kbYvpBJ8izu+
V05438+bUObn4k894XLgAQJAWbhGSsiYP4xUm28FwyOsiBTvIx+cK8GJilP8BRy/
Wd7W32fPLwpUerFGlJqEb+nIR42OCZ61C3BSVyY5XpsviQ==
-----END RSA PRIVATE KEY-----

2. Storing the RSA keys

RSA keys are multi-line strings of characters, usually stored in .pem files. But the simplest and most computationally efficient way to store a key is to serialize it and store it in an environment variable in your program, using a .env file for example:

PK_RSA = "-----BEGIN RSA PUBLIC KEY-----\n etc"
SK_RSA = "-----BEGIN RSA PRIVATE KEY-----\n etc"

.env variables can be referenced in your Python program using the native os library:

import os

private_key = os.getenv("SK_RSA")
public_key = os.getenv("PK_RSA")

print(private_key)
print(public_key)

But as we previously mentioned, protecting the keys as best as possible is vital since leaking them would allow attackers to break the encryptions. While having your key in a .env file appears as a quick fix to the storage problem, using a secret manager like Onboardbase is much more secure. With Onboardbase, you can inject RSA keys in software programs as environment variables in your CI/CD process in just 3 commands, removing the need to directly share keys or hardcode them in files or environment variables.

After defining your key in your account dashboard―it’s super easy to define multi-line environment variables―you can use Onboardbase CLI’s setup command to leverage its features in your own project in a few seconds:

Result
onboardbase setup

The command generates an onboardbase.yml file used to access the online vault:

setup:
	project: rsa
	environment: development

All you have left to do is to run the build command to integrate RSA keys from Onboardbase at runtime:

onboardbase build --command="python3 rsa.py"

If you need the public key to be accessible worldwide, storing it in a .pem file and exposing it on your web server is still the best way to fetch it from client programs:

import requests

response = requests.get('https://yourwebsite.com/public_key_RSA.pem')
public_key = response.text

3. RSA Encryption

RSA only requires the public key to encrypt a short message:

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
import requests

response = requests.get('https://yourwebsite.com/public_key_RSA.pem')
public_key = response.text
key = RSA.importKey(public_key)

message = b'A message to secure'
print(message)

cipher = PKCS1_OAEP.new(key)
ciphertext = cipher.encrypt(message)
print(ciphertext)

The result, known as ciphertext, can then be transmitted over any type of channel without risking any information leak.

4. RSA Decryption

For the inverse operation, you’ll need to use your private key:

from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
import os

private_key = os.getenv("SK_RSA")

key = RSA.importKey(private_key)
cipher = PKCS1_OAEP.new(key)

# we assume the cipher text has been sent and stored in a variable
decrypted_message = cipher.decrypt(ciphertext)
print(decrypted_message)

Then run the following command to inject your RSA private key at runtime from Onboardbase:

onboardbase build --command="python3 decrypt.py"

Forward Secrecy With RSA

Now, the big problem with RSA is you can’t use it to encrypt messages longer than its key size: if your RSA key is 1024 bits long, your messages won’t exceed 1024 bits.

But RSA is perfect to encrypt short app secrets, keys, and credentials. This is what you can leverage in hybrid encryption systems balancing the strengths of both symmetric and asymmetric encryption methods to implement Forward Secrecy. Forward Secrecy (FS) is a security best practice consisting in automatically changing the keys used to encrypt data. Each temporary key (aka session key) encrypts a single message. This way, even if the key is leaked, it won’t compromise the whole system.

In the following code snippet, we use RSA to encrypt and send a randomized session key. AES-256 then uses the session key to encrypt any sort of message. The ciphertext and the encrypted session key are then sent to the receiver, and the receiver just decrypts the key and the message:

from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes
from Crypto.Cipher import AES, PKCS1_OAEP
import os
import requests

header = b'header'
data = b'secret data of any length'

# ENCRYPTION

## Get the public RSA key
response = requests.get('https://yourwebsite.com/public_key_RSA.pem')
public_key = response.text
public_key = RSA.importKey(public_key)

## Session key creation & encryption
session_key = get_random_bytes(16)
cipher_rsa = PKCS1_OAEP.new(public_key)
encrypted_session_key = cipher_rsa.encrypt(session_key)

## AES Encryption
cipher_aes = AES.new(session_key, AES.MODE_GCM)
cipher.update(header)
ciphertext, tag = cipher_aes.encrypt_and_digest(data)
nonce = cipher.nonce

# DECRYPTION

## Get the private RSA key

private_key = os.getenv("SK_RSA")
private_key = RSA.importKey(private_key)

## Session key decryption
cipher_rsa = PKCS1_OAEP.new(private_key)
session_key = cipher_rsa.decrypt(encypted_session_key)

## AES decryption
cipher_aes = AES.new(session_key, AES.MODE_EAX, nonce)
data = cipher_aes.decrypt_and_verify(ciphertext, tag)

Thanks to this system, the AES key will never be transmitted in plain view and we avoid a security pitfall, hence the term Forward Secrecy. We use the GCM flavor of AES (AES Galois Counter Mode) with 256-bit keys as it guarantees the encrypted data hasn’t been modified in transit.

Note that while this encryption schema works, it is still a weak approach to forward secrecy. There are more modern ways to go about it, as we discuss in an article on Perfect Forward Secrecy using the ECC encryption system. The main difference is that with ECC there isn’t even a need to send keys over a network, they can just be calculated on the go thanks to the mathematical properties of ECC functions. It makes ECC much more secure than forward secrecy with RSA.

Store Your RSA Key Pairs With Onboardbase

Result

This is the minimum you need to know to use RSA encryption to secure your applications, so don’t wait any longer to get started and subscribe to Onboardbase for free.

Onboardbase makes it easy to store encryption keys while programmatically changing and using them: it’s your best ally to keep your RSA key pairs safe. A dashboard centralizes public and private keys for the whole dev team, and a command-line interface is available to integrate them into any software project. Each one of your keys is encrypted with AES-CBC-256 and the web app is served over HTTPS with perfect forward secrecy. It’s so easy and secure to use you won’t pick another tool for the job: register today and never leak your app secrets ever again.

Subscribe to our newsletter

The latest news, articles, features and resources of Onboardbase, sent to your inbox weekly