Dutch
English
cybersecurity
cryptography

PGP encryption. Practical examples

Zhassulan Zhussupov
08 April, 2023

What is PGP

What is OpenPGP?

OpenPGP (Pretty Good Privacy) is an open encryption and digital signature standard based on Phil Zimmermann's PGP software. OpenPGP defines the format of encrypted messages, including packet format, algorithms, and protocols for key management.

OpenPGP provides end-to-end encryption, which ensures that only the intended recipient(s) of a message can read it, and not even the email service provider or other intermediaries. OpenPGP also supports digital signatures, which enable the recipient to verify the sender's identity and ensure the integrity of the message during transmission.

OpenPGP is widely used for secure communication, including email encryption and file encryption, and is supported by a number of email clients, such as Thunderbird, Apple Mail, and Microsoft Outlook, as well as a number of file encryption software applications. It is used by individuals, businesses, and organizations around the world to safeguard sensitive information and guarantee the privacy and security of their communications.

GPG

GPG (GNU Privacy Guard) is a free and open-source implementation of the OpenPGP (Pretty Good Privacy) encryption and signature standard. It is a piece of cryptographic software that enables users to encrypt and sign messages and files, ensuring the secrecy and authenticity of the data.

GPG is utilized for secure communication, such as email and file encryption. It can be used to encrypt messages and files so that only the intended recipient(s) can read them, as well as to digitally sign messages and files to ensure that they were not altered during transmission.

GPG uses a public-key encryption system, which entails the generation of two keys: a public key that can be freely shared with anyone, and a private key that is kept secret and used to decrypt messages and files encrypted with the public key.

Globally, individuals, businesses, and organizations use GPG to safeguard sensitive data and ensure the privacy and security of their communications.

Digital Signatures

PGP provides authentication and integrity verification for messages.  

The latter is used to determine whether a message has been altered since it was sent (the message integrity property), while the former is used to determine whether the message was actually sent by the individual or organization claiming to be its sender. (a digital signature).  

Because the message's content is encrypted, any modifications to the message will prevent decryption with the correct key. Using either RSA or DSA, the originator creates a digital signature for the message using PGP. To accomplish this,  

PGP computes a hash (also known as a message digest) from the plaintext and then generates the digital signature using the sender's private key and the resulting hash.      

Sending confidential messages  

PGP can be used to send confidential messages: it employs a hybrid cryptosystem that combines symmetric-key and public-key encryption.  

The message is encrypted with a symmetric encryption algorithm that requires a sender-generated symmetric key. The single-use symmetric key is also known as a session key. The recipient is sent the message and its session key.  

The session key must be sent to the recipient so they can decrypt the message, but it is encrypted with the recipient's public key to secure it during transmission.  

Only the recipient's private key can decrypt the session key and use it to decrypt the message symmetrically.

Practical example 1

According to documentation:

"The gnupg module allows Python programs to make use of the functionality provided by the GNU Privacy Guard (abbreviated GPG or GnuPG). Using this module, Python programs can encrypt and decrypt data, digitally sign documents and verify digital signatures, manage (generate, list and delete) encryption keys, using Public Key Infrastructure (PKI) encryption technology based on OpenPGP."

img

Let's say we have mykeys.asc.
Here's a simple implementation of PGP encryption using the gnupg module in Python:

import gnupg

def encrypt_message(key_file, message):
    gpg = gnupg.GPG()
    key = gpg.import_keys_file(key_file)
    encrypted_data = gpg.encrypt(
        message,
        recipients=['alice.bob@example.com'],
        always_trust=True
    )
    return str(encrypted_data)

key_file = 'mykeys.asc'
message = 'Welcome to websec blog!'
encrypted_message = encrypt_message(key_file, message)
print(encrypted_message)

As you can see, in this implementation, the gnupg module is used to generate a GPG object that can be used to import the recipient's key from a file. We then encrypt the message with the recipient's public key using the gpg.encrypt method.

To use this code, you will need to have GnuPG installed on your system. For example, in Debian-based systems, run:

sudo apt install gpg

and the gnupg Python module installed. You will also need to have the recipient's key saved in a file (in this example, we're using a file named mykeys.asc).

img

img

Finally, run our script:

python3 start.py

img

Practical example 2. PGP key generation

Let's go to add function to generate PGP-key (keygen.py):

import gnupg

def generate_key(key_type, key_length, name_email, passphrase=None):
    gpg = gnupg.GPG()
    input_data = gpg.gen_key_input(
        key_type=key_type,
        key_length=key_length,
        name_email=name_email,
        passphrase=passphrase
    )
    key = gpg.gen_key(input_data)

    ascii_armored_public_keys = gpg.export_keys(key.fingerprint)
    ascii_armored_private_keys = gpg.export_keys(
        keyids=key.fingerprint,
        secret=True,
        passphrase=passphrase,
    )

    with open(f'{key.fingerprint}.asc', 'w') as f:
        f.write(ascii_armored_public_keys)
        f.write(ascii_armored_private_keys)

    return key.fingerprint

key_type = 'RSA'
key_length = 2048
name_email = 'Alice Bob <alice.bob@example.com>'
passphrase = 'mysecretpassphrase'
fingerprint = generate_key(key_type, key_length, name_email, passphrase)
print(f'key fingerprint: {fingerprint}')

The generate_key function generates a public and private key pair using the gnupg module. It takes as input the key type, key length, name and email of the key owner, and an optional passphrase for the private key. It returns the fingerprint of the generated key.

Demo

Run our script:

keygen.py

img

img

As you can see, everything is OK. When you run the code, it will generate a public and private key pair and save them to the current directory.

Practical example 3. Message encryption

Here is an another example. Let's go to encrypt message. First of all, generating public and private key pair from previous example:

def generate_key(key_type, key_length, name_email, passphrase=None):
    gpg = gnupg.GPG()
    input_data = gpg.gen_key_input(
        key_type=key_type,
        key_length=key_length,
        name_email=name_email,
        passphrase=passphrase
    )
    key = gpg.gen_key(input_data)

    ascii_armored_public_keys = gpg.export_keys(key.fingerprint)
    ascii_armored_private_keys = gpg.export_keys(
        keyids=key.fingerprint,
        secret=True,
        passphrase=passphrase,
    )

    with open(f'{key.fingerprint}.asc', 'w') as f:
        f.write(ascii_armored_public_keys)
        f.write(ascii_armored_private_keys)

    return key.fingerprint

Encrypt message function:

def encrypt_message(message, recipients):
    gpg = gnupg.GPG()
    encrypted_data = gpg.encrypt(
        message,
        recipients=recipients,
        always_trust=True
    )
    return str(encrypted_data)

The encrypt_message function takes as input the message to be encrypted as a string and the fingerprint of the recipient's key. It uses the gnupg module to encrypt the message using the recipient's key, and returns the encrypted message as a string.

Then, just add main logic:

key_type = 'RSA'
key_length = 2048
name_email = 'Alice Bob <alice.bob@example.com>'
passphrase = 'mysecretpassphrase'

# generate public and private key pair
fingerprint = generate_key(key_type, key_length, name_email, passphrase)
print(f'key fingerprint: {fingerprint}')

# encrypt message with generated public key
message = 'Hello, world! Meow-meow!! Welcome to websec cybersecurity blog!'
recipients = [fingerprint]
encrypted_message = encrypt_message(message, recipients)
print(f'encrypted message: {encrypted_message}')

So, final code is looks like (encrypt-msg.py):

import gnupg
import os

def generate_key(key_type, key_length, name_email, passphrase=None):
    gpg = gnupg.GPG()
    input_data = gpg.gen_key_input(
        key_type=key_type,
        key_length=key_length,
        name_email=name_email,
        passphrase=passphrase
    )
    key = gpg.gen_key(input_data)

    ascii_armored_public_keys = gpg.export_keys(key.fingerprint)
    ascii_armored_private_keys = gpg.export_keys(
        keyids=key.fingerprint,
        secret=True,
        passphrase=passphrase,
    )

    with open(f'{key.fingerprint}.asc', 'w') as f:
        f.write(ascii_armored_public_keys)
        f.write(ascii_armored_private_keys)

    return key.fingerprint

def encrypt_message(message, recipients):
    gpg = gnupg.GPG()
    encrypted_data = gpg.encrypt(
        message,
        recipients=recipients,
        always_trust=True
    )
    return str(encrypted_data)

key_type = 'RSA'
key_length = 2048
name_email = 'Alice Bob <alice.bob@example.com>'
passphrase = 'mysecretpassphrase'

# generate public and private key pair
fingerprint = generate_key(key_type, key_length, name_email, passphrase)
print(f'key fingerprint: {fingerprint}')

# encrypt message with generated public key
message = 'Hello, world! Meow-meow!! Welcome to websec cybersecurity blog!'
recipients = [fingerprint]
encrypted_message = encrypt_message(message, recipients)
print(f'encrypted message: {encrypted_message}')

As you can see, the logic is pretty simple.

Demo

Ok, let me show everything in action.

python3 encrypt-msg.py

img

Practical example 4. File encryption

What about file encryption?

First of all, add new function for file encrypting:

def encrypt_file(plaintext_file, recipients):
    gpg = gnupg.GPG()
    with open(plaintext_file, 'rb') as f:
        status = gpg.encrypt_file(
            f, recipients=recipients,
            output='encrypted.gpg',
            always_trust=True
        )
    print(status.ok)
    print(status.status)
    print(status.stderr)
    return status

The encrypt_file function takes as input the path to the file to be encrypted and a list of the fingerprints of the recipients' public keys. It uses the gnupg module to encrypt the file using the recipients' public keys, and saves the encrypted data to a file with a .gpg extension.

Then, just replace message encryption logic to file encryption:

key_type = 'RSA'
key_length = 2048
name_email = 'Alice Bob <alice.bob@example.com>'
passphrase = 'mysecretpassphrase'

# generate public and private key pair
fingerprint = generate_key(key_type, key_length, name_email, passphrase)
print(f'key fingerprint: {fingerprint}')

# encrypt file with generated public key
recipients = [fingerprint]
encrypted_message = encrypt_file("plain.txt", recipients)
print('file encrypted successfully.')

Final version is looks like this encrypt-file.py:

import gnupg
import os

def generate_key(key_type, key_length, name_email, passphrase=None):
    gpg = gnupg.GPG()
    input_data = gpg.gen_key_input(
        key_type=key_type,
        key_length=key_length,
        name_email=name_email,
        passphrase=passphrase
    )
    key = gpg.gen_key(input_data)

    ascii_armored_public_keys = gpg.export_keys(key.fingerprint)
    ascii_armored_private_keys = gpg.export_keys(
        keyids=key.fingerprint,
        secret=True,
        passphrase=passphrase,
    )

    with open(f'{key.fingerprint}.asc', 'w') as f:
        f.write(ascii_armored_public_keys)
        f.write(ascii_armored_private_keys)

    return key.fingerprint

def encrypt_file(plaintext_file, recipients):
    gpg = gnupg.GPG()
    with open(plaintext_file, 'rb') as f:
        status = gpg.encrypt_file(
            f, recipients=recipients,
            output='encrypted.gpg',
            always_trust=True
        )
    print(status.ok)
    print(status.status)
    print(status.stderr)
    return status

key_type = 'RSA'
key_length = 2048
name_email = 'Alice Bob <alice.bob@example.com>'
passphrase = 'mysecretpassphrase'

# generate public and private key pair
fingerprint = generate_key(key_type, key_length, name_email, passphrase)
print(f'key fingerprint: {fingerprint}')

# encrypt file with generated public key
recipients = [fingerprint]
encrypted_message = encrypt_file("plain.txt", recipients)
print('file encrypted successfully.')

Demo

Let me show this logic in action. Let's say we have a plaintext file:

img

Then, run our python script:

python3 encrypt-file.py

img

img

Note that in the examples above, the public and private keys are written to the same file:

with open(f'{key.fingerprint}.asc', 'w') as f:
    f.write(ascii_armored_public_keys)
    f.write(ascii_armored_private_keys)

So, the good practice is to write the private key in a separate file. Something like this:

with open(f'{key.fingerprint}.pub', 'w') as f:
    f.write(ascii_armored_public_keys)

with open(f'{key.fingerprint}.asc', 'w') as f:
    f.write(ascii_armored_private_keys)

You can use our open source solution https://github.com/websecnl/WebSec-PGP-Suite which is licensed under the GNU General Public License v3.0:

img

References

RFC4880
Python Wrapper for GnuPG
https://github.com/websecnl/WebSec-PGP-Suite

Authored By
Zhassulan Zhussupov

Cybersecurity enthusiast | Author | Speaker | CTF player | R&D Engineer | Jiu-Jitsu Practicioner

Deel met de wereld!

Beveiligingsbehoeften?

Bent u er echt zeker van dat uw organisatie veilig is?

Bij WebSec helpen we u deze vraag te beantwoorden door geavanceerde beveiligingsbeoordelingen uit te voeren.

Wil je meer weten? Plan een gesprek in met een van onze experts.

Afspraak Inplannen
Authored By
Zhassulan Zhussupov

Cybersecurity enthusiast | Author | Speaker | CTF player | R&D Engineer | Jiu-Jitsu Practicioner