Skip to content

How Decryption Works with GnuPG Discord

ibnaleem edited this page Dec 19, 2023 · 2 revisions

GnuPG Discord does not store or collect private keys. Instead, you are mandated to provide your private key and passphrase for signatures and decryption. When you run /decrypt or /sign, you are met with a Discord TextInput Modal.

Since most Discord bots are not open-sourced, Discord has placed a warning for users to not share their passwords or sensitive information. This includes private keys and passphrases. Fortuately, GnuPG Discord is completely open sourced, and the handling of this form can be viewed inside the decryption cog.

class PrivateKeyModal(Modal, title="Decrypt Using Private Key"):
    private_key = TextInput(style=discord.TextStyle.long,label="Private Key in ASCII Format (never stored)",required=True, placeholder="ASCII format only")
    passphrase = TextInput(style=discord.TextStyle.long,label="Passphrase (never stored)",required=True,placeholder="Passphrase to unlock private key")
    message = TextInput(style=discord.TextStyle.long,label="Encrypted Message",required=True,placeholder="ASCII format only")

a class is created called PrivateKeyModal which inherits from discord.ui.Modal. This is essential for creating the Modal. private_key, passphrase and message are instances of the TextInput() class which creates text fields for users to input text inside of.

async def on_submit(self, interaction: Interaction):
    decrypted_text = self.decrypt_text(private_key=self.private_key.value,passphrase=self.passphrase.value,message=self.message.value,)
    await interaction.user.send(decrypted_text)
    await interaction.response.send_message(f"{interaction.user.mention} Check your DMs", ephemeral=True)

A user does not submit anything until they trigger the on_submit() method of the PrivateKeyModal(Modal) class. This is done by clicking the "Submit" button in the Modal. To retreive the private key, passphrase, and encrypted message, the .value attribute on the private_key, passphrase and message instances is used. These values are sent to the decrypt_text() method:

def decrypt_text(self, private_key: str, passphrase: str, message: str) -> str:
    gpg = gnupg.GPG()
    try:
        import_result = gpg.import_keys(private_key)
        key_fingerprint = import_result.fingerprints[0]
        decrypted_data = gpg.decrypt(message, passphrase=passphrase, fingerprint=key_fingerprint)
        if decrypted_data.ok:
            decrypted_text = decrypted_data.data.decode("utf-8")
        else:
            decrypted_text = f"Decryption failed: {decrypted_data.status}"

    except Exception as e:
        decrypted_text = f"Error during decryption: {str(e)}"

    gpg.delete_keys(key_fingerprint, secret=True)

    return decrypted_text

The private key is imported and the passphrase is used to unlock it for decryption. After the decrypted_text holds a value, the private key is deleted using gpg.delete_keys(key_fingerprint, secret=True). GnuPG Discord cannot retrieve your private key after the on_submit() has finished executing because private_key, passphrase and message instances are stored in random access memory (RAM) and the memory is allocated to another program once on_submit() has finished executing.

This is the same procedure that is used for signing messages.

Clone this wiki locally