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 python-gnupg pycryptodome. 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.
I've broken this down into three separate files.
I've used the PGPy library to do this.
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))
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-----
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-----
"