Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for password protected public key encryption private keys #89

Open
klall opened this issue Dec 18, 2018 · 7 comments
Open

Comments

@klall
Copy link

klall commented Dec 18, 2018

The setting PGCRYPTO_KEY is used in PGPSymmetricKeyFieldMixin (

class PGPSymmetricKeyFieldMixin(PGPMixin):
)

but not PGPPublicKeyFieldMixin

class PGPPublicKeyFieldMixin(PGPMixin):

I was unable to use a public/private key with a passphrase with a TextPGPPublicKeyField. I regenerated a public/private key without a passphrase it worked fine.

Does PGPPublicKeyFieldMixin need to be updated to support PGCRYPTO_KEY?

Thanks

@peterfarrell
Copy link
Collaborator

PGPPublicKey fields use public key encryption (PUBLIC_PGP_KEY / PRIVATE_PGP_KEY) and PGPSymmetricKey fields use symmetric key encryption (PGCRYPTO_KEY). If you want to use symmetric key encryption (PGCRYPTO_KEY), you would need to use TextPGPSymmetricKeyField instead of TextPGPPublicKeyField. You can find the chart of supported fields here:

https://github.com/incuna/django-pgcrypto-fields#django-model-field-equivalents

We do not currently support public key encryption with a passphrase. We will welcome a PR. Please be sure to include tests.

@peterfarrell
Copy link
Collaborator

@klall If you want to make a PR, the documentation for pgp_public_decrypt() in PGCRYPTO is here:

https://www.postgresql.org/docs/9.5/pgcrypto.html

You'd have to figure out how to add in the psw parameter.

@peterfarrell peterfarrell changed the title Question: How to use password with PGP Public Key Add support for password protected public key encryption private keys Feb 1, 2019
@ab-smith
Copy link

ab-smith commented Aug 4, 2020

Hi, is this still open?

@peterfarrell
Copy link
Collaborator

peterfarrell commented Aug 6, 2020 via email

@jmp
Copy link

jmp commented Jun 18, 2021

I've attempted to solve this in #364. Any comments/suggestions there would be welcome.

@some1ataplace
Copy link

some1ataplace commented Mar 27, 2023

To add support for password-protected private keys for public key encryption in django-pgcrypto-fields, you can modify the PGPPublicKeyFieldMixin class to accept an additional parameter for the passphrase.

Here's an example implementation:

from pgcrypto import PGPKey
from pgcrypto.fields import PGPPublicKeyField
from pgcrypto.mixins import PGPMixin


class PGPPublicKeyFieldMixin(PGPMixin):

    def init(self, args, **kwargs):
        self.passphrase = kwargs.pop('passphrase', None)
        super().init(args, **kwargs)

    def to_python(self, value):
        """
        Deserialize the value from the database.
        """
        if value is None:
            return None

        key = PGPKey.from_blob(value)
        if self.passphrase is not None:
            key.decrypt(self.passphrase)

        return key

    def get_prep_value(self, value):
        """
        Serialize the value to the database.
        """
        if isinstance(value, str):
            try:
                value = PGPKey.from_blob(value)
                if self.passphrase is not None:
                    value.decrypt(self.passphrase)
            except ValueError:
raise ValueError("Invalid PGP key data")
            except Exception as e:
                raise ValueError(f"Invalid PGP key data: {e}")
        elif not isinstance(value, PGPKey) and value is not None:
            raise ValueError("Value must be a PGPKey instance or None")

        if value is not None and self.passphrase is not None:
            value.encrypt(self.passphrase)

        return super().get_prep_value(value)

In this implementation, the PGPPublicKeyFieldMixin class inherits from the PGPMixin class, which handles the encryption and decryption of the PGP key. The init method is modified to accept an additional passphrase argument, which is stored in the instance variable self.passphrase.

The to_python method deserializes the value from the database and decrypts the PGP key using the passphrase, if one was provided. The get_prep_value method serializes the value to the database and encrypts the PGP key using the passphrase, if one was provided.

With this implementation, you can use the PGPPublicKeyField in the same way as before, with added support for password-protected private keys.


To add support for password protected public key encryption private keys, you can follow these steps:

  1. First, you need to modify the PGPPublicKeyFieldMixin class in pgcrypto/mixins.py to accept an additional passphrase parameter. This can be done by adding a new attribute to the class, like so:
class PGPPublicKeyFieldMixin(PGPMixin):
    passphrase = models.CharField(max_length=255, blank=True, default='')
  1. Next, update the _encrypt method within the PGPPublicKeyFieldMixin class to use the proper passphrase instead of the default value:
def _encrypt(self, value):
    if self.passphrase:
        query = "pgp_pub_encrypt(%s, dearmor(%s), %s)"
        return query % (value, self._get_setting('PUBLIC_PGP_KEY'), self.passphrase)
    else:
        query = "pgp_pub_encrypt(%s, dearmor(%s))"
        return query % (value, self._get_setting('PUBLIC_PGP_KEY'))
  1. Similarly, update the _decrypt method within the PGPPublicKeyFieldMixin class to use the passphrase if it's set:
def _decrypt(self, field):
    if self.passphrase:
        query = "pgp_pub_decrypt(%s, dearmor(%s), %s)"
        return query % (field, self._get_setting('PRIVATE_PGP_KEY'), self.passphrase)
    else:
        query = "pgp_pub_decrypt(%s, dearmor(%s))"
        return query % (field, self._get_setting('PRIVATE_PGP_KEY'))
  1. Now you can configure your Django model to use this new passphrase protected TextPGPPublicKeyField:
from pgcrypto.fields import TextPGPPublicKeyField

class MyModel(models.Model):
    encrypted_text = TextPGPPublicKeyField(passphrase='your_passphrase')

By making these changes, the PGPPublicKeyFieldMixin class would now support password protected public key encryption private keys. Make sure to test the functionality thoroughly to ensure proper operation.

@peterfarrell
Copy link
Collaborator

@some1ataplace Thanks for the in depth comment. PRs are welcome however setting a passphrase on the field (which is configuration) is less than optimal.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

Successfully merging a pull request may close this issue.

5 participants