RSA is one of the most popular techniques of public key encryption. When we use RSA (public-private key encryption), one might think that it is simple – we just have to encrypt with the recipient’s public key and the decryption will happen with the recipient’s private key. However, there is a problem. RSA can only encrypt 245 bytes of data at a maximum at one time.
As a rule of thumb, you can only encrypt data as large as the RSA key length. So, if you’ve got a 4096-bit RSA key, you can only encrypt messages up to 4096 bits long. Not only that, but it’s incredibly slow. If a 2048 bit RSA key is used, then 2048/8 = 256 bytes can be encrypted at once. Why is the RSA limited to encrypt only 245 bytes of data? It is because of padding standards PKCS1 (which requires 11 bytes) and OAEP requires further padding (42 bytes or 66 bytes). There are two kinds of padding used in RSA encryption – PKCS#1v1.5 (Standard) uses 11 bytes and OAEP Padding (Modern/Recommended) uses 42 or 66 bytes. In the programs written over here, we will be using OAEP padding.
But, why use padding in RSA in the first place?
Raw RSA encryption without padding is insecure. Padding prevents attackers from guessing small or predictable messages and ensures that the same message produces a different ciphertext every time it is encrypted.
The issue with RSA
Either way, RSA has a problem. If we have a large document, then we cannot keep encrypting slowly in 245 bytes at once. The main issue with RSA is that it is computationally very expensive and time-taking. So, instead of encrypting the entire document or communication with RSA, what we instead do is:
- We encrypt with the symmetric encryption AES
- The key used in symmetric encryption is then encrypted using RSA
- This way the most important information (the symmetric key) is highly secured through public key encryption.
AES is a symmetric block cipher, and is incredibly fast. The plaintext is split into chunks called blocks, and each block is encrypted in a chain. There are different ways of doing this (also called modes of operation), and they are: ECB (Electronic Code Book), CBC (Cipher Block Chaining), and CFB (Cipher Feedback).
However, since AES is a symmetric encryption algorithm, there needs to be a symmetric key that needs to be exchanged. So, instead of encrypting the entire original message with RSA (which is extremely inefficient and clumsy), what we do is: we encrypt the AES symmetric key along with RSA and once the recipient opens the symmetric key with their own RSA private key, they now have the AES symmetric key to decrypt the original message encrypted with AES.
So, the steps are as follows:
1. Take a plain text that needs to be encrypted. This plain text is encrypted using AES symmetric encryption. The key used will be a 256 bit (32 bytes) key.
2. Remember the AES symmetric key itself is now encrypted using RSA 2048 bit key (which can encrypt up to 245 bytes – much larger capacity than the 32 bytes key it is encrypting).
So, converting the above steps into programs…
Program 1: Assume we have the AES symmetric key (256 bit or 32 bytes). This program will do the AES encryption and decryption using a 256 bit symmetric key. In the sample program, the key used is “AAAAAAAAAAAAAAAA” and this key will be encrypted in Program 2.
Program 2: Since you now have the AES symmetric key which is used. This program will do the RSA encryption and decryption of the key itself. The public-private key pair used here is generated from Program 3.
Program 3: But, to do RSA, you need public key private key generation. This program does the public key private key generation. In standard industry applications, the P and Q used in RSA is 300 digit long prime numbers. For education reasons, Program 4 is added.
Program 4: The actual public key – private key pair generation happens from P, Q, N, E, D. The prime numbers chosen are P and Q. These are 300 digit prime numbers used. E is always chosen as 65537 as it is a Fermat’s prime number and is guaranteed to be relatively prime with (P-1)*(Q-1) for any P,Q chosen. So, this program is a friendly program showing the same steps with P and Q chosen as small prime numbers unlike the 300 digit prime numbers programmatically chosen in Program 3. Public -Private pairs here cannot be used in the above RSA-AES combinations. This is only for understanding purposes.
Program 1: AESEncryption.py
import base64
from Crypto.Cipher import AES
from Crypto.Util.Padding import pad,unpad
#AES ECB mode without IV
data = ”'{text: “This is plain text that will be encrypted and decrypted using AES symmetric key. – UNICMINDS”}”’
key = ‘AAAAAAAAAAAAAAAA’ #Must Be 16 char for AES128
def encrypt(raw):
raw = pad(raw.encode(),16)
cipher = AES.new(key.encode(‘utf-8’), AES.MODE_ECB)
return base64.b64encode(cipher.encrypt(raw))
def decrypt(enc):
enc = base64.b64decode(enc)
cipher = AES.new(key.encode(‘utf-8’), AES.MODE_ECB)
return unpad(cipher.decrypt(enc),16)
encrypted = encrypt(data)
print(‘encrypted ECB Base64:’,encrypted.decode(“utf-8”, “ignore”))
encrypted=encrypted.decode(“utf-8”, “ignore”)
decrypted = decrypt(encrypted)
print(‘decrypted data: ‘,decrypted.decode(“utf-8”, “ignore”))
Program 2: RSAEncryptionOfAESKey.py
from Crypto.Cipher import PKCS1_OAEP
from Crypto.PublicKey import RSA
from base64 import b64encode
from base64 import b64decode
# working
message = b’AAAAAAAAAAAAAAAA’
key = RSA.importKey(”’—–BEGIN PUBLIC KEY—–
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA4GgqnTIcIUF9pGEEQ44e
FY1rc+7Dh4/sCPqfzauYf73gyxaFs1eAhZnVh39b/G05yCq2ztozwHBx4S3A62DT
hDGZM5ZRZCxgtKhuyR976pLwWx9Nk0vKJ3K6qz5QWFFjBmb2Q4dpHA3Yy7jiwOPj
hUqfpZEqrwoBmSdsUUMrBcPAwt9A0XH5hJcK8thkJ2q8f8wRdaSLpeWgY7JTRDxM
GFRJ6uHLTLbunRKWnvl4FWhdGFejHCdrcX27cWp5OiYHapJQ9V25kSn6X6W06g4C
RCeE0ncSnaEkaKVTjICFcEq1rWiHX29dnsJVX30he9V+gjIXgpFKOfcyD6Y2bPFp
PQIDAQAB
—–END PUBLIC KEY—–”’)
cipher = PKCS1_OAEP.new(key)
ciphertext = cipher.encrypt(message)
print(“Encrypted Text”, b64encode(ciphertext).decode(‘utf-8’))
encodedText=b64encode(ciphertext).decode(‘utf-8’)
ct = b64decode(encodedText)
ciphertext=ct
key = RSA.importKey(”’—–BEGIN PRIVATE KEY—–
MIIEowIBAAKCAQEA4GgqnTIcIUF9pGEEQ44eFY1rc+7Dh4/sCPqfzauYf73gyxaF
s1eAhZnVh39b/G05yCq2ztozwHBx4S3A62DThDGZM5ZRZCxgtKhuyR976pLwWx9N
k0vKJ3K6qz5QWFFjBmb2Q4dpHA3Yy7jiwOPjhUqfpZEqrwoBmSdsUUMrBcPAwt9A
0XH5hJcK8thkJ2q8f8wRdaSLpeWgY7JTRDxMGFRJ6uHLTLbunRKWnvl4FWhdGFej
HCdrcX27cWp5OiYHapJQ9V25kSn6X6W06g4CRCeE0ncSnaEkaKVTjICFcEq1rWiH
X29dnsJVX30he9V+gjIXgpFKOfcyD6Y2bPFpPQIDAQABAoIBAB7TKDFeGf+WcaYD
/pQyAB9pRN6QqFKleiB0nsFfZgv7/tYewqBTL3AKpMpfO/k1XrfIaEKLNgsj1vy9
rm+Wpg9VScxMhGMcdm8yaL9fQAQFiZcWum72fO8Ewy/1GA+9pDrTp1W40r8cBtDb
FWi2FQFw8fOJ+IFBQF1zjR0vewgoLNzDpjfBby8vQUvQ2WgH0Y+hNOTucTaCKmiN
CKwdx+zLNYYz4howlmDU9mVg+gOyjuoDpmghJTqPd5+C9g6HVA2W20DSij1U7uW4
4e6BwAjViqYX36d3YXB6FE5pRAhzaCbXLGjAYnBCZrMR/Zvmi3Cj5PrrPVq/agp4
6/i9Q7ECgYEA5xAr1KHDZDXkB2PjCA73vIw4HTB4rIkN2pgZ4kvW49/Ytc21k8Ex
RFZBj42pLJHCnMtNwuJhULLJphz19Ld/sr2o56YqNIoohuVgLu4uviqnZ2r1feDo
2yJ7UKNh+nWgfJZnKEgUPgTiLMXjuhDvvGYF69RDyyHUKDh2P8bbMZECgYEA+KAY
bOgxe2P43RhMrWiLLuOGShNwamZvXgkXxS0T3nYAypTBHQD4VGQciygNjGkQSAe/
yrDPoL4vnfNYqx1ETD8BCHYAtRyHHxI5wpnoTtNAPr5GY4YZDdZFD7qhG33Vd1ba
JjYqo7WxH9lmbMnICeUUdSPK3J4wn48rvfuZJu0CgYB7PKrD68sUxZFrR6EtCR6k
l3zORK34B9k1v23+vkhMnXUt8htoROAL/J1W/U0/kjZj/iLpUGhq7BCU4llkPgKD
yJgvhPZ5sz5ORER6g1q23nUOuYNZsf0/8zImHh4BwX7pNCas21TAh0ZCbwE4mhPj
Pd7mmv3Vd6N6GDMpbNw3oQKBgQCsE6o0HlS5EQ1agQn47yV9023LcT7Z9YHY9LQl
/TgGPWf4zhIGb/hv+EYlLhiKeOES6YId2Fgr6dXtHVLeQ8hUeTrOz1VYBKGkqKmf
fYRioiWSB4GSOmq2v/lWlJYS//mxukQMNGs4mXU5FO+mFdZEuu94z8gE/9upY58j
w7JRVQKBgFWGRZVDZCHfc/mNEKmdFxh3YOxjuG65AIkPL7bSt4ek2Ul9U8ZYj5/x
wf7EvKzk5L6Ahw7UHcXlIY0Ih26s7gzUMgFxssqx4JqtHTh3TPoN/EUdjbQ4tUv5
uMBwwTB/OXsvhnv9lO1CbgnBddGPnPqu8ix6uBG5ccSpNSddzKgB
—–END PRIVATE KEY—–”’)
cipher = PKCS1_OAEP.new(key)
message = cipher.decrypt(ciphertext)
print(“Decrypted text”,message)
Program 3: keygen1.py
from Crypto.PublicKey import RSA
def createPublicPrivateKey():
# Generate RSA key pair (2048 bits)
key = RSA.generate(2048)
# Export Private Key in a format Program 2 likes
private_key = key.export_key().decode(‘utf-8’)
# Export Public Key
public_key = key.publickey().export_key().decode(‘utf-8’)
return {
“publicKey”: public_key,
“privateKey”: private_key
}
if __name__ == “__main__”:
keys = createPublicPrivateKey()
print(“— COPY THIS INTO PROGRAM 2 PUBLIC KEY —“)
print(keys[“publicKey”])
print(“\n— COPY THIS INTO PROGRAM 2 PRIVATE KEY —“)
print(keys[“privateKey”])
Program 4: KeyGenWithPQ.py
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
def create_rsa_from_primes(p, q, e=65537):
# Calculate RSA components
n = p * q
phi = (p – 1) * (q – 1)
if e >= n:
raise ValueError(f”Public exponent {e} is larger than modulus {n}. Use larger primes!”)
# Calculate the private exponent d
d = pow(e, -1, phi)
# Calculate CRT coefficients (required by the library to build the key)
dmp1 = d % (p – 1)
dmq1 = d % (q – 1)
iqmp = pow(q, -1, p)
# Assemble the private key object
private_numbers = rsa.RSAPrivateNumbers(
p=p, q=q, d=d, dmp1=dmp1, dmq1=dmq1, iqmp=iqmp,
public_numbers=rsa.RSAPublicNumbers(e=e, n=n)
)
return private_numbers.private_key(), n, d
if __name__ == “__main__”:
# — INPUTS —
p = 997
q = 881
e = 65537
# Generate the key and capture the calculated math
key, n, d = create_rsa_from_primes(p, q, e)
# — EXPORT TO PEM FORMAT —
private_pem = key.private_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PrivateFormat.PKCS8,
encryption_algorithm=serialization.NoEncryption()
)
public_pem = key.public_key().public_bytes(
encoding=serialization.Encoding.PEM,
format=serialization.PublicFormat.SubjectPublicKeyInfo
)
# — PRINT EVERYTHING —
print(“=== RSA KEY COMPONENTS (THE MATH) ===”)
print(f”Prime 1 (p): {p}”)
print(f”Prime 2 (q): {q}”)
print(f”Modulus (n = p*q): {n}”)
print(f”Public Exponent (e): {e}”)
print(f”Private Exponent (d): {d}”)
print(“-” * 40)
print(“\n— RSA PRIVATE KEY (PEM) —“)
print(private_pem.decode())
print(“— RSA PUBLIC KEY (PEM) —“)
print(public_pem.decode())
Hope this is useful, thank you for reading.
You may like to read: Learning to Code at Home, The Clock Cycle of a CPU, and The Best Roblox Coding Books for Kids




