This project is a Python-based implementation of the RSA (Rivest–Shamir–Adleman) cryptosystem. It was developed as a final project for an advanced Python programming course, demonstrating the core algorithms behind asymmetric encryption from scratch.
The system can generate public/private key pairs, encrypt plaintext messages, and decrypt ciphertext.
- Key Generation: Creates RSA key pairs of arbitrary bit length (e.g., 2048, 4096).
- Encryption: Encrypts string messages into a series of ciphertext numbers.
- Decryption: Decrypts ciphertext numbers back into the original string.
- Custom Math Implementation: Implements all core algorithms to show a foundational understanding, including:
- Miller-Rabin Primality Test for finding large primes.
- Linear Congruential Generator (LCG) as a simple PRNG to find prime candidates (for academic purposes).
- Karatsuba Algorithm for fast multiplication of large integers.
- Extended Euclidean Algorithm for finding the modular multiplicative inverse (
d).
- Secure Primitives: Uses Python's
secretsmodule for cryptographically secure random number generation during primality testing. - Robust Blocking: Automatically handles message blocking and padding based on key size.
- CLI Interface: A clean, command-line interface using
argparsefor all operations.
The RSA algorithm's security relies on the practical difficulty of factoring the product of two large prime numbers (factoring n into p and q). The process is split into three phases:
This is the most complex step, where the public and private keys are created.
-
Find Primes: Two distinct, large prime numbers,
$p$ and$q$ , are found.- This implementation uses the LCG (
lcg_prng) to generate large random number candidates. - Each candidate is checked for primality using the Miller-Rabin Primality Test (
is_prime).
- This implementation uses the LCG (
-
Calculate Modulus: The modulus
$n$ is calculated by multiplying the primes:$n = p \times q$ - This implementation uses the Karatsuba Algorithm (
karatsuba_multiply) for this large multiplication. -
$n$ is used for both the public and private keys.
-
Calculate Totient: Euler's totient function,
$\phi(n)$ , is calculated.$\phi(n) = (p-1) \times (q-1)$ - This value is kept secret and is crucial for finding the private key.
-
Find Public Exponent (
$e$ ): An integer$e$ is chosen such that$1 < e < \phi(n)$ and$e$ is coprime to$\phi(n)$ (i.e.,$\text{gcd}(e, \phi(n)) = 1$ ).- This implementation uses the common standard
$e = 65537$ ($2^{16} + 1$ ).
- This implementation uses the common standard
-
Find Private Exponent (
$d$ ): The private exponent$d$ is calculated as the modular multiplicative inverse of$e$ modulo$\phi(n)$ .$d \equiv e^{-1} \pmod{\phi(n)}$ - This implementation finds
$d$ using the Extended Euclidean Algorithm (egcdandmodinv).
The Keys:
-
Public Key:
$(e, n)$ . This pair is shared publicly for encrypting messages. -
Private Key:
$(d, n)$ . This pair is kept secret for decrypting messages.
Before encryption, the string message (e.g., "Hello") must be converted into one or more integers (
- The string is encoded into
bytes(e.g., UTF-8). - These bytes are split into blocks, each small enough to ensure the resulting integer
$m$ is less than$n$ . - Each block of bytes is converted into a large integer using the
bytes_to_intfunction (which treats the bytes as a base-256 number).
With the public key
- This is performed by the
modexpofunction (modular exponentiation).
With the private key
- This also uses the efficient
modexpofunction. The recovered integer$m$ is then converted back into bytes usingint_to_bytesand decoded into a string.
This project is structured as a Python package for clean separation of concerns:
python-rsa-from-scratch/
├── .gitignore # Git ignore file
├── LICENSE # MIT license file
├── README.md # This documentation file
├── main.py # Main runnable script with the argparse CLI
└── rsa/
├── __init__.py # Initialize package
├── crypto_math.py # Core math: Miller-Rabin, Karatsuba, EGCD, etc.
├── rsa_core.py # High-level RSA logic: generate_keys, encrypt, decrypt
└── utils.py # Helper functions (e.g., prime pair generation)
This project is a command-line application.
First, generate your public and private keys. You can specify the bit size (default is 2048).
# Generate a standard 2048-bit key pair
python main.py generate
# Generate a stronger 4096-bit key pair
python main.py generate --bits 4096
# Specify custom output filenames
python main.py generate -b 2048 --pub my_public.key --priv my_private.keyThis will create two files (e.g., public.key and private.key) in your directory.
Use the encrypt command with your public key to encrypt a message.
# Encrypt a message using the default public.key
python main.py encrypt -m "Hello, this is a secret message!"
# Specify the key file and output file
python main.py encrypt -m "My secret" -k my_public.key -o secret.txtThis will create a ciphertext file (e.g., ciphertext.txt).
Use the decrypt command with your private key to read the ciphertext and reveal the original message.
# Decrypt the default ciphertext.txt using the default private.key
python main.py decrypt
# Specify the ciphertext and private key files
python main.py decrypt -c secret.txt -k my_private.keyThe decrypted message will be printed directly to your console.
$ python main.py decrypt
Decrypting ciphertext from ciphertext.txt using key private.key...
--- Decrypted Message ---
Hello, this is a secret message!
-------------------------
Feel free to connect or reach out if you have any questions!
- Maryam Rezaee
- GitHub: @msmrexe
- Email: ms.maryamrezaee@gmail.com
This project is licensed under the MIT License. See the LICENSE file for full details.