Safeguarding User Data in Django (Part 1): The Importance of Avoiding Plain Text Storage and Related Security Issues

Amir Ayat
6 min readJun 15, 2023

--

This article will delve into the significance of avoiding storing user data in plain text format and its potential security issues.

Please check the source code on GitHub.

adapted from [link]

Introduction

In today’s interconnected world, the collection and storage of user data have become essential for numerous online services. However, with great power comes great responsibility, and organizations must prioritize the security and protection of user information. One common and avoidable pitfall is storing user data in plain text format within databases. This article will delve into the significance of avoiding plain text storage and its potential security issues.

There are two main methods for safeguarding data. Data at rest encryption and data in transit encryption. Data at rest encryption involves securing data when it is stored or stationary, such as on a hard drive, database, or backup device. It ensures that even if unauthorized individuals gain access to the physical storage medium, the data remains encrypted and unreadable without the proper decryption key. On the other hand, data in transit encryption focuses on safeguarding data as it moves between different locations or devices, typically over a network. It encrypts the data during transmission, preventing unauthorized interception or eavesdropping by encrypting the content and ensuring that only authorized parties can decipher the information. While both encryption methods are vital for overall data security, data at rest encryption protects data at rest, while data in transit encryption protects data during transportation. We will discuss data-in-transit encryption in this chapter. You can find more about data-at-rest encryption in the next section.

The Problem with Plain Text Storage

Storing user data in plain text refers to saving sensitive information, such as passwords, personal identification details, and financial data, without any encryption or obfuscation. This practice leaves user information highly vulnerable to unauthorized access and exploitation by malicious actors. Without proper security measures, even a single data breach could have severe consequences for both users and the organization responsible for safeguarding the data.

Security Issues and Risks

  1. Password Vulnerability: When passwords are stored in plain text, they become easily accessible to anyone accessing the database. This means that if a breach occurs, hackers can instantly obtain and misuse users’ login credentials, potentially compromising their accounts on various platforms.
  2. Identity Theft: Plain text storage exposes personally identifiable information (PII) to significant risks. Attackers could exploit this data to commit identity theft, fraud, or other cybercrimes, causing substantial harm to users and organizations alike.
  3. Compliance Challenges: Many industries have legal and regulatory requirements for protecting user data, such as the General Data Protection Regulation (GDPR) in the European Union or the Health Insurance Portability and Accountability Act (HIPAA) in the healthcare sector. Storing user data in plain text violates these regulations and can lead to substantial penalties and reputational damage.
  4. Insider Threats: Storing sensitive data in plain text raises the risk of insider threats. Employees or individuals with privileged access to the database might misuse or leak this information, either intentionally or inadvertently. Safeguarding data through encryption can mitigate such risks by limiting access to authorized personnel.
  5. Amplified Impact of Breaches: In a data breach, the consequences are far more severe when user data is stored in plain text. The potential damage is not only limited to the compromised information but extends to all other accounts that users may have, as many individuals reuse passwords across multiple platforms.

The Solution: Secure Data Storage Practices

  1. Encryption: Employing strong encryption algorithms is crucial for safeguarding user data. Hashing algorithms like bcrypt or PBKDF2 should be used to hash passwords before storing them in the database. Encryption ensures that even if an unauthorized party gains access to the data, it remains unreadable and unusable.
  2. Salting: Adding a unique salt to each password before hashing strengthens the security further. Salting ensures that even if two users have the same password, the hashed values will differ, making it harder for attackers to crack multiple passwords simultaneously.
  3. Access Controls: Implement strict access controls and authentication mechanisms to limit access to user data. Only authorized individuals should be able to view or modify the stored information.
  4. Regular Auditing and Monitoring: Continuously monitor and audit the database for any unauthorized access attempts or suspicious activity. Implementing intrusion detection systems and logging mechanisms can help identify potential breaches and take timely action.
  5. Education and Awareness: Educate employees and users about the importance of secure data storage practices, password hygiene, and the risks associated with plain text storage. Encourage strong, unique passwords and guide recognizing phishing attempts and social engineering techniques.

How to do Encryption on Django Models?

There are several useful packages for doing so. Here I have chosen django-encrypted-model-fields,which is a set of fields that wrap standard Django fields with encryption provided by the Python cryptography library.

Encryption/Decryption takes place in the Python

The following code represents the user model in plain text field format.

class CustomUserModel(AbstractUser):
"""
plain text user model
"""

username_validator = UnicodeUsernameValidator()

username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = models.CharField(
_("first name"), max_length=150, blank=True)
last_name = models.CharField(_("last name"), max_length=150, blank=True)
email = models.EmailField(_("email address"), blank=True, unique=True)
phone = models.CharField(_("mobile phone"), max_length=13)

class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
db_table = "plaintext_users"
plain text users' data at risk

Let’s change the above model to an encrypted model.

class CustomUserModel(AbstractUser):
"""
encrypted user model
"""

username_validator = UnicodeUsernameValidator()

username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = EncryptedCharField(
_("first name"), max_length=150, blank=True)
last_name = EncryptedCharField(_("last name"), max_length=150, blank=True)
email = EncryptedEmailField(_("email address"), blank=True, unique=True)
phone = EncryptedCharField(_("mobile phone"), max_length=13)

class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
db_table = "encrypted_users"
encrypted users’ data in safe
decrypted user data on REST API

For the last step, we should do encryption on id . By applying hashing algorithms to the IDs, the sensitive information and patterns associated with the original IDs are transformed into a unique sequence of characters, effectively obscuring the underlying data. This serves as a protective measure against unauthorized access and potential data breaches, as even if an attacker manages to gain access to the hashed IDs, they would find it exceedingly difficult to reverse-engineer the original values. Hashing also facilitates the quick retrieval of records without compromising security, as it enables efficient indexing and searching based on the hashed values. Furthermore, when used in combination with other security measures like salting, hashing significantly enhances the overall integrity and confidentiality of the database, safeguarding the sensitive information it holds.

django-hashid-fieldis a custom Model Field that uses the Hashids library to obfuscate an IntegerField or AutoField. It can be used in new models or dropped in place of an existing IntegerField, explicit AutoField, or an automatically generated AutoField.

class CustomUserModel(AbstractUser):
"""
encrypted user model
"""

username_validator = UnicodeUsernameValidator()

id = HashidAutoField(primary_key=True)
username = models.CharField(
_("username"),
max_length=150,
unique=True,
help_text=_(
"Required. 150 characters or fewer. Letters, digits and @/./+/-/_ only."
),
validators=[username_validator],
error_messages={
"unique": _("A user with that username already exists."),
},
)
first_name = EncryptedCharField(
_("first name"), max_length=150, blank=True)
last_name = EncryptedCharField(_("last name"), max_length=150, blank=True)
email = EncryptedEmailField(_("email address"), blank=True, unique=True)
phone = EncryptedCharField(_("mobile phone"), max_length=13)

class Meta:
verbose_name = _("user")
verbose_name_plural = _("users")
db_table = "encrypted_users"
encrypted id of user record on REST API

Data Encryption/Decryption in the Database

In this article, we discussed the importance of data encryption and techniques for doing this in the programming language (Python). In the next part, we will discuss the data encryption methods in the database.

Safeguarding User Data in Django (Part 2): Database Encryption/Decryption

--

--