incuna / django-pgcrypto-fields

Transparent field level encryption for Django using the pgcrypto postgresql extension.
BSD 2-Clause "Simplified" License
229 stars 49 forks source link

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

Open klall opened 5 years ago

klall commented 5 years ago

The setting PGCRYPTO_KEY is used in PGPSymmetricKeyFieldMixin (https://github.com/incuna/django-pgcrypto-fields/blob/429b9a8ce6971d8d547ea8be6f8234ed4d82673c/pgcrypto/mixins.py#L143)

but not PGPPublicKeyFieldMixin https://github.com/incuna/django-pgcrypto-fields/blob/429b9a8ce6971d8d547ea8be6f8234ed4d82673c/pgcrypto/mixins.py#L128

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 commented 5 years ago

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 commented 5 years ago

@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.

ab-smith commented 3 years ago

Hi, is this still open?

peterfarrell commented 3 years ago

Yes, PR are welcome.

On Tue, Aug 4, 2020, 12:29 PM Abder notifications@github.com wrote:

Hi, is this still open?

— You are receiving this because you commented. Reply to this email directly, view it on GitHub https://github.com/incuna/django-pgcrypto-fields/issues/89#issuecomment-668755657, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAVZRZAYFZUHB4Q73LSGAJ3R7BHRFANCNFSM4GLAODBQ .

jmp commented 3 years ago

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

some1ataplace commented 1 year ago

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 commented 1 year ago

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