Why are .env files considered secure if they are plain text files?

I’m still new to Python. I’ve been investigating how best to securely store passwords. I read that the best practice is storing passwords and other connection info, like to an SMTP server, is putting them in a plain text file called .env then using the module python-dotenv to read the data into environment variables.

  1. Am I missing something? Is the password in the plain text .env file supposed to be encrypted, or can it be in plain text? I have found 3 examples of using the .env files and none of them used encryption.
  2. Why does plain text work in this case?

EDIT: My test environment is my laptop. My production environment will be Python, likely in a Function App, in the Azure ecosystem. I’m not positive about using the Function App but it does support Python. The bigger problem is I have to use Azure, and let people drag and drop one or more files for the Python program to handle. And I haven’t figured out the drag’n’drop part yet. Which will probably involve Javascript.

If someone has access to your system, it doesn’t matter if it’s plain text or encrypted. The only point is the keys/passwords/tokens should not be part of VCS (i.e. git) and shouldn’t be uploaded, whereas the code that uses the files could be uploaded.

4 Likes

I do not have a .env file and its sounds like bad advice to put passwords and other sensitive info into one. It is not “best practice”, who suggested it was?

If you have full-disk-encryption then at-rest your password in .env will not be accessible.
But if you are not encrypting then anyone that has your machine has your passwords.

Usually an email program will store SMTP credentials encrypted in a wallet or keystore.

Hi Chuck!

(Edit: see later reply for the context the rest of this post applies to)

Using a “.env” file is part of a software architecture pattern that designs programs to read their configuration from environment variables. This is done to strictly separate a systems’s code from its configuration. This is in contrast to, for example, reading configuration from a static file (INI, TOML, etc.) or having the configuration within executable code.

The design philosophy I have most often seen this associated with is called “The 12 Factor App” - you can read about how it recommends using environment variables for configuring services here.

But wait, you say - “.env” is a file and I am reading from the file with a library called dotenv! The key is separating a “development” environment from a “production” environment:

  • A “development environment” is the collection of settings and services used when you are editing your program. These settings are best restricted to your local computer. Because they are only used for development the values are not considered sensitive, but are still often not committed to source control because they may be different for every developer and their computer.
  • A “production environment” is the settings used when the program is run in a way that is publicly accessible, used by non developers and interacting with real user data. The values used for settings in production are sensitive and have to be stored in a way where they are never written to disk.

When developing, it is tedious to make sure your terminal or IDE always has the right environment variables set for your program to run. The convention that emerged was to store the development environment variables in a local file called “.env” (though it could be called anything) and when developing the app looks for that file and reads configuration from it.

But in production - when you actually run the app on a server - there is no .env file. Instead, you use an external service (often provided by your app hosting provider) to keep the production values off of the server and inject the values into your apps process environment when it runs. You define the values in a console or secret store somewhere else and then they get loaded into memory without ever being saved to a file your app can read. This way if the server where your app runs is compromised in some way, there are no secret values easily accessible to an attacker.

So in summary:

Using environment variables to configure web services is a common pattern used to keep sensitive configuration values off disk when running a public web service. Structuring your code this way is nice when you deploy it but can make it annoying to work on your code. A “.env” file is a convention for storing development-only, non-sensitive versions of your service’s environment variables so you don’t have to remember to configure them all the time when doing development.

2 Likes

I should say explicitly, the patterns I’m describing here are usually used with network services or web APIs where your code is running in an environment that you control and the sensitive configuration you need to keep safe is yours.

If you are creating a desktop or terminal application that is intended to run on a users computer / where the sensitive information belongs to a user, what @barry-scott said applies. The operating system usually provides a mechanism for apps to store sensitive values or an app may use a library to manage its own encrypted storage with a user managed key.

If you are creating a system service that needs credentials then this is covered by systemd the crypt (is that the right name) feature that will keep secrets secret.
See systemd-cryptenroll etc.

I’m not 100% positive they used the phrase “best practice” but there are numerous videos on Youtube that show people how to do this. One was from a guy named Indently.

Got it. The production environment would be on MS Azure and they have a keyring type thing I could use, I forget the name they call it. I just have to figure out how to get that working in Python as I will eventually be installing Python programs into Azure as an Azure Function App.

Thanks for the good explanation. None of the videos I watched about the .env file said it was for the test environment only. And none of the web pages I saw said that either.

I didn’t want to make the user enter any IMAP or SMTP passwords, and stuff like that. Though they might have to login and enter the password for their username. But that would be handled by the Azure ecosystem.

Sadly, “it’s shown in a Youtube video” spans the full gamut from best prac all the way down to outright awful (you’ll find videos out there that do direct SQL query construction in ways that leave themselves wide open to SQL injection, for example). There certainly will be some good advice out there, but in amongst it, some that’s decidedly situational.

Environment variables are extremely convenient ways of managing external configuration, since they are entirely standard. The conveniton of a .env file containing local settings is also fairly well known. So you can fairly confidently make use of env vars, expecting that anywhere you deploy to WILL allow you to set them (which isn’t necessarily the case for, say, systemd configuration, or a dedicated keyring). Are they good for passwords? Maybe, maybe not. But are they useful? Undoubtedly. They’re the perfect place to put settings like “what is this node’s ID?” and “what port number should I listen on?”, which aren’t at all security considerations, but they want to be different on every deployment.

So, are .env files best practice? As always, the answer is: It depends.

1 Like

That’s exactly the kind of thing, yes! App Services uses this pattern for configuration: Configure app settings. And it looks like Azure Functions are very similar: Environment variables . In either case, in your Python code just uses os.environ. The configuration complexity gets shifted out of app code and into ops/devops instead. :wink: