Unlock RSA Encryption and Digital Signatures in Python – Complete Guide

This article explains RSA asymmetric encryption, key pair generation, encryption/decryption limits, digital signing and verification, and provides a full Python implementation using PyCryptodome, complete with code snippets, base64 handling, and practical usage tips.

Python Crawling & Data Mining
Python Crawling & Data Mining
Python Crawling & Data Mining
Unlock RSA Encryption and Digital Signatures in Python – Complete Guide

Introduction

Encryption transforms plaintext into ciphertext using a key; decryption reverses the process. In many internet scenarios data is transmitted without encryption, exposing it to theft. RSA provides asymmetric encryption where a public key encrypts data and a private key decrypts it, enabling secure transmission and authentication.

RSA Algorithm Overview

Symmetric encryption uses the same key for both operations, while asymmetric encryption (RSA) uses a public key for encryption and a private key for decryption. A sender S obtains the receiver R’s public key, encrypts the message, and R decrypts it with its private key. Because only the private key can decrypt, eavesdroppers cannot read the data.

In a man‑in‑the‑middle scenario, an attacker could replace R’s public key, so digital signatures are required to verify the sender’s identity. The signing process hashes the message (with a salt), encrypts the hash with the sender’s private key, and the receiver verifies the signature using the sender’s public key.

Key Points for Implementing RSA in Python

1. Install the cryptographic library: pip install pycryptodome.

2. Convert between str and bytes using encode() and decode(). RSA operations require bytes data.

3. Generate a key pair (recommended 2048 bits). The maximum encryptable block size is key_length/8 − 11 bytes (245 bytes for a 2048‑bit key).

from Crypto import Random
from Crypto.PublicKey import RSA

# Pseudo‑random generator
random_gen = Random.new().read

# Generate RSA key pair (2048 bits)
rsa = RSA.generate(2048, random_gen)

private_pem = rsa.exportKey()
with open('private.pem', 'wb') as f:
    f.write(private_pem)

public_pem = rsa.publickey().exportKey()
with open('public.pem', 'wb') as f:
    f.write(public_pem)

4. Base64 encoding is used to transmit binary data as ASCII. The urlsafe_b64encode and urlsafe_b64decode helpers replace ‘+’/‘/’ with ‘‑’/‘_’ for URL safety.

Python RSA Encryption, Decryption, Signing and Verification Class

The following RSACipher class wraps the full workflow, supporting chunked encryption of long strings (including Chinese characters), decryption, signing, and verification.

from Crypto import Random
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_v1_5 as Cipher_PKCS1_v1_5
import base64
from Crypto.Signature import PKCS1_v1_5 as Sig_pk
from Crypto.Hash import SHA

class RSACipher():
    """RSA encryption, decryption, signing, and verification utilities."""

    def read_xml(self, xmlfile):
        """Read plaintext from an XML file."""
        with open(xmlfile, 'r', encoding="utf-8") as file:
            xmlstr = file.read()
            print(xmlstr)
        return xmlstr

    def encrypt_file(self, encrypt_file):
        """Read encrypted binary data from a file."""
        with open(encrypt_file, 'rb') as f:
            message = f.read()
        return message

    def Encrypt(self, message, publicKeyfile, out_file):
        """Encrypt a plaintext string using a public key.
        Returns base64‑encoded ciphertext.
        """
        with open(publicKeyfile, 'r') as f:
            publicKey = f.read()
        pubKey = RSA.importKey(publicKey)
        cipher = Cipher_PKCS1_v1_5.new(pubKey)
        message = message.encode()
        length = len(message)
        default_length = 245  # max block size for 2048‑bit key
        offset = 0
        res = bytes()
        while length - offset > 0:
            if length - offset > default_length:
                _res = cipher.encrypt(message[offset:offset + default_length])
            else:
                _res = cipher.encrypt(message[offset:])
            offset += default_length
            res += _res
        encrypt_text = base64.b64encode(res)
        with open(out_file, 'wb') as f_w:
            f_w.write(encrypt_text)
        return encrypt_text

    def Decrypt(self, message, privateKeyfile, out_file):
        """Decrypt base64‑encoded ciphertext using a private key.
        Returns the original plaintext string.
        """
        with open(privateKeyfile, 'r') as f:
            privateKey = f.read()
        rsaKey = RSA.importKey(privateKey)
        cipher = Cipher_PKCS1_v1_5.new(rsaKey)
        randomGenerator = Random.new().read
        message = base64.b64decode(message.decode())
        res = []
        for i in range(0, len(message), 256):
            res.append(cipher.decrypt(message[i:i + 256], randomGenerator))
        plainText = b"".join(res).decode()
        print(plainText)
        with open(out_file, 'w', encoding='utf-8') as f_w:
            f_w.write(plainText)
        return plainText

    def sign(self, message, private_sign_file):
        """Create a digital signature for a message using a private key.
        Returns a base64‑encoded signature string.
        """
        with open(private_sign_file, 'r') as f:
            private_sign = f.read()
        message = message.encode()
        private_key = RSA.importKey(private_sign)
        hash_value = SHA.new(message)
        signer = Sig_pk.new(private_key)
        signature = signer.sign(hash_value)
        result = base64.b64encode(signature).decode()
        return result

    def verify(self, message, public_sign_file, signature):
        """Verify a base64‑encoded signature using the sender's public key.
        Returns True if the signature is valid.
        """
        with open(public_sign_file, 'r') as f:
            public_sign = f.read()
        signature = base64.b64decode(signature)
        public_key = RSA.importKey(public_sign)
        hash_value = SHA.new(message.encode())
        verifier = Sig_pk.new(public_key)
        return verifier.verify(hash_value, signature)

if __name__ == '__main__':
    rsacipher = RSACipher()
    xmlfile = r'new1.xml'
    message = rsacipher.read_xml(xmlfile)
    encryptFile = "encrypt.txt"
    publicKeyfile = "rsa.pub"
    encrypt_text = rsacipher.Encrypt(message, publicKeyfile, encryptFile)
    print('Encrypted:
%s' % encrypt_text)
    private_sign_file = "private.pem"
    signature = rsacipher.sign(message, private_sign_file)
    print('Signature:
%s' % signature)
    decryptFile = "deencrypt.txt"
    privateFile = "rsa.key"
    decrypt_text = rsacipher.Decrypt(encrypt_text, privateFile, decryptFile)
    print('Decrypted:
%s' % decrypt_text)
    public_sign_file = "public.pem"
    result = rsacipher.verify(decrypt_text, public_sign_file, signature)
    print('Verification:
%s' % result)

The class handles chunked encryption/decryption to accommodate data larger than the RSA block size, automatically encodes/decodes with Base64, and ensures that the original message string is preserved after signing and verification.

Supporting Diagrams

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

RSAencryptiondigital signaturecryptographyBase64PyCryptodome
Python Crawling & Data Mining
Written by

Python Crawling & Data Mining

Life's short, I code in Python. This channel shares Python web crawling, data mining, analysis, processing, visualization, automated testing, DevOps, big data, AI, cloud computing, machine learning tools, resources, news, technical articles, tutorial videos and learning materials. Join us!

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.