Hiding a password in a python script (insecure obfuscation only)
Asked Answered
O

21

169

I have got a python script which is creating an ODBC connection. The ODBC connection is generated with a connection string. In this connection string I have to include the username and password for this connection.

Is there an easy way to obscure this password in the file (just that nobody can read the password when I'm editing the file) ?

Outcrop answered 1/10, 2008 at 14:37 Comment(2)
Just remember that the users running this file will have at least read access to it and can easily grab the passwords. If thins can only be read by you and you are worried about people seeing it over your shoulder go for it, but be warned while the average observer can't memorize things fast enough to grab a password, anyone with access to the script and a small bit of technical know-how and a small amount of ambition will be able to grab your passwords. Always think security through very carefully, it's important.Mandal
Possible duplicate of I need to securely store a username and password in Python, what are my options?Lawrenson
B
145

Base64 encoding is in the standard library and will do to stop shoulder surfers:

>>> import base64
>>>  print(base64.b64encode("password".encode("utf-8")))
cGFzc3dvcmQ=
>>> print(base64.b64decode("cGFzc3dvcmQ=").decode("utf-8"))
password
Babi answered 1/10, 2008 at 14:43 Comment(13)
I agree. The base64 encoded password looks much more mysterious.Arrant
But doesn't help the fact that the script must be readable by the user running it and the password must not.Freestone
You don't encode the entire script though. Just the password inside the script.Bussy
I don't think that base64 is better obfuscating than rot13 in this context. On the contrary, base64 has its typical characteristics (equal sign, ...) and is thus easier detectable than other approaches. Any obfuscation has no practical benefit, though. Really bad that this answer is this highly rated. It just gives a false feeling of security...Compendium
If you're recording the password so that it can be used by the script, anyone with access to the script will be able to able to get the password, not matter which encryption method you use. The requirement here was just to hide the password from someone just looking at the script while it was open. In this case base64 is preferable to rot13 as it is in the Python standard library.Babi
Really? >>> u'abc'.encode('rot13') 'nop' The problem here is that in the question and answer it is not clear that this should just help against people looking over your shoulder and might misguiding others.Compendium
base64 is NOT encryption. it's obfuscation at best.Corny
This throws a TypeError in Python 3. This can be fixed by using base64.b64encode("password".encode('utf-8')) and base64.b64decode("cGFzc3dvcmQ=").decode('utf-8')Pronghorn
I'm not sure how even this obscures the password. You can read it right there in line two very plainly: "password" So what if you later encode it, you had to put it there plainly first before it could be encoded. Where's the obfuscation?Stylolite
TypeError: a bytes-like object is required, not 'str'Disburse
while running this code i'm returning error like ..at this line print(base64.b64encode("password")) TypeError: a bytes-like object is required, not 'str'Suggs
You can use: base64.b64encode(b"password") and then base64.b64decode(b'...').decode()Joris
agree with @Stylolite , you can still see the password. Perhaps using a config.ini file with the cryptography library would be better.Streamlet
V
74

Here is a simple method:

  1. Create a python module - let's call it peekaboo.py.
  2. In peekaboo.py, include both the password and any code needing that password
  3. Create a compiled version - peekaboo.pyc - by importing this module (via python commandline, etc...).
  4. Now, delete peekaboo.py.
  5. You can now happily import peekaboo relying only on peekaboo.pyc. Since peekaboo.pyc is byte compiled it is not readable to the casual user.

This should be a bit more secure than base64 decoding - although it is vulnerable to a py_to_pyc decompiler.

Vin answered 2/4, 2014 at 19:45 Comment(7)
This still has some shortcomings, but it's actually very close to what I want. It will allow me to demo python scripts that include user/passwd connections without revealing the password onscreen, or having to type it into the command prompt. After importing peekaboo import peekabo the password is available as peekaboo.password (if peekaboo.py contained password='secret')Stylolite
If you want to take this idea one step further, you can use Cython to compile any .py file into C and generate a platform specific binary (ex: .pyd for windows, .so for macOS, etc)... By cythonizing your script and sharing the generated binary you'll get the benefit of this answer + add another layer of obfuscation, because now you have decompile C code to get to password. This is not 100% secure, but it'll take a lot of labor to get to the sensitive data you want to hide.Spinnaker
cythonizing, perfectJokjakarta
The issue: open .pyd-file with hex editor and see the secret string.Non
I used this method, but I had to move the generated .pyc file out of a __pycache__ folder that VS Code created. I then had to rename the .pyc file from modname.cpython-310.pyc to modname.pyc (to closely match the original modname.py module file I created in step 1.Streamlet
The .pyc file can also be opened and viewed in a text editor like Notepad, with the credentials viewable in plain text.Streamlet
Also don't forget to add the new .pyc file to the .gitignore for extra security!Streamlet
F
59

Douglas F Shearer's is the generally approved solution in Unix when you need to specify a password for a remote login.
You add a --password-from-file option to specify the path and read plaintext from a file.
The file can then be in the user's own area protected by the operating system. It also allows different users to automatically pick up their own own file.

For passwords that the user of the script isn't allowed to know - you can run the script with elavated permission and have the password file owned by that root/admin user.

Freestone answered 1/10, 2008 at 15:34 Comment(5)
How exactly do you run the script with elevated permissions without giving a root or admin password? Is it related to set UID bits?Mandal
Never mind I figured it out. For anyone else who cares: If a script has a setuid bit set the OS will 'pass' the setuid bit to the interpreter. Unfortunately, there are massive gaping security holes so most modern distros turn off setuid for scripts.Mandal
I can't find any information on the --password-from-file option. Do you have any examples? Thanks!Talbot
@Talbot - I meant that you would code a feature like this and add the ability to read a passwd from a fileFreestone
@MartinBeckett but like Youarefunny said, you would have to raise setuid on python in order to give the script root access to the password file?Talbot
C
34

If you are working on a Unix system, take advantage of the netrc module in the standard Python library. It reads passwords from a separate text file (.netrc), which has the format decribed here.

Here is a small usage example:

import netrc

# Define which host in the .netrc file to use
HOST = 'mailcluster.loopia.se'

# Read from the .netrc file in your home directory
secrets = netrc.netrc()
username, account, password = secrets.authenticators( HOST )

print username, password
Coucher answered 23/6, 2011 at 9:17 Comment(0)
I
22

How about importing the username and password from a file external to the script? That way even if someone got hold of the script, they wouldn't automatically get the password.

Intoxicant answered 1/10, 2008 at 15:28 Comment(0)
D
20

base64 is the way to go for your simple needs. There is no need to import anything:

>>> 'your string'.encode('base64')
'eW91ciBzdHJpbmc=\n'
>>> _.decode('base64')
'your string'
Dubai answered 1/10, 2008 at 22:26 Comment(6)
What exactly is silly?! The whole reply, or the not-importing part?Dubai
Base64 only adds the illusion of security.Wichita
Jonathan, it seems as if you didn't read the question. It's about obscurity (and a very temporary one), not security, so I don't understand why you consider my answer not helpful.Dubai
I didn't know you could do this instead of having to use the base64 module. And there are a lot of encodings too like zlib too... fun :)Masao
Which way is generally preferred? This or the base64 module?Felafel
@Felafel Using the base64 module is the preferred way nowadays. The latter doesn't work anymore in newer versions of Python.Deltoro
S
19

The best solution, assuming the username and password can't be given at runtime by the user, is probably a separate source file containing only variable initialization for the username and password that is imported into your main code. This file would only need editing when the credentials change. Otherwise, if you're only worried about shoulder surfers with average memories, base 64 encoding is probably the easiest solution. ROT13 is just too easy to decode manually, isn't case sensitive and retains too much meaning in it's encrypted state. Encode your password and user id outside the python script. Have he script decode at runtime for use.

Giving scripts credentials for automated tasks is always a risky proposal. Your script should have its own credentials and the account it uses should have no access other than exactly what is necessary. At least the password should be long and rather random.

Sining answered 1/10, 2008 at 16:9 Comment(2)
Very nice answer - thank you. For the small scripts I'm writing (which are maintenance scripts anyway - the BASE64 encoding will suffice)Outcrop
This sounds good, but can you give an example implementation? Right now it's just a description of a general practice, and not as useful to someone hasn't done this before.Stylolite
C
5

for python3 obfuscation using base64 is done differently:

import base64
base64.b64encode(b'PasswordStringAsStreamOfBytes')

which results in

b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM='

note the informal string representation, the actual string is in quotes

and decoding back to the original string

base64.b64decode(b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM=')
b'PasswordStringAsStreamOfBytes'

to use this result where string objects are required the bytes object can be translated

repr = base64.b64decode(b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM=')
secret = repr.decode('utf-8')
print(secret)

for more information on how python3 handles bytes (and strings accordingly) please see the official documentation.

Chastise answered 3/4, 2019 at 1:40 Comment(2)
@luke I really doubt they were talking about those kind of quotes as it makes no sense having those quote there.Usanis
@Usanis my bad, didn't look closely enough at the stringPhraseology
K
5

A way that I have done this is as follows:

At the python shell:

>>> from cryptography.fernet import Fernet
>>> key = Fernet.generate_key()
>>> print(key)
b'B8XBLJDiroM3N2nCBuUlzPL06AmfV4XkPJ5OKsPZbC4='
>>> cipher = Fernet(key)
>>> password = "thepassword".encode('utf-8')
>>> token = cipher.encrypt(password)
>>> print(token)
b'gAAAAABe_TUP82q1zMR9SZw1LpawRLHjgNLdUOmW31RApwASzeo4qWSZ52ZBYpSrb1kUeXNFoX0tyhe7kWuudNs2Iy7vUwaY7Q=='

Then, create a module with the following code:

from cryptography.fernet import Fernet

# you store the key and the token
key = b'B8XBLJDiroM3N2nCBuUlzPL06AmfV4XkPJ5OKsPZbC4='
token = b'gAAAAABe_TUP82q1zMR9SZw1LpawRLHjgNLdUOmW31RApwASzeo4qWSZ52ZBYpSrb1kUeXNFoX0tyhe7kWuudNs2Iy7vUwaY7Q=='

# create a cipher and decrypt when you need your password
cipher = Fernet(key)

mypassword = cipher.decrypt(token).decode('utf-8')

Once you've done this, you can either import mypassword directly or you can import the token and cipher to decrypt as needed.

Obviously, there are some shortcomings to this approach. If someone has both the token and the key (as they would if they have the script), they can decrypt easily. However it does obfuscate, and if you compile the code (with something like Nuitka) at least your password won't appear as plain text in a hex editor.

Kainite answered 2/7, 2020 at 1:27 Comment(1)
Here is ready to use python wrapper using fernet (and above code) github.com/moshahmed/blowfish-zip-unzip/blob/master/python/…Zohara
B
4

This is a pretty common problem. Typically the best you can do is to either

A) create some kind of ceasar cipher function to encode/decode (just not rot13) or

B) the preferred method is to use an encryption key, within reach of your program, encode/decode the password. In which you can use file protection to protect access the key.

Along those lines if your app runs as a service/daemon (like a webserver) you can put your key into a password protected keystore with the password input as part of the service startup. It'll take an admin to restart your app, but you will have really good pretection for your configuration passwords.

Bary answered 1/10, 2008 at 16:19 Comment(0)
K
3

Your operating system probably provides facilities for encrypting data securely. For instance, on Windows there is DPAPI (data protection API). Why not ask the user for their credentials the first time you run then squirrel them away encrypted for subsequent runs?

Kikuyu answered 1/10, 2008 at 15:22 Comment(0)
A
3

Here is my snippet for such thing. You basically import or copy the function to your code. getCredentials will create the encrypted file if it does not exist and return a dictionaty, and updateCredential will update.

import os

def getCredentials():
    import base64

    splitter='<PC+,DFS/-SHQ.R'
    directory='C:\\PCT'

    if not os.path.exists(directory):
        os.makedirs(directory)

    try:
        with open(directory+'\\Credentials.txt', 'r') as file:
            cred = file.read()
            file.close()
    except:
        print('I could not file the credentials file. \nSo I dont keep asking you for your email and password everytime you run me, I will be saving an encrypted file at {}.\n'.format(directory))

        lanid = base64.b64encode(bytes(input('   LanID: '), encoding='utf-8')).decode('utf-8')  
        email = base64.b64encode(bytes(input('   eMail: '), encoding='utf-8')).decode('utf-8')
        password = base64.b64encode(bytes(input('   PassW: '), encoding='utf-8')).decode('utf-8')
        cred = lanid+splitter+email+splitter+password
        with open(directory+'\\Credentials.txt','w+') as file:
            file.write(cred)
            file.close()

    return {'lanid':base64.b64decode(bytes(cred.split(splitter)[0], encoding='utf-8')).decode('utf-8'),
            'email':base64.b64decode(bytes(cred.split(splitter)[1], encoding='utf-8')).decode('utf-8'),
            'password':base64.b64decode(bytes(cred.split(splitter)[2], encoding='utf-8')).decode('utf-8')}

def updateCredentials():
    import base64

    splitter='<PC+,DFS/-SHQ.R'
    directory='C:\\PCT'

    if not os.path.exists(directory):
        os.makedirs(directory)

    print('I will be saving an encrypted file at {}.\n'.format(directory))

    lanid = base64.b64encode(bytes(input('   LanID: '), encoding='utf-8')).decode('utf-8')  
    email = base64.b64encode(bytes(input('   eMail: '), encoding='utf-8')).decode('utf-8')
    password = base64.b64encode(bytes(input('   PassW: '), encoding='utf-8')).decode('utf-8')
    cred = lanid+splitter+email+splitter+password
    with open(directory+'\\Credentials.txt','w+') as file:
        file.write(cred)
        file.close()

cred = getCredentials()

updateCredentials()
Alfilaria answered 18/10, 2019 at 10:23 Comment(0)
W
2

Place the configuration information in a encrypted config file. Query this info in your code using an key. Place this key in a separate file per environment, and don't store it with your code.

Wichita answered 1/10, 2008 at 22:29 Comment(0)
F
2

More homegrown appraoch rather than converting authentication / passwords / username to encrytpted details. FTPLIB is just the example. "pass.csv" is the csv file name

Save password in CSV like below :

user_name

user_password

(With no column heading)

Reading the CSV and saving it to a list.

Using List elelments as authetntication details.

Full code.

import os
import ftplib
import csv 
cred_detail = []
os.chdir("Folder where the csv file is stored")
for row in csv.reader(open("pass.csv","rb")):       
        cred_detail.append(row)
ftp = ftplib.FTP('server_name',cred_detail[0][0],cred_detail[1][0])
Furtherance answered 30/5, 2013 at 19:24 Comment(0)
P
1

Do you know pit?

https://pypi.python.org/pypi/pit (py2 only (version 0.3))

https://github.com/yoshiori/pit (it will work on py3 (current version 0.4))

test.py

from pit import Pit

config = Pit.get('section-name', {'require': {
    'username': 'DEFAULT STRING',
    'password': 'DEFAULT STRING',
    }})
print(config)

Run:

$ python test.py
{'password': 'my-password', 'username': 'my-name'}

~/.pit/default.yml:

section-name:
  password: my-password
  username: my-name
Pilchard answered 28/6, 2016 at 10:3 Comment(3)
Pit does not have any documentationArmourer
As @Armourer has noted - I don't see ANY documentation in those github/pypi links for "pit" - but the above description is clear - and overall I like this solution for "hiding" credentials from easy view...Eyebolt
I am reluctant to use a module that is not maintained, and I get errors when I try to use it as instructed: /usr/lib/python3.7/site-packages/pit.py:93: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details. return yaml.load(open(Pit._config))Eyebolt
H
1

If running on Windows, you could consider using win32crypt library. It allows storage and retrieval of protected data (keys, passwords) by the user that is running the script, thus passwords are never stored in clear text or obfuscated format in your code. I am not sure if there is an equivalent implementation for other platforms, so with the strict use of win32crypt your code is not portable.

I believe the module can be obtained here: http://timgolden.me.uk/pywin32-docs/win32crypt.html

Hautevienne answered 11/11, 2017 at 16:37 Comment(0)
S
1

You could also consider the possibility of storing the password outside the script, and supplying it at runtime

e.g. fred.py

import os
username = 'fred'
password = os.environ.get('PASSWORD', '')
print(username, password)

which can be run like

$ PASSWORD=password123 python fred.py
fred password123

Extra layers of "security through obscurity" can be achieved by using base64 (as suggested above), using less obvious names in the code and further distancing the actual password from the code.

If the code is in a repository, it is often useful to store secrets outside it, so one could add this to ~/.bashrc (or to a vault, or a launch script, ...)

export SURNAME=cGFzc3dvcmQxMjM=

and change fred.py to

import os
import base64
name = 'fred'
surname = base64.b64decode(os.environ.get('SURNAME', '')).decode('utf-8')
print(name, surname)

then re-login and

$ python fred.py
fred password123
Skunk answered 18/7, 2019 at 23:37 Comment(1)
The first part of this answer about retrieving the password from an environment variable is good enough for most situations, including command-line tool and Jenkins server job. The only improvement could be to suggest doing a onetime export VAR=val before running the script, as a light defense against screenshots/shoulder-surfers. The second part about obfuscating the contents of the environment variable doesn't add much value IMNSHO.Tabithatablature
C
1

Why not have a simple xor?

Advantages:

  • looks like binary data
  • noone can read it without knowing the key (even if it's a single char)

I get to the point where I recognize simple b64 strings for common words and rot13 as well. Xor would make it much harder.

Clarissaclarisse answered 22/10, 2019 at 9:29 Comment(1)
what do you mean by xor? could you help provide an example here?Feticide
P
0

There are several ROT13 utilities written in Python on the 'Net -- just google for them. ROT13 encode the string offline, copy it into the source, decode at point of transmission.

But this is really weak protection...

Partition answered 1/10, 2008 at 14:43 Comment(1)
please include a link or sample code to make this answer more usefulSwearingen
T
0

This doesn't precisely answer your question, but it's related. I was going to add as a comment but wasn't allowed. I've been dealing with this same issue, and we have decided to expose the script to the users using Jenkins. This allows us to store the db credentials in a separate file that is encrypted and secured on a server and not accessible to non-admins. It also allows us a bit of a shortcut to creating a UI, and throttling execution.

Tsunami answered 29/10, 2018 at 16:14 Comment(0)
R
-1
import base64
print(base64.b64encode("password".encode("utf-8")))
print(base64.b64decode(b'cGFzc3dvcmQ='.decode("utf-8")))
Rhotacism answered 25/5, 2020 at 12:20 Comment(1)
This is the solution from the accepted answer.Meuser

© 2022 - 2024 — McMap. All rights reserved.