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

Automatic field encryption and decryption #129

Open
7 of 13 tasks
FabricioPatrocinio opened this issue Jan 28, 2024 · 4 comments
Open
7 of 13 tasks

Automatic field encryption and decryption #129

FabricioPatrocinio opened this issue Jan 28, 2024 · 4 comments

Comments

@FabricioPatrocinio
Copy link

FabricioPatrocinio commented Jan 28, 2024

Initial Checks

  • I have searched Google & GitHub for similar requests and couldn't find anything
  • I have read and followed the docs and still think this feature is missing

Description

Idea

Automatically perform encryption for data entry schemes with their natural values ​​and another scheme being inherited from the previous one this time for data presentation. I have more or less a good idea of ​​how to do it, I'll demonstrate it using your tool.

Example of the code I use today

from datetime import datetime
from uuid import UUID

from pydantic import EmailStr, Field, field_validator
from schemas import BaseSchema
from utils.cryptographic import decrypt, encrypt


class EnrollmentStudentRequestSchema(BaseSchema):
    name: str
    address: str
    email: EmailStr
    student_cpf: str = Field(min_length=11, max_length=14)
    student_rg: str = Field(min_length=7, max_length=9)
    grade: int
    school_name: str
    financial_responsible_name: str | None = None
    financial_responsible_cpf: str | None = None

    @field_validator(
        "email",
        "student_cpf",
        "student_rg",
        "financial_responsible_name",
        "financial_responsible_cpf",
        mode="after",
    )
    @classmethod
    def parse_encrypt(cls, value: str | EmailStr) -> str | EmailStr:
        return encrypt(value)


class EnrollmentStudentResponseSchema(EnrollmentStudentRequestSchema):
    created_at: datetime
    updated_at: datetime | None = None

    @field_validator(
        "email",
        "student_cpf",
        "student_rg",
        "financial_responsible_name",
        "financial_responsible_cpf",
        mode="before",
    )
    @classmethod
    def parse_decrypt(cls, value: str | EmailStr) -> str | EmailStr:
        return decrypt(value)

I believe that this should already be embedded in the FIELD field, using the same logic that I showed above, you could provide a new parameter, which would receive an inheritance by composition with two arguments, Field(encryptar=True or False, encryption_key=ENCRYPTION_KEY) or using a class to configure, example:

from datetime import datetime
from uuid import UUID

from pydantic import EmailStr, Field
from schemas import BaseSchema


class EnrollmentStudentRequestSchema(BaseSchema):
    name: str
    address: str
    email: EmailStr = Field(encryptar=True, encryption_key=ENCRYPTION_KEY)
    student_cpf: str = Field(min_length=11, max_length=14, encryptar=True, encryption_key=ENCRYPTION_KEY)
    student_rg: str = Field(min_length=7, max_length=9, encryptar=True, encryption_key=ENCRYPTION_KEY)
    grade: int
    school_name: str
    financial_responsible_name: str | None = Field(encryptar=True, encryption_key=ENCRYPTION_KEY)
    financial_responsible_cpf: str | None = Field(encryptar=True, encryption_key=ENCRYPTION_KEY)

class EnrollmentStudentResponseSchema(EnrollmentStudentRequestSchema):
    email: EmailStr = Field(encryptar=False, encryption_key=ENCRYPTION_KEY)
    student_cpf: str = Field(min_length=11, max_length=14, encryptar=False, encryption_key=ENCRYPTION_KEY)
    student_rg: str = Field(min_length=7, max_length=9, encryptar=False, encryption_key=ENCRYPTION_KEY)
    financial_responsible_name: str | None = Field(encryptar=False, encryption_key=ENCRYPTION_KEY)
    financial_responsible_cpf: str | None = Field(encryptar=False, encryption_key=ENCRYPTION_KEY)
    created_at: datetime
    updated_at: datetime | None = None

# ----------------------------- OR -----------------------------

class EnrollmentStudentRequestSchema(BaseSchema):
    name: str
    address: str
    email: EmailStr
    student_cpf: str = Field(min_length=11, max_length=14)
    student_rg: str = Field(min_length=7, max_length=9)
    grade: int
    school_name: str
    financial_responsible_name: str | None = None
    financial_responsible_cpf: str | None = None

    class Config:
        encryptar_list = [
            "email",
            "student_cpf",
            "student_rg",
            "financial_responsible_name",
            "financial_responsible_cpf",
        ] # Defaul null
        encryptar = True
        encryption_key = ENCRYPTION_KEY # If there are values ​​in the config above, this must be mandatory
    

class EnrollmentStudentResponseSchema(EnrollmentStudentRequestSchema):
    created_at: datetime
    updated_at: datetime | None = None

    class Config:
        decrypt = True

I prefer using field ;) But they could make both available!

I hope you understand the idea, I can try to implement something like that one day and contribute with you! :)

My cryptographic functions

from cryptography.fernet import Fernet
from settings import settings

cipher_suite = Fernet(settings.ENCRYPTION_KEY.encode())


def encrypt(value: str) -> str:
    v = cipher_suite.encrypt(value.encode())

    return v.decode()


def decrypt(value: str) -> str:
    v = cipher_suite.decrypt(value.encode())

    return v.decode()

Affected Components

@greydoubt
Copy link

This sounds cool!

@Viicos
Copy link
Member

Viicos commented Jan 29, 2024

Probably too specific to be included in Pydantic, especially in the Field signature. Perhaps in pydantic-extra-types?

@FabricioPatrocinio
Copy link
Author

We could even receive the function itself instead of encryption_key, so everyone would use their chosen encryption lib. My point is that it should be as simple as possible to do end-to-end data encryption

@sydney-runkle
Copy link
Member

Probably too specific to be included in Pydantic, especially in the Field signature. Perhaps in pydantic-extra-types?

I like @Viicos suggestion here. Moving this issue to pydantic-extra-types.

@sydney-runkle sydney-runkle transferred this issue from pydantic/pydantic Jan 29, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

4 participants