admin管理员组

文章数量:1391925

I am trying to implement a system in which pictures are encrypted on my database, but when they are sent as a response to a user, this data becomes decrypted. Here is how I am encrypting...

def encrypt_validated_data(obj, image_name):
  for data_key in obj.validated_data:
    if type(obj.validated_data[data_key]) is InMemoryUploadedFile:
      file_extension = obj.validated_data[data_key].image.format.lower()
      file_extension = "." + file_extension
      file_name = image_name + file_extension
      encrypted_file = obj.cipher.encrypt(
        obj.validated_data[data_key].read()
      ).decode("utf-8")
      
      obj.validated_data[data_key] = ContentFile(
        encrypted_file,
        name = file_name
      )
    else:
      obj.validated_data[data_key] = obj.cipher.encrypt(
        obj.validated_data[data_key].encode("utf-8")
      ).decode("utf-8")
  
  return obj

This code is called whenever an object is going to be saved to the database. It encrypts all the fields, and deals with the image case. Now, I need to decrypt this data for my response. My first thought was to manipulate the to_representation on the serializer such that I could decrypt each data part. This works great for text fields, but I haven't figured out a way to do it with image fields! This is the code so far...

class ProfileSerializer(serializers.ModelSerializer):
  cipher = Fernet(settings.ENCRYPTED_FIELDS_KEY)
  profile_picture = serializers.ImageField(required=False)
  
  class Meta:
    model = YearRepresentative
    fields = [
      "user_id",
      "name",
      "profile_picture",
      "pronouns"
    ]
  
  def to_representation(self, instance):
    encrypted_data = super().to_representation(instance)
    for key in encrypted_data:
      # We keep user ids encrypted
      if key == "user_id" or encrypted_data[key] is None:
        pass
      elif key == "profile_picture":
        encrypted_picture = instance.profile_picture
        
        encrypted_file_content = encrypted_picture.read()
        
        decrypted_file_content = self.cipher.decrypt(encrypted_file_content)
        
        decrypted_picture = ContentFile(
          decrypted_file_content,
          name = encrypted_picture.name
        )
        encrypted_data[key] = decrypted_picture 
      else:
        encrypted_field = encrypted_data[key]
        encrypted_data[key] = self.cipher.decrypt(encrypted_field).decode("utf-8")
        
    return encrypted_data

When I call encrypted_data[key] = decrypted_picture, I get the error message...

'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte

Unicode error hint
The string that could not be encoded/decoded was: �PNG

This makes sense, but I'm not sure how to solve it exactly. What I am looking to ultimately achieve with this implementation too is that the decrypted image is only available on API calls - that is, it isn't saved to storage in a place where it can get accessed. Is that possible?

How should I go about decrypting an image and then getting it in a format in which it can be returned as a response? Thank you for any help!

I am trying to implement a system in which pictures are encrypted on my database, but when they are sent as a response to a user, this data becomes decrypted. Here is how I am encrypting...

def encrypt_validated_data(obj, image_name):
  for data_key in obj.validated_data:
    if type(obj.validated_data[data_key]) is InMemoryUploadedFile:
      file_extension = obj.validated_data[data_key].image.format.lower()
      file_extension = "." + file_extension
      file_name = image_name + file_extension
      encrypted_file = obj.cipher.encrypt(
        obj.validated_data[data_key].read()
      ).decode("utf-8")
      
      obj.validated_data[data_key] = ContentFile(
        encrypted_file,
        name = file_name
      )
    else:
      obj.validated_data[data_key] = obj.cipher.encrypt(
        obj.validated_data[data_key].encode("utf-8")
      ).decode("utf-8")
  
  return obj

This code is called whenever an object is going to be saved to the database. It encrypts all the fields, and deals with the image case. Now, I need to decrypt this data for my response. My first thought was to manipulate the to_representation on the serializer such that I could decrypt each data part. This works great for text fields, but I haven't figured out a way to do it with image fields! This is the code so far...

class ProfileSerializer(serializers.ModelSerializer):
  cipher = Fernet(settings.ENCRYPTED_FIELDS_KEY)
  profile_picture = serializers.ImageField(required=False)
  
  class Meta:
    model = YearRepresentative
    fields = [
      "user_id",
      "name",
      "profile_picture",
      "pronouns"
    ]
  
  def to_representation(self, instance):
    encrypted_data = super().to_representation(instance)
    for key in encrypted_data:
      # We keep user ids encrypted
      if key == "user_id" or encrypted_data[key] is None:
        pass
      elif key == "profile_picture":
        encrypted_picture = instance.profile_picture
        
        encrypted_file_content = encrypted_picture.read()
        
        decrypted_file_content = self.cipher.decrypt(encrypted_file_content)
        
        decrypted_picture = ContentFile(
          decrypted_file_content,
          name = encrypted_picture.name
        )
        encrypted_data[key] = decrypted_picture 
      else:
        encrypted_field = encrypted_data[key]
        encrypted_data[key] = self.cipher.decrypt(encrypted_field).decode("utf-8")
        
    return encrypted_data

When I call encrypted_data[key] = decrypted_picture, I get the error message...

'utf-8' codec can't decode byte 0x89 in position 0: invalid start byte

Unicode error hint
The string that could not be encoded/decoded was: �PNG

This makes sense, but I'm not sure how to solve it exactly. What I am looking to ultimately achieve with this implementation too is that the decrypted image is only available on API calls - that is, it isn't saved to storage in a place where it can get accessed. Is that possible?

How should I go about decrypting an image and then getting it in a format in which it can be returned as a response? Thank you for any help!

Share Improve this question asked Mar 12 at 11:37 fiocottifiocotti 752 silver badges7 bronze badges
Add a comment  | 

1 Answer 1

Reset to default 1

It looks like you have the issue because self.cipher.decrypt(encrypted_file_content) returns raw binary data, but you’re trying to assign it directly to encrypted_data[key], which is expected to be a serializable object.

The key point to solve it:

  1. The decrypted image needs to be properly formatted before returning it in the API response

  2. You need to return a URL or a file-like object that DRF can handle

My suggestion is to modify the to_representation method:

class ProfileSerializer(serializers.ModelSerializer):
    # ...

    def to_representation(self, instance):
        encrypted_data = super().to_representation(instance)

        for key in encrypted_data:
            if key == "user_id" or encrypted_data[key] is None:
                pass
            elif key == "profile_picture":
                encrypted_picture = instance.profile_picture
                encrypted_file_content = encrypted_picture.read()
                decrypted_file_content = self.cipher.decrypt(encrypted_file_content)

                # Convert decrypted binary data to Base64 string for API response
                encrypted_data[key] = f"data:image/png;base64,{base64.b64encode(decrypted_file_content).decode()}"
            else:
                encrypted_field = encrypted_data[key]
                encrypted_data[key] = self.cipher.decrypt(encrypted_field).decode("utf-8")

        return encrypted_data

This should solve it, since it onverts the decrypted image to a Base64-encoded string for embedding in the API response, ensuring it remains accessible only via API without being stored.

本文标签: Decrypting image fields for Django REST API responsesStack Overflow