Encrypting links between registered users and their sensitive data
I want to make it impractical to link the users to their sensitive data without their passwords – even with a full access to the database.
Furthermore, if a user has multiple pieces of sensitive data, I also want to avoid linking the different pieces together
Based on the comments and some searching, I have updated the question.
I found a few similar questions but none seem to give the details I'm looking for, or have slightly different prerequisites (f.ex. data sharing).
So, in addition to registering website users and managing them with standard CRUD operations, I'm storing some possibly sensitive pieces of data from the users into the database. In that respect, this question is similar, and the answers offer guidelines for me as well. Specifically, I should not "store anything in [my] database that could be used to obtain the encryption key without knowing the password."
The client side is composed of simple html/css/js pages and, at the moment, a desktop app is not an option. I analyze the data only in groups (based on variables in the data) so that individual data are of no interest. However, I want to keep it possible for the users to see their own data and, for example, to delete the data if wanted.
What I'm thinking is generating a key for each piece of data, encrypting the key–data_id pairs into the database and decrypting them each time an unencrypted key is needed, either for storing data or when a user wants to see their data:
import json
from cryptography.fernet import Fernet
def get_key(password, data_id):
# Check that the given password is valid
if not self.check_password(password):
raise KeyError('The given password is not valid')
# Always use string representation of the data_id since json allows only string keys
data_id_str = str(data_id)
# Create a Fernet object with the password
f = Fernet(password)
# Set the encoding for the bytes <-> string conversion
encoding = 'utf-8'
# Decrypt and load into a dict the existing keys
if self.encrypted_keys:
# Ensure that the encrypted keys are in bytes for the Fernet
bytes_encrypted_keys = bytes(self.encrypted_keys)
# Decrypt the encrypted keys and transform the bytes object into a string
keys_string = f.decrypt(bytes_encrypted_key).decode(encoding)
# Load the string into a dict
keys_dict = json.loads(keys_string)
# Create an empty dict if no keys defined
else:
keys_dict = {}
# Try to get a key for the data_id
try:
key = keys_dict[data_id_str]
# The key not found
except KeyError:
# Generate a new a URL-safe 32-byte key and decode as a string into the keys_dict
key = keys_dict.setdefault(
data_id_str,
Fernet.generate_key().decode(encoding),
)
# Turn the updated keys_dict into a string
updated_keys_string = json.dumps(keys_dict)
# Encode the string into bytes for the Fernet
bytes_keys = updated_keys_string.encode(encoding)
# Encrypt the updated keys
self.encrypted_keys = f.encrypt(bytes_keys)
# Save the encrypted keys into the database
self.encrypted_keys.save()
# Return the decrypted key for the data_id
return key
Does this seem like a reasonable process? Are there some obvious flaws I'm missing? Is this overkill? Are there some other things I should consider?
I'm aware that a weak spot in this is the strength of the password. I'll try to manage that with the standard strength checks.
I also understand that an access to the server gives the ability to intercept the process and compromise the keys. Of course, if there was a way to prevent this, without a desktop app, I'd be interested. Currently, I'm hoping, at least, to secure the database.
Thank you for the advice!