Python RSA-OAEP Code Example (Online Runner)

Python RSA-OAEP examples with hash selection, PEM keys, and hybrid file encryption matching the RSA tools.

Online calculator: use the site RSA tool.

Note: This snippet requires locally installed dependencies and will not run in the online runner.

Calculation method

The RSA tool uses RSA-OAEP with selectable hash functions and PEM keys. The file tool uses a hybrid scheme: AES-256-CBC encrypts the file, and the AES key is RSA-OAEP encrypted into a JSON package.

Install the dependency first: pip install pycryptodome.

Implementation notes

  • Package: pycryptodome provides RSA key handling and OAEP padding.
  • Implementation: OAEP uses the selected hash (SHA-1/256/384/512). File encryption uses a random AES-256 key + IV, then wraps the AES key using RSA-OAEP and stores everything in a JSON payload.
  • Notes: RSA-OAEP has a maximum message size (key_bytes - 2*hash_len - 2), so encrypt small payloads only. Use RSA for keys and AES for bulk data, as shown here.
python
from __future__ import annotations

import base64
import json
from pathlib import Path
from typing import Literal

from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA1, SHA256, SHA384, SHA512
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes

HashAlg = Literal["SHA-1", "SHA-256", "SHA-384", "SHA-512"]
CipherEncoding = Literal["hex", "base64"]

HASH_MAP = {
    "SHA-1": SHA1,
    "SHA-256": SHA256,
    "SHA-384": SHA384,
    "SHA-512": SHA512,
}


def _encode_ciphertext(data: bytes, encoding: CipherEncoding) -> str:
    return data.hex() if encoding == "hex" else base64.b64encode(data).decode("ascii")


def _decode_ciphertext(value: str, encoding: CipherEncoding) -> bytes:
    return bytes.fromhex(value) if encoding == "hex" else base64.b64decode(value)


def generate_rsa_keypair(key_size: int = 2048) -> tuple[str, str]:
    key = RSA.generate(key_size)
    public_pem = key.publickey().export_key().decode("ascii")
    private_pem = key.export_key().decode("ascii")
    return public_pem, private_pem


def rsa_encrypt(text: str, public_pem: str, hash_alg: HashAlg = "SHA-256", encoding: CipherEncoding = "base64") -> str:
    key = RSA.import_key(public_pem)
    cipher = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    ciphertext = cipher.encrypt(text.encode("utf-8"))
    return _encode_ciphertext(ciphertext, encoding)


def rsa_decrypt(ciphertext: str, private_pem: str, hash_alg: HashAlg = "SHA-256", encoding: CipherEncoding = "base64") -> str:
    key = RSA.import_key(private_pem)
    cipher = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    plain = cipher.decrypt(_decode_ciphertext(ciphertext, encoding))
    return plain.decode("utf-8", errors="replace")


def _pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
    pad_len = block_size - (len(data) % block_size)
    return data + bytes([pad_len] * pad_len)


def _pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes:
    if not data:
        return data
    pad_len = data[-1]
    if pad_len < 1 or pad_len > block_size:
        raise ValueError("Invalid PKCS7 padding")
    if data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("Invalid PKCS7 padding")
    return data[:-pad_len]


def rsa_encrypt_file(path: Path, public_pem: str, hash_alg: HashAlg = "SHA-256") -> str:
    data = path.read_bytes()
    aes_key = get_random_bytes(32)
    iv = get_random_bytes(16)
    aes = AES.new(aes_key, AES.MODE_CBC, iv=iv)
    ciphertext = aes.encrypt(_pkcs7_pad(data))

    key = RSA.import_key(public_pem)
    cipher_rsa = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    encrypted_key = cipher_rsa.encrypt(aes_key)

    payload = {
        "version": 1,
        "hash": hash_alg,
        "mode": "AES-256-CBC",
        "iv": iv.hex(),
        "encryptedKey": base64.b64encode(encrypted_key).decode("ascii"),
        "ciphertext": base64.b64encode(ciphertext).decode("ascii"),
        "originalName": path.name,
        "mimeType": "application/octet-stream",
    }
    return json.dumps(payload, indent=2)


def rsa_decrypt_file(package_json: str, private_pem: str, fallback_hash: HashAlg = "SHA-256") -> bytes:
    payload = json.loads(package_json)
    hash_alg = payload.get("hash") if payload.get("hash") in HASH_MAP else fallback_hash

    key = RSA.import_key(private_pem)
    cipher_rsa = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    aes_key = cipher_rsa.decrypt(base64.b64decode(payload["encryptedKey"]))

    aes = AES.new(aes_key, AES.MODE_CBC, iv=bytes.fromhex(payload["iv"]))
    plaintext = aes.decrypt(base64.b64decode(payload["ciphertext"]))
    return _pkcs7_unpad(plaintext)

# Example usage
public_pem, private_pem = generate_rsa_keypair(2048)

cipher = rsa_encrypt("hello", public_pem, hash_alg="SHA-256", encoding="base64")
print(cipher)

plain = rsa_decrypt(cipher, private_pem, hash_alg="SHA-256", encoding="base64")
print(plain)

File encryption example

python
from __future__ import annotations

import base64
import json
from pathlib import Path
import tempfile
from typing import Literal

from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA1, SHA256, SHA384, SHA512
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes

HashAlg = Literal["SHA-1", "SHA-256", "SHA-384", "SHA-512"]

HASH_MAP = {
    "SHA-1": SHA1,
    "SHA-256": SHA256,
    "SHA-384": SHA384,
    "SHA-512": SHA512,
}


def generate_rsa_keypair(key_size: int = 2048) -> tuple[str, str]:
    key = RSA.generate(key_size)
    public_pem = key.publickey().export_key().decode("ascii")
    private_pem = key.export_key().decode("ascii")
    return public_pem, private_pem


def _pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
    pad_len = block_size - (len(data) % block_size)
    return data + bytes([pad_len] * pad_len)


def _pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes:
    if not data:
        return data
    pad_len = data[-1]
    if pad_len < 1 or pad_len > block_size:
        raise ValueError("Invalid PKCS7 padding")
    if data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("Invalid PKCS7 padding")
    return data[:-pad_len]


def rsa_encrypt_file(path: Path, public_pem: str, hash_alg: HashAlg = "SHA-256") -> str:
    data = path.read_bytes()
    aes_key = get_random_bytes(32)
    iv = get_random_bytes(16)
    aes = AES.new(aes_key, AES.MODE_CBC, iv=iv)
    ciphertext = aes.encrypt(_pkcs7_pad(data))

    key = RSA.import_key(public_pem)
    cipher_rsa = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    encrypted_key = cipher_rsa.encrypt(aes_key)

    payload = {
        "version": 1,
        "hash": hash_alg,
        "mode": "AES-256-CBC",
        "iv": iv.hex(),
        "encryptedKey": base64.b64encode(encrypted_key).decode("ascii"),
        "ciphertext": base64.b64encode(ciphertext).decode("ascii"),
        "originalName": path.name,
        "mimeType": "application/octet-stream",
    }
    return json.dumps(payload, indent=2)


def rsa_decrypt_file(package_json: str, private_pem: str, fallback_hash: HashAlg = "SHA-256") -> bytes:
    payload = json.loads(package_json)
    hash_alg = payload.get("hash") if payload.get("hash") in HASH_MAP else fallback_hash

    key = RSA.import_key(private_pem)
    cipher_rsa = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    aes_key = cipher_rsa.decrypt(base64.b64decode(payload["encryptedKey"]))

    aes = AES.new(aes_key, AES.MODE_CBC, iv=bytes.fromhex(payload["iv"]))
    plaintext = aes.decrypt(base64.b64decode(payload["ciphertext"]))
    return _pkcs7_unpad(plaintext)

with tempfile.TemporaryDirectory() as temp_dir:
    sample_path = Path(temp_dir) / "sample.bin"
    sample_path.write_bytes(b"hello")
    public_pem, private_pem = generate_rsa_keypair(2048)
    package_json = rsa_encrypt_file(sample_path, public_pem, hash_alg="SHA-256")
    restored = rsa_decrypt_file(package_json, private_pem)
    print(restored)

Complete script (implementation + tests)

python
from __future__ import annotations

import base64
import json
from typing import Literal

from Crypto.Cipher import AES, PKCS1_OAEP
from Crypto.Hash import SHA1, SHA256, SHA384, SHA512
from Crypto.PublicKey import RSA
from Crypto.Random import get_random_bytes

HashAlg = Literal["SHA-1", "SHA-256", "SHA-384", "SHA-512"]
CipherEncoding = Literal["hex", "base64"]

HASH_MAP = {
    "SHA-1": SHA1,
    "SHA-256": SHA256,
    "SHA-384": SHA384,
    "SHA-512": SHA512,
}


def _encode_ciphertext(data: bytes, encoding: CipherEncoding) -> str:
    return data.hex() if encoding == "hex" else base64.b64encode(data).decode("ascii")


def _decode_ciphertext(value: str, encoding: CipherEncoding) -> bytes:
    return bytes.fromhex(value) if encoding == "hex" else base64.b64decode(value)


def generate_rsa_keypair(key_size: int = 2048) -> tuple[str, str]:
    key = RSA.generate(key_size)
    public_pem = key.publickey().export_key().decode("ascii")
    private_pem = key.export_key().decode("ascii")
    return public_pem, private_pem


def rsa_encrypt(text: str, public_pem: str, hash_alg: HashAlg = "SHA-256", encoding: CipherEncoding = "base64") -> str:
    key = RSA.import_key(public_pem)
    cipher = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    ciphertext = cipher.encrypt(text.encode("utf-8"))
    return _encode_ciphertext(ciphertext, encoding)


def rsa_decrypt(ciphertext: str, private_pem: str, hash_alg: HashAlg = "SHA-256", encoding: CipherEncoding = "base64") -> str:
    key = RSA.import_key(private_pem)
    cipher = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    plain = cipher.decrypt(_decode_ciphertext(ciphertext, encoding))
    return plain.decode("utf-8", errors="replace")


def _pkcs7_pad(data: bytes, block_size: int = 16) -> bytes:
    pad_len = block_size - (len(data) % block_size)
    return data + bytes([pad_len] * pad_len)


def _pkcs7_unpad(data: bytes, block_size: int = 16) -> bytes:
    if not data:
        return data
    pad_len = data[-1]
    if pad_len < 1 or pad_len > block_size:
        raise ValueError("Invalid PKCS7 padding")
    if data[-pad_len:] != bytes([pad_len] * pad_len):
        raise ValueError("Invalid PKCS7 padding")
    return data[:-pad_len]


def rsa_encrypt_file(path, public_pem: str, hash_alg: HashAlg = "SHA-256") -> str:
    data = path.read_bytes()
    aes_key = get_random_bytes(32)
    iv = get_random_bytes(16)
    aes = AES.new(aes_key, AES.MODE_CBC, iv=iv)
    ciphertext = aes.encrypt(_pkcs7_pad(data))

    key = RSA.import_key(public_pem)
    cipher_rsa = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    encrypted_key = cipher_rsa.encrypt(aes_key)

    payload = {
        "version": 1,
        "hash": hash_alg,
        "mode": "AES-256-CBC",
        "iv": iv.hex(),
        "encryptedKey": base64.b64encode(encrypted_key).decode("ascii"),
        "ciphertext": base64.b64encode(ciphertext).decode("ascii"),
        "originalName": path.name,
        "mimeType": "application/octet-stream",
    }
    return json.dumps(payload, indent=2)


def rsa_decrypt_file(package_json: str, private_pem: str, fallback_hash: HashAlg = "SHA-256") -> bytes:
    payload = json.loads(package_json)
    hash_alg = payload.get("hash") if payload.get("hash") in HASH_MAP else fallback_hash

    key = RSA.import_key(private_pem)
    cipher_rsa = PKCS1_OAEP.new(key, hashAlgo=HASH_MAP[hash_alg])
    aes_key = cipher_rsa.decrypt(base64.b64decode(payload["encryptedKey"]))

    aes = AES.new(aes_key, AES.MODE_CBC, iv=bytes.fromhex(payload["iv"]))
    plaintext = aes.decrypt(base64.b64decode(payload["ciphertext"]))
    return _pkcs7_unpad(plaintext)

def run_tests() -> None:
    public_pem, private_pem = generate_rsa_keypair(2048)
    cipher = rsa_encrypt("hello", public_pem, hash_alg="SHA-256", encoding="hex")
    recovered = rsa_decrypt(cipher, private_pem, hash_alg="SHA-256", encoding="hex")
    assert recovered == "hello"

    import tempfile
    from pathlib import Path

    with tempfile.TemporaryDirectory() as temp_dir:
        sample_path = Path(temp_dir) / "sample.bin"
        sample_path.write_bytes(b"hello")
        payload = rsa_encrypt_file(sample_path, public_pem, hash_alg="SHA-256")
        restored = rsa_decrypt_file(payload, private_pem)
        assert restored == b"hello"

    print("RSA tests passed")


if __name__ == "__main__":
    run_tests()