How to hide sensitive credentials using Python - GeeksforGeeks

14 Sep, 2021
Have you ever been in a situation where you are working on a python project need to share your code with someone or you are hosting your code in a public repository but don’t want to share the sensitive credentials so it isn’t exploited by a random user?
For example, you are making a web app in Django, where there is a concept of ‘SECRET_KEY’ which is a randomly generated unique key and is used for cryptographic signing of data. As the name suggests it should not be publicly shared as it defeats many of Django’s security protections. Or maybe you are using cloud storage say AWS S3, you will need to store the access token in the code and also prevent unauthorized users to misuse the credentials, how can we do both? For such cases, we need to prevent hardcoding of the ‘key’ (essentially the variables holding our credentials) into our code and subsequently not exposing it in our public repository.
One of the easiest and basic methods is to save the credentials in another python file say secrets.py and import it into the required file. We need to .gitignore the secrets.py file. Now we can store the credentials in essentially two ways, the first being to use python variables to store the values and the second more preferred way is to use a dictionary. Dictionary is preferred because if we try to access a non-existent variable, it will raise an error but in the case of the dictionary, we can return a default value.
We have saved the following credentials in a dictionary in secrets.py:
Python3
# secrets.pysecrets = {    'SECRET_KEY': "superSecretkey1234",    'DATABASE_USER': "testusr",    'DATABASE_PASSWORD': 'pass1234',    'DATABASE_PORT': 5432  }
Now import the credentials in the required file, main.py. 
Python3
# main.pyfrom secrets import secrets  secret_key = secrets.get('SECRET_KEY')  # gives default value if the credential is absentgoogle_maps_key = secrets.get('gmaps_key',                               'mapsapikey543')  db_user = secrets.get('DATABASE_USER', 'root')db_pass = secrets.get('DATABASE_PASSWORD', 'pass')db_port = secrets.get('DATABASE_PORT', 3306)  print('secret_key :', secret_key)print('google_maps_key :', google_maps_key)print('db_user :', db_user)print('db_pass :', db_pass)  # no need to type cast numbers and booleansprint('db_port :', db_port, type(db_port))
Output :
This works and we don’t need to worry about data type conversion of boolean and integer values(you will understand why this is important in the later methods) but isn’t the recommended approach because the file name and dictionary name can vary for different projects so it doesn’t form a standard solution. More importantly, this approach is restricted to python as in a more realistic scenario we could be working with multiple languages which also require access to the same credentials, and storing them in a way that is only accessible to one language isn’t ideal. A better approach is to use environment variables.
We can store the credentials as environment variables. Environment variables are essentially key-value pairs that are set using the functionality of the operating system and can be used by any programming language since they are linked to the environment or operating system. Since we are setting credentials as environment variables we aren’t exposing them in our code, so if someone else has the access to our code the credentials wouldn’t be set in their environment. Also, we can set different values for production and local environments like using a different mailing service while in development and we don’t need to worry about changing the code. 
Many hosting providers like Heroku, netlify, etc. provide an easy way to set the environment variables. In python we can access the environment variables using os.environ and it works very similar to a normal python dictionary.  os.environ returns string values and we need to manually typecast every value. Assuming we have set the same credentials mentioned above as environment variables.  
Python3
# main.pyimport os    def convert(val):      if type(val) != str:          return val    if val.isnumeric():        return int(val)    elif val == 'True':        return True    elif val == 'False':        return False    else:        return val    secret_key = convert(os.environ.get('SECRET_KEY'))  # gives default value if the credential is absentgoogle_maps_key = convert(os.environ.get('gmaps_key',                                          'mapsapikey543'))db_user = convert(os.environ.get('DATABASE_USER', 'root'))db_pass = convert(os.environ.get('DATABASE_PASSWORD', 'pass'))db_port = convert(os.environ.get('DATABASE_PORT', '3306'))  print('secret_key :', secret_key)print('google_maps_key :', google_maps_key)print('db_user :', db_user)print('db_pass :', db_pass)print('db_port :', db_port, type(db_port))
Output :
Also, environment variables set locally during development are persistent only for a session so we need to manually set them every time before we run our project. To make the process simpler we have an awesome package called python-decouple. The package helps in loading the credentials both from the environment or from an external .env or .ini file.
We store the credentials in a .env or settings.ini file and gitignore them. python-decouple searches for options in this order :
Environment variables.ini or .env file.default value passed during the call.
Environment variables.
ini or .env file.
default value passed during the call.
If the environment variable is already set it returns the same otherwise tries to read from the file and if not found it can return a default value. So it can read from the environment while in production and from the file while in development. 
Follow the simple 3 step process below:
Step 1: Install python-decouple using pip.
pip install python-decouple
Step 2: Store your credentials separately.
First, we need to ‘decouple’ our credentials from our code repository into a separate file. If you are using a version control system say git make sure to add this file to .gitignore. The file should be in one of the following forms and should be saved at the repository’s root directory:
settings.ini
.env   -notice there is no name to the file
These are popular file formats to save the configuration for the project. The files follow the syntax:
KEY=YOUR_KEY    -> without any quotes.
As a convention, we store the key name in all caps.
Here we are using .env format with the following credentials saved:
Now, we will .gitignore this file. To know more about .gitignore read this:
Step 3: Load your credentials securely. 
We can cast values by specifying the “cast” parameter to the corresponding type. Also if we do not specify a default value and the config object cannot find the given key it will raise an  “UndefinedValueError”.
Python3
# main.py  # 1. Import the config object from decouple.from decouple import config  # 2. Retrieve the credentials in your code.  # default data-type of returned value is str.SECRET_KEY = config('SECRET_KEY')   # you can cast the values on the fly.DEBUG = config('DEBUG', cast=bool)   # provide defaulft value if key is not found.EMAIL_HOST = config('EMAIL_HOST',                     default='localhost') EMAIL_USER = config('EMAIL_USER',                    default='gfg')  # if key not found and no default is given,# it will raise UndefinedValueError.EMAIL_PASSWORD = config('EMAIL_PASSWORD') EMAIL_PORT = config('EMAIL_PORT',                    default=25, cast=int)  print('secret_key :', SECRET_KEY)print('email_host :', EMAIL_HOST)print('email_user :', EMAIL_USER)print('email_pass :', EMAIL_PASSWORD)print('email_port :', EMAIL_PORT, type(EMAIL_PORT))
Output:
python-utility
Python
Writing code in comment?
Please use ide.geeksforgeeks.org,
generate link and share the link here.
Python Dictionary
Read a file line by line in Python
How to Install PIP on Windows ?
Enumerate() in Python
Different ways to create Pandas Dataframe
Iterate over a list in Python
Python String | replace()
*args and **kwargs in Python
Reading and Writing to text files in Python
Create a Pandas DataFrame from Lists