pythonencryptioncryptographybase64gnupg

How can encrypt\encode (Base64) for a generated key including secret message with 2nd public data key in Python?


I'm trying to encrypt the a new generated key using another public key belongs to my friend recipient_public_key, then encode the final output in Base64. This process also can be done step by step here using tools like Kleopatra after installed Gpg4win, however I'm not interested in using tools.

Task:

Generate an RSA key pair with a 1024-bit key size, using your username. Consider the below message, and sign the message without encrypting it. In a text file, include the following: your public key, the message, and the Base64-encoded signature of the message. Then, encrypt this file using another\recipient's public key, encode the final output in Base64, and print it.

Message to sign: This is my secret message

At first, I've generated an RSA key pair with a 1024-bit key size, using my name as the username using available Python packages for this like . I need to generate and sign the message without encrypting it as below:

I have tried the following approach which ends of the following error:

ValueError: Plaintext is too long.

# !pip install pycryptodome
from Crypto.PublicKey import RSA
from Crypto.Cipher import PKCS1_OAEP
from Crypto.Signature import pkcs1_15
from Crypto.Hash import SHA256
import base64
import pgpy # Import pgpy

# Step 1: Generate RSA key pair
key = RSA.generate(1024)
private_key = key.export_key()
public_key = key.publickey().export_key()

# Step 2: Save your public key for submission
with open("my_public_key.pem", "wb") as f:
    f.write(public_key)

# Step 3: Prepare the message with your name
message = "This is my secret messgae"
message_bytes = message.encode()

# Step 4: Sign the message with the private key
hash_message = SHA256.new(message_bytes)
signature = pkcs1_15.new(key).sign(hash_message)

# Step 5: Base64 encode the signature
signature_base64 = base64.b64encode(signature)

# Step 6: Save the public key, message, and signature in a file
with open("final-msg.txt", "wb") as f:
    f.write(public_key + b"\n" + message_bytes + b"\n" + signature_base64)

# Step 7: Encrypt the final message with the provided public key
recipient_public_key_pem = """-----BEGIN PGP PUBLIC KEY BLOCK-----
.............
-----END PGP PUBLIC KEY BLOCK-----"""

# Step 7: Encrypt the final message with the provided public key

# **Increased key size to 2048 bits**
key = RSA.generate(2048)  
private_key = key.export_key()
public_key = key.publickey().export_key()

# Use pgpy to import the PGP key
recipient_key, _ = pgpy.PGPKey.from_blob(recipient_public_key_pem) 

# Re-initialize cipher_rsa with the new key
cipher_rsa = PKCS1_OAEP.new(key) 

# Encrypt final-msg.txt with the recipient's public key
with open("final-msg.txt", "rb") as f:
    final_msg = f.read()
encrypted_message = cipher_rsa.encrypt(final_msg)

# Step 8: Base64 encode the encrypted message
encrypted_message_base64 = base64.b64encode(encrypted_message)

# Step 9: Save the base64 encoded encrypted message
with open("final_msg_base64.txt.gpg", "wb") as f:
    f.write(encrypted_message_base64)

print("Encryption completed. The Base64-encoded encrypted message is saved in 'final_msg_base64.txt.gpg'")

In the spirit of DRY, I tried 2nd approach:

# !pip install python-gnupg
import gnupg
import base64

# Initialize GPG
gpg = gnupg.GPG()

# 1. Generate an RSA Key Pair
input_data = gpg.gen_key_input(
    key_type="RSA",
    key_length=1024,
    name_real="Alice Bob",             # Replace with your name
    name_email="Alice.Bob@gmail.com",  # Replace with your email
    expire_date=0  # Key does not expire
)
key = gpg.gen_key(input_data)
print(f"Generated Key Fingerprint: {key.fingerprint}")

# 2. Create the message
message = f"This is my secret message"
with open("msg.txt", "w") as f:
    f.write(message)

# 3. Sign the message
#signed_data = gpg.sign(message, default_key=key.fingerprint, detach=True)
signed_data = gpg.sign(message, keyid=key.fingerprint, detach=True) 

# Write the signature data to a file in binary mode
with open("msg.sig", "wb") as f:  # Open in binary write mode ("wb")
    f.write(signed_data.data)  # Write the bytes directly

# 4. Base64 encode the signature
with open("msg.sig", "rb") as sig_file:
    signature_base64 = base64.b64encode(sig_file.read()).decode("utf-8")
    with open("msg-base64.sig", "w") as encoded_sig_file:
        encoded_sig_file.write(signature_base64)

# 5. Export your public key
public_key = gpg.export_keys(key.fingerprint)
with open("my_public_key.asc", "w") as pubkey_file:
    pubkey_file.write(public_key)

# 6. Concatenate the public key, message, and Base64-encoded signature
with open("final-msg.txt", "w") as final_file:
    final_file.write(public_key)
    final_file.write("\n" + message + "\n")
    final_file.write(signature_base64)

# 7. Import recipient's public key
recipient_public_key ="""-----BEGIN PGP PUBLIC KEY BLOCK-----

..........
-----END PGP PUBLIC KEY BLOCK-----"""
# Replace with the actual provided public key
import_result = gpg.import_keys(recipient_public_key)
print("Imported Recipient's Public Key:", import_result.fingerprints)

# 8. Encrypt the final message with the recipient's public key
with open("final-msg.txt", "rb") as final_msg_file:
    # Use encrypt instead of encrypt_file
    encrypted_data = gpg.encrypt(
        final_msg_file.read(),  # Read the file content
        import_result.fingerprints,  # Pass recipients as positional argument
        output="final-msg.txt.gpg" 
    )
    if encrypted_data.ok:
        print("Message encrypted successfully.")
    else:
        print(f"Encryption failed: {encrypted_data.status}") # Print the error status
        print(encrypted_data.stderr)                         # Print any error details to help with debugging
        
# Only proceed to base64 encoding if encryption was successful
if encrypted_data.ok:        
    # 9. Base64 encode the encrypted file
    with open("final-msg.txt.gpg", "rb") as encrypted_file:
        encrypted_base64 = base64.b64encode(encrypted_file.read()).decode("utf-8")
        with open("final_msg_base64.txt.gpg", "w") as encoded_encrypted_file:
            encoded_encrypted_file.write(encrypted_base64)

    # Output the Base64 encoded result
    print("Base64 Encoded Encrypted Message:")
    print(encrypted_base64)
else:
    print("Skipping base64 encoding due to encryption failure.")
WARNING:gnupg:potential problem: ERROR: key_generate 83918950
WARNING:gnupg:gpg returned a non-zero error code: 2
WARNING:gnupg:potential problem: FAILURE: sign 17
WARNING:gnupg:gpg returned a non-zero error code: 2
WARNING:gnupg:gpg returned a non-zero error code: 2
WARNING:gnupg:gpg returned a non-zero error code: 2
Generated Key Fingerprint: 
Imported Recipient's Public Key: ['1AB8693860852A6B0EE7DD81B8F979BA0A99A039']
Encryption failed: invalid recipient
[GNUPG:] KEY_CONSIDERED 1AB8693860852A6B0EE7DD81B8F979BA0A99A039 0
gpg: 8D7830FB995B0A91: There is no assurance this key belongs to the named user
[GNUPG:] INV_RECP 10 1AB8693860852A6B0EE7DD81B8F979BA0A99A039
[GNUPG:] FAILURE encrypt 53
gpg: [stdin]: encryption failed: Unusable public key

Skipping base64 encoding due to encryption failure.

Any idea how I could manage to encrypt (base64) generated key within the secret message using target recipient_public_key? so hopefully recipient could use his public_key and read the secret message.


Solution

  • I've broken this down into three separate files.

    I've used the PGPy library to do this.

    Friend's keys

    For the purposes of testing I've created key files that we will assume are your friends:

    from pathlib import Path
    import tempfile
    
    import pgpy
    from pgpy.constants import (
        PubKeyAlgorithm,
        KeyFlags,
        HashAlgorithm,
        SymmetricKeyAlgorithm,
        CompressionAlgorithm,
    )
    
    # file locations
    tmp_dir = Path(tempfile.gettempdir())
    recipient_private_key_file = Path.joinpath(tmp_dir, "friend_private.pem")
    recipient_public_key_file = Path.joinpath(tmp_dir, "friend_receiver.pem")
    
    # Generate a primary key using RSA
    key = pgpy.PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 4096)
    
    # we now have some key material, but our new key doesn't have a user ID yet, and therefore is not yet usable!
    uid = pgpy.PGPUID.new("A. N. Other", comment="John Doe", email="another@example.come")
    
    # now we must add the new user id to the key. We'll need to specify all of our preferences at this point
    # because PGPy doesn't have any built-in key preference defaults at this time
    # this example is similar to GnuPG 2.1.x defaults, with no expiration or preferred keyserver
    key.add_uid(
        uid,
        usage={KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage},
        hashes=[
            HashAlgorithm.SHA256,
            HashAlgorithm.SHA384,
            HashAlgorithm.SHA512,
            HashAlgorithm.SHA224,
        ],
        ciphers=[
            SymmetricKeyAlgorithm.AES256,
            SymmetricKeyAlgorithm.AES192,
            SymmetricKeyAlgorithm.AES128,
        ],
        compression=[
            CompressionAlgorithm.ZLIB,
            CompressionAlgorithm.BZ2,
            CompressionAlgorithm.ZIP,
            CompressionAlgorithm.Uncompressed,
        ],
    )
    
    
    # ASCII armored private key
    # print(str(key))
    recipient_private_key_file.write_text(str(key))
    
    # ASCII armored public key
    # print(str(key.pubkey))
    recipient_public_key_file.write_text(str(key.pubkey))
    
    

    Create own key and sign message

    I understood that you wanted to create your own public key and use that to sign the message which includes the public key.

    You then use your friend's public key to encrypt the message.

    from pathlib import Path
    import tempfile
    
    from pgpy import (
        PGPMessage,
        PGPKey,
        PGPUID,
    )
    from pgpy.constants import (
        PubKeyAlgorithm,
        KeyFlags,
        HashAlgorithm,
        SymmetricKeyAlgorithm,
        CompressionAlgorithm,
    )
    import warnings
    
    # Hide pgpy deprecation warnings
    warnings.filterwarnings("ignore")
    
    # file locations
    tmp_dir = Path(tempfile.gettempdir())
    local_private_key_file = Path.joinpath(tmp_dir, "my_private.pem")
    local_public_key_file = Path.joinpath(tmp_dir, "my_receiver.pem")
    recipient_public_key_file = Path.joinpath(tmp_dir, "friend_receiver.pem")
    encrypted_message_file = Path.joinpath(tmp_dir, "encrypted_data.bin")
    data = "This is my secret message"
    
    
    # Generate local keys
    key = PGPKey.new(PubKeyAlgorithm.RSAEncryptOrSign, 1024)
    uid = PGPUID.new(
        "Abraham Lincoln", comment="Honest Abe", email="abraham.lincoln@whitehouse.gov"
    )
    key.add_uid(
        uid,
        usage={KeyFlags.Sign, KeyFlags.EncryptCommunications, KeyFlags.EncryptStorage},
        hashes=[
            HashAlgorithm.SHA256,
            HashAlgorithm.SHA384,
            HashAlgorithm.SHA512,
            HashAlgorithm.SHA224,
        ],
        ciphers=[
            SymmetricKeyAlgorithm.AES256,
            SymmetricKeyAlgorithm.AES192,
            SymmetricKeyAlgorithm.AES128,
        ],
        compression=[
            CompressionAlgorithm.ZLIB,
            CompressionAlgorithm.BZ2,
            CompressionAlgorithm.ZIP,
            CompressionAlgorithm.Uncompressed,
        ],
    )
    # Save keys to file
    local_private_key_file.write_text(str(key))
    local_public_key_file.write_text(str(key.pubkey))
    
    # Create cleartext message and sign it
    data_with_key = data + "\n" + str(key.pubkey)
    ct_message = PGPMessage.new(data_with_key, cleartext=True, encoding="UTF-8")
    ct_message |= key.sign(ct_message)
    str_msg = str(ct_message)
    print("Message before encrypting:\n", str_msg)
    # Get recipients public key
    recipient_public_key_pem, _ = PGPKey.from_file(recipient_public_key_file)
    # Encrypt message with key and save to file
    msg = PGPMessage.new(str_msg)
    encrypted_message = recipient_public_key_pem.encrypt(msg)
    # Write encrypted message to file
    encrypted_message_file.write_text(str(encrypted_message))
    
    

    The message before encryption looked like:

    $ python3 gen_msg.py 
    Message before encrypting:
     -----BEGIN PGP SIGNED MESSAGE-----
    Hash: SHA256
    
    This is my secret message
    - -----BEGIN PGP PUBLIC KEY BLOCK-----
    
    xo0EZzGVFwEEAPKqOqP3Osa9sXRItLfGbxMdR88lwpklp8p39iz9dOVvOQ24EeEg
    WbrQEb5UZ56YEGukmroc0h616RLM/+b67+3W+5D6IZo6xcxFGyQhzPWmI5rRXKMq
    uoZfCly60jsOdVtd3rca3JIRZYTqAgUedEGZvfuevuMZ5ao1cIzoZhV1ABEBAAHN
    PUFicmFoYW0gTGluY29sbiAoSG9uZXN0IEFiZSkgPGFicmFoYW0ubGluY29sbkB3
    aGl0ZWhvdXNlLmdvdj7CwAoEEwEIADQFAmcxlRcCGw4ECwkIBwUVCAkKCwUWAgMB
    AAIeARYhBMwPgRN60DXWDIwtgSGIUbetWaR+AAoJECGIUbetWaR+B30D/13jogya
    KfUstSwDPPjgVt3GORqkPtO59t22cDgWms6ipuevjUZv5xh6WY7vyWfGqOoG6WNF
    yhHj+9L1DlDexKaGj6rutUgtdhoQjMfwoPVD9MuZIlvmcLaeUyfGDW+Gum4gSuVu
    QAJiT3L+Nlek3K0vZToJfeTUcxA2PP4QDnOg
    =SA1g
    - -----END PGP PUBLIC KEY BLOCK-----
    
    -----BEGIN PGP SIGNATURE-----
    Charset: utf-8
    
    wrMEAQEIAB0FAmcxlRcWIQTMD4ETetA11gyMLYEhiFG3rVmkfgAKCRAhiFG3rVmk
    fkyLBACi2KKo2BU4Y8eY0xjrHfGsUk+V3aRbkihaSS2uQuIOrWTCdBlfsg7sS+BG
    3gIvEHBJGx90UnPdivJssVTQMndD5TLdKJza6iqiR68tjYhc9yobKNiyJQa9TZHO
    BcEqUqXPR1vuDIC0hxynScvJBBzYHolLe9hZCgDTiGu13VrlnQ==
    =AqNK
    -----END PGP SIGNATURE-----
    
    

    And the generated encrypted_data.bin file contains:

    $ more /tmp/encrypted_data.bin
    -----BEGIN PGP MESSAGE-----
    
    wcFMA5vXs3tk6F7QAQ/6Aml3FUVwiJdZXdTluyLC8fmzm081qTNWNyubnQLTANbp
    O77rcS8HDdMWyHU0MG9f2CkMR02EVUdLNS939860ZZ3DhMWe9jZNxZjs5q9Yhlyy
    bLHBT5VQoXkuaj4DpB5dvoI6f06XEA3SVwmRGRiOb76tkYauWhz9fCbVTodLT60+
    8IVIRHCtW52UIP6KIJqQgOnPa5P2MKp3CeQJ4KGEIABKQGXzZGk+0X/ZjqjPG8bK
    pQPhWUZRcRXbvcSn3J+0qyu6ehIsMyHoLewvIXcdfjqhdVg7ds2BTlY8DDl/lw1Q
    js/FYRQpyZ2bJShRjgknzGVY2UIf920lIEEmFZ7QPtzwivy3EyKmn0Pkl9oDFYah
    shGPK5jmufBaRxejU8kHO8XRCvf04UJzGRmjxVRdnNWua7uC+Ukger59nBCSetQC
    A+HMsSroT6qd1sqYnuF8qvlIlntqVmDXtRI1lUH1IWoBnmXyLzP3x3P7OOwBIZWy
    o6bRkgDgjSJMaJ4QyiFvPrb02xswTUwWTTieSchEZ31qz/Z+MpYJlptLWx0BxyWV
    iNBZddfEmYGAUbUbKYvYEA5eKAu/KcpnIdNZVeOkYy5nm86ZbPeUCIjMvo05RCZz
    LbY4PpAL4K8YHCVRmk59ZGFsb95PeTU7+GVewbGdf/yp8pIMO2e3KQrF+byfAPLS
    wmoBCqF27aE03yYpMMi9Za/m9LFPM042PfDOP0Ur7mfw3RcFTBKgYrmt/sHaGv5Q
    OJk5p+u8Q2WGDYtbuzzwkcASXuG3mS4DUWepINuBbgACYs/ZZcuWq5iTrKyjmjBI
    W9upnhuIooEUzkOipI+Qh93uZFZFIY4IUo0QgXY4DQKHegLnRk/Zp1ZfQo3cXzHO
    SLXUtClVWJbOhk5ogU4HNVAV/RGJcVJGlBsQrwBBvSnW2l4BPNTqtWe74+1oOT2M
    w7RS1Hxk245WmJj6DlKp8JJXBeEViMt+WmkGhIUtxCCKBxg2pupURY4r8R1Zh8BP
    O/CSA69SXBU9XQXROgcjeG3Fe6L8n0F8l/d9oAa8u/RRdVfse1BGI6v5zLiQDqDr
    YD148MfIwnkW6JqJY45DonrzArSxf8VxB60xSOpGJeu3jgrphY9Q1wv2DNBOCMxC
    pesf7GuJg1d+BHB3ezCVV1Syal6o28GNAQ1EmRRrpilSXl+FHtjxerJbrP92cocl
    Rje/ZJl+WLmk/LDzmssv+LhJqFVykQiWuWoTaQQeL0PePuCJ+d7jTMMj9oVWQu4w
    rqMI5Zab58SrWcyr1JHSYpZUMpmt49uP6VJ5h3rqVxnvy48G5vS7EYaiTQoYBwie
    ox7CRJ8f9gnxot0KHuutvAkUBL0Oj1P16DgXSvoZPv1NIMJSpPwS1VmisOJcej9a
    9ELt9wc9erZKGeL4BYty4y/Oo3MXSXpyjgVO065UP9uIfl9ZuqeiF1OL8sIaicwq
    M/o6OJ1yiyu77Dm2ekJHZF27QczSvrLvQTT06wLu0L7AcTM/fq9rKUjYF7T4UfmK
    qGrVwDbDP3FqJtFnV/6njb08FKGHplXUbUQbLo4JqNMr6w5GYfZnqU0BE38V/7lt
    v6c9qJ3Wccaxiy7HPXAucfS4HETJlHm08hpM1Bp8lqa1K4VIZeKCIEPbe404QDe0
    0e722OxzljZ2sOL2vWdIwHPoqj7v5ROjXmH0OPXYStav6b4yCt7b9FIefT96G0Kz
    OsK38fqLmJD//cjrThhmPak7BmuJPFf+Hr9CKlSZkhAqMcwwepVdd2lv56c=
    =6jOj
    -----END PGP MESSAGE-----
    
    

    Read with friend's private key

    The encrypted message can be decrypted using the friend's private key:

    from pathlib import Path
    import tempfile
    
    from pgpy import (
        PGPMessage,
        PGPKey,
    )
    import warnings
    
    warnings.filterwarnings("ignore")
    
    tmp_dir = Path(tempfile.gettempdir())
    recipient_private_key_file = Path.joinpath(tmp_dir, "friend_private.pem")
    encrypted_message_file = Path.joinpath(tmp_dir, "encrypted_data.bin")
    
    
    def extract_key_and_msg(msg_text: str) -> tuple[str, str]:
        idx = msg_text.index("\n-----BEGIN PGP PUBLIC KEY BLOCK-----")
        return msg_text[:idx], msg_text[idx:]
    
    
    # Read local key
    recipient_key, _ = PGPKey.from_file(recipient_private_key_file)
    
    ## Read from encrypted file
    encrypted_message = PGPMessage.from_file(encrypted_message_file)
    plaintext = recipient_key.decrypt(encrypted_message).message
    message_from_blob = PGPMessage.from_blob(plaintext)
    msg, sender_key_blob = extract_key_and_msg(message_from_blob.message)
    
    sender_key, _ = PGPKey.from_blob(sender_key_blob)
    
    result = sender_key.verify(message_from_blob)
    
    if result:
        print("Message verified")
    else:
        raise Exception("Houston, we have a problem")
    
    print(f'You were sent the following message:\n"{msg}"')
    print(f'Including public key:\n"{sender_key_blob}"')
    
    

    Which gives them the following output:

    $ python read_msg.py
    Message verified
    You were sent the following message:
    "This is my secret message"
    Including public key:
    "
    -----BEGIN PGP PUBLIC KEY BLOCK-----
    
    xo0EZzGVFwEEAPKqOqP3Osa9sXRItLfGbxMdR88lwpklp8p39iz9dOVvOQ24EeEg
    WbrQEb5UZ56YEGukmroc0h616RLM/+b67+3W+5D6IZo6xcxFGyQhzPWmI5rRXKMq
    uoZfCly60jsOdVtd3rca3JIRZYTqAgUedEGZvfuevuMZ5ao1cIzoZhV1ABEBAAHN
    PUFicmFoYW0gTGluY29sbiAoSG9uZXN0IEFiZSkgPGFicmFoYW0ubGluY29sbkB3
    aGl0ZWhvdXNlLmdvdj7CwAoEEwEIADQFAmcxlRcCGw4ECwkIBwUVCAkKCwUWAgMB
    AAIeARYhBMwPgRN60DXWDIwtgSGIUbetWaR+AAoJECGIUbetWaR+B30D/13jogya
    KfUstSwDPPjgVt3GORqkPtO59t22cDgWms6ipuevjUZv5xh6WY7vyWfGqOoG6WNF
    yhHj+9L1DlDexKaGj6rutUgtdhoQjMfwoPVD9MuZIlvmcLaeUyfGDW+Gum4gSuVu
    QAJiT3L+Nlek3K0vZToJfeTUcxA2PP4QDnOg
    =SA1g
    -----END PGP PUBLIC KEY BLOCK-----
    "