This might seem like a strange question, but I have this idea that I want to make a python script that requires a pass login. The user should be able to type in the desired pass in the beginning of the program then the code will write that into the actual source code (so no extra files are generated).
I know that this is possible by doing something like this
with open('test.py','a') as f:
f.write('\nprint "hello world"')
Running this script 3 times will generate the following code
with open('test.py','a') as f:
f.write('\nprint "hello world"')
print "hello world"
print "hello world"
print "hello world"
But I would like to make my python script work on every windows machine that doesn't have python installed. So i would have to use PyInstaller - but then how would I be able to write to the source code?
(Optional solution to my question would be an answer how to securely save then password without creating too many obscure files that frightens the end-user)
AFAIK there is no way to modify your code after it is an executable, but you can simply store the password as hash in one file (Method A) or better use a special module for it (Method B). You should never store passwords anywhere in plain text (even not in your executable)
Method A (only use this if you can't use other libraries)
The code could look like this:
# To create the password file (e.g. change password)
import hashlib
with open('password', 'wb') as f:
p = 'new password'
f.write(hashlib.sha512(p.encode('utf-8')).digest()) # hash and save password
# To check the password
import hashlib
with open('password', 'rb') as f:
p_in = # your method to read get the password from the user
p = hashlib.sha512(p_in.encode('utf-8')).digest() # create hash
if p == f.read(): # verify hash (password)
# right password
else:
# wrong password
The content of the file is the binary form of the hash.
One important thing to note is, that you should use a secure hash function (look into the article linked above) or better use Method B.
Method B (you should use this)
Here is a way more secure and even simpler version (as pointed out by user9876) with the usage of the library passlib which is for such things.
This is an example copied from the passlib documentation:
# import the context under an app-specific name (so it can easily be replaced later)
from passlib.apps import custom_app_context as pwd_context
# encrypting a password...
hash = pwd_context.encrypt("somepass")
# verifying a password...
ok = pwd_context.verify("somepass", hash)
As you can see the hashing and verification is very simple and you can configure various parameters if you want.
There are a many ways to store the hash, which all have pros and cons so you have to carefully think about them.
A simple File.
You could use the same file to store other settings of you program
If someone installs your program into C:\Program Files\ your program would probably not have the rights to store a file there (but you can use some standard directory like %APPDATA%)
You could hide the file (but if someone copies the program there is a high chance, that it will be lost)
The Windows registry. You can use the standard python winreg module.
Hidden from the user
No extra files
Only on windows
Not portable (if you copy the program to another computer the password will be lost)
Append it to the executable. This is an possibility, but it wouldn't work in your case, because you can't modify a running executable. That means you would need another program to change your main program and that would be another file. So it is the same number of files as if you use the first option, but more work.
Another think to note is, that you could have a master password or fallback password if someone (accidentally) deletes your saved password. But you should think about this, because someone who knows the master password can delete the old password and get into your program.
As you already noticed, storing data in code has more problems than it solves. The way to store "hidden" configuration would be to use _winreg (or winreg in py3) under Windows, and ConfigParser for a ~/.config/myapp.ini file under Linux and other POSIX systems. But then, most people use an .INI file in %APPDATA% under Windows too, that's hidden enough.
If you write a wrapper class that abstracts away the differences, your application code can use this uniformly as a small key/value store. More or less ready-to-use solutions are in this recipe and in kilnconfig.
Then when it comes to passwords, use py-bcrypt to securely persist them.
NEVER NEVER NEVER store passwords!!! It is just insecure!
Use the following approach instead:
make a file "passwords.pwd" (windows will not recognize the file type - good for dummy useres)
Don't store the pssword but the hashing function of the password (you can use e.g. passlib or do your own approach):
import hashlib
password = "12345" #take user input here
hashed_password = hashlib.sha512(password).hexdigest()
print hashed_password
Whenever you have to verify a password, just do the above calculation and compare the result to the strored hash value.
Related
This question already has answers here:
How to create an encrypted ZIP file?
(8 answers)
Closed 11 months ago.
I have an encrypted ZIP file and for some reason, any password I feed it doesn't seem to matter as it can add files to the archive regardless. I checked for any ignored exceptions or anything, but nothing seems to be fairly obvious.
I posted the minimalist code below:
import zipfile
z = zipfile.ZipFile('test.zip', 'a') #Set zipfile object
zipPass = str(input("Please enter the zip password: "))
zipPass = bytes(zipPass, encoding='utf-8')
z.setpassword(zipPass) #Set password
z.write("test.txt")
I am not sure what I am missing here, but I was looking around for anything in zipfile that can handle encrypted zipfiles and add files into them using the password, as the only thing I have is the ``z.setpassword()` function that seems to not work here.
TL;DR: z.write() doesn't throw an exception and neither does z.setpassword() or anything zipfile related when fed the incorrect password, and willingly adds files no matter what. I was expecting to get BadPasswordForFile.
Is there any way to do this?
What I found in the documentation for zipfile is that the library supports decryption only with a password. It cannot encrypt. So you won't be able to add files with a password.
It supports decryption of encrypted files in ZIP archives, but it currently cannot create an encrypted file.
https://docs.python.org/3/library/zipfile.html
EDIT: Further, looking into python bugs Issue 34546: Add encryption support to zipfile it appears that in order to not perpetuate a weak password scheme that is used in zip, they opted to not include it.
Something that you could do is utilize subprocess to add files with a password.
Further, if you wanted to "validate" the entered password first, you could do something like this but you'd have to know the contents of the file because decrypt will happily decrypt any file with any password, the plaintext result will just be not correct.
Issues you'll have to solved:
Comparing file contents to validate password
Handling when a file exists already in the zip file
handling when the zipfile already exists AND when it doesn't.
import subprocess
import zipfile
def zip_file(zipFilename, filename):
zipPass = str(input("Please enter the zip password: "))
zipPass = bytes(zipPass, encoding='utf-8')
#If there is a file that we know the plain-text (or original binary)
#TODO: handle fipFilename not existing.
validPass=False
with zipfile.ZipFile(zipFilename, 'r') as zFile:
zFile.setpassword(zipPass)
with zFile.open('known.txt') as knownFile:
#TODO: compare contents of known.txt with actual
validPass=True
#Next to add file with password cannot use zipfile because password not supported
# Note this is a linux only solution, os dependency will need to be checked
#if compare was ok, then valid password?
if not validPass:
print('Invalid Password')
else:
#TODO: handle zipfile not-exist and existing may have to pass
# different flags.
#TODO: handle filename existing in zipFilename
#WARNING the linux manual page for 'zip' states -P is UNSECURE.
res = subprocess.run(['zip', '-e', '-P', zipPass, zipFilename, filename])
#TODO: Check res for success or failure.
EDIT:
I looked into fixing the whole "exposed password" issue with -P. Unfortunately, it is non trivial. You cannot simply write zipPass into the stdin of the subprocess.run with input=. I think something like pexpect might be a solution for this, but I haven't spent the time to make that work. See here for example of how to use pexpect to accomplish this: Use subprocess to send a password_
After all of the lovely replies, I did find a workaround for this just in case someone needs the answer!
I did first retry the z.testzip() and it does actually catch the bad passwords, but after seeing that it wasn't reliable (apparently hash collisions that allow for bad passwords to somehow match a small hash), I decided to use the password, extract the first file it sees in the archive, and then extract it. If it works, remove the extracted file, and if it doesn't, no harm done.
Code works as below:
try:
z = zipfile.ZipFile(fileName, 'a') #Set zipfile object
zipPass = bytes(zipPass, encoding='utf-8') #Str to Bytes
z.setpassword(zipPass) #Set password
filesInArray = z.namelist() #Get all files
testfile = filesInArray[0] #First archive in list
z.extract(testfile, pwd=zipPass) #Extract first file
os.remove(testfile) #remove file if successfully extracted
except Exception as e:
print("Exception occurred: ",repr(e))
return None #Return to mainGUI - this exits the function without further processing
Thank you guys for the comments and answers!
using python-gnupg v0.3.5 on windows 7 w/Python 2.7 and GPG4Win v2.2.0
test_gnupg.py results in 2 failures:
Test that searching for keys works ... FAIL
Doctest: gnupg.GPG.recv_keys ... FAIL
2 keyrings exist in each of these locations(secring & pubring in each):
under the GPGHome directory (C:\Program Files (x86)\GNU\GnuPG)
under the user profile(C:\Users\\AppData\Roaming\gnupg)
If I create GPG instance and set the keyring file path to the user profile pubring.pgp I get a result from GPG.list_keys(). If I let it use the gpghome directory pubring.pgp I get no results from list_keys() because that keyring is empty.
So given I specify the user profile keyring and I have a key to use this is what happens:
>>>data = '1234 abcd 56678'
>>>fingerprint = u'<fingerprint>'
>>>enc = gpg.encrypt(data,fingerprint)
>>>enc.data
''
encrypt_file() gives the same results, nothing happens, no errors. I'm not particularly savvy in any of this but it seems like if I have data and public key this should be dead simple. I'm having a horrendous time trying to determine what is wrong given I see no log files anywhere and I have no errors when attempting this.
How can I determine what is going wrong here?
I've read pretty much everything I can find here on StackOverflow, http://pythonhosted.org/python-gnupg/#getting-started and the google group for python-gnupg.
Also why do I have 2 separate sets of keyrings in the first place?
edit:
clarified there are 2 separate sets of pubring and secring
edit 2:
answer below was instrumental in leading to the actual problem.
the gnupg.GPG() constructor is setting gpg command line options that include 'no-tty', calling gnupg.GPG(options='') resolves the issue and successfully encrypts both data and files.
Okay, I finally got around to looking at this and got basic encryption to work from the command line. Here's an example that will work to encrypt data entered from the command line:
import gnupg
gpg_home = "/path/to/gnupg/home"
gpg = gnupg.GPG(gnupghome=gpg_home)
data = raw_input("Enter data to encrypt: ")
rkey = raw_input("Enter recipient's key ID: ")
encrypted_ascii_data = gpg.encrypt(data, rkey)
print(encrypted_ascii_data)
Change the gpg_home to whichever of those two GnuPG paths you want to use. The first one looks like the default installation location and the second one appears to be specific to your user account. The script will prompt for some text to encrypt and a key ID to encrypt to, then print the ASCII armoured encrypted data to stdout.
EDIT: I'm not certain, but I suspect the reason your code failed was either due to using the whole fingerprint for the recipient key ID, which is unnecessary (I used the 0xLONG format, an example of which is on my profile), or you called the wrong GPG home directory.
EDIT 2: This works to encrypt files and writes the output to a file in the same directory, it will work as is on *nix systems. You will need to change the gpg_home as with the above example:
import gnupg
gpg_home = "~/.gnupg"
gpg = gnupg.GPG(gnupghome=gpg_home)
data = raw_input("Enter full path of file to encrypt: ")
rkeys = raw_input("Enter key IDs separated by spaces: ")
savefile = data+".asc"
afile = open(data, "rb")
encrypted_ascii_data = gpg.encrypt_file(afile, rkeys.split(), always_trust=True, output=savefile)
afile.close()
My work here is done! :)
BTW, both these examples use Python 2.7, for Python 3 you'll need to modify the raw_input() lines to use input() instead.
I have a python program that just needs to save one line of text (a path to a specific folder on the computer).
I've got it working to store it in a text file and read from it; however, I'd much prefer a solution where the python file is the only one.
And so, I ask: is there any way to save text in a python program even after its closed, without any new files being created?
EDIT: I'm using py2exe to make the program an .exe file afterwards: maybe the file could be stored in there, and so it's as though there is no text file?
You can save the file name in the Python script and modify it in the script itself, if you like. For example:
import re,sys
savefile = "widget.txt"
x = input("Save file name?:")
lines = list(open(sys.argv[0]))
out = open(sys.argv[0],"w")
for line in lines:
if re.match("^savefile",line):
line = 'savefile = "' + x + '"\n'
out.write(line)
This script reads itself into a list then opens itself again for writing and amends the line in which savefile is set. Each time the script is run, the change to the value of savefile will be persistent.
I wouldn't necessarily recommend this sort of self-modifying code as good practice, but I think this may be what you're looking for.
Seems like what you want to do would better be solved using the Windows Registry - I am assuming that since you mentioned you'll be creating an exe from your script.
This following snippet tries to read a string from the registry and if it doesn't find it (such as when the program is started for the first time) it will create this string. No files, no mess... except that there will be a registry entry lying around. If you remove the software from the computer, you should also remove the key from the registry. Also be sure to change the MyCompany and MyProgram and My String designators to something more meaningful.
See the Python _winreg API for details.
import _winreg as wr
key_location = r'Software\MyCompany\MyProgram'
try:
key = wr.OpenKey(wr.HKEY_CURRENT_USER, key_location, 0, wr.KEY_ALL_ACCESS)
value = wr.QueryValueEx(key, 'My String')
print('Found value:', value)
except:
print('Creating value.')
key = wr.CreateKey(wr.HKEY_CURRENT_USER, key_location)
wr.SetValueEx(key, 'My String', 0, wr.REG_SZ, 'This is what I want to save!')
wr.CloseKey(key)
Note that the _winreg module is called winreg in Python 3.
Why don't you just put it at the beginning of the code. E.g. start your code:
import ... #import statements should always go first
path = 'what you want to save'
And now you have path saved as a string
How can I check a user-supplied path is sanitised?
I want to ensure it has no wildcards nor any shenanigans. Right now, I'm checking that it is not escaping the correct folder so:
if os.path.commonprefix([os.path.abspath(path),os.getcwd()]) != os.getcwd():
# raise error etc..
But like all self-written security check code, I want it held up to better scrutiny! And it doesn't address that the path is actually legal after all that.
I will then be using the path to create assets and such.
You could use Werkzeug's secure_filename:
werkzeug.utils.secure_filename(filename)
Pass it a filename and it will return a secure version of it. This
filename can then safely be stored on a regular file system and passed
to os.path.join(). The filename returned is an ASCII only string for
maximum portability.
On windows system the function also makes sure that the file is not
named after one of the special device files.
>>> secure_filename("My cool movie.mov")
'My_cool_movie.mov'
>>> secure_filename("../../../etc/passwd")
'etc_passwd'
>>> secure_filename(u'i contain cool \xfcml\xe4uts.txt')
'i_contain_cool_umlauts.txt'
I have a fairly naive thing I want to do and I want to know if someone can answer can tell me if this is just flat out stupid. If what I am going to ask is not stupid but perhaps naive, I'd appreciate if I can get a nudge in a correct direction.
I have a file named pwds.py. Its contents are
import hashlib
class Pwds:
def __init__(self):
pass
def printGood(self,key):
y = hashlib.sha1()
y.update(key.encode('ascii'))
if y.hexdigest() == "db5f60442c78f08eefb0a2efeaa860b071c4cdae":
print("You entered the correct key!")
else:
print("Intruder!")
Then I have another file named runme.py, whose contents are
import pwds
x = input("Please type the password: ")
y = pwds.Pwds()
y.printGood(x)
x = input("Press any key to end")
The first time runme.py is run, a pwds.pyc file is created. My thought was that once the .pyc file was created, I could delete pwds.py and run runme.py as normal. Additionally, I thought the contents of pwds.py would be contained in .pyc but made unreadable since this is a "compiled" Python file. Thus, while I can delete pwds.py and successfully run runme.py, pwds.pyc is pretty much readable if I open it in, say, Notepad.
Thus, the question(s) in general: How can I keep the contents of pwds.py unreadable? What I wanted to do with the above code was to keep "secret" information in a Python file, compile it, and have its contents be accessible only if the correct key were typed. Is this approach too stupid to even consider? I didn't want to get into writing a "garbler" and a "degarbler". I thought this would be a simple and cheap solution.
Thanks for reading this! Please let me know if there is any other information I should provide.
The .pyc file simply contains the compiled python code so it doesn't need to be recompiled everytime you run your program. Thus all strings in it are still readable (you could always look at the binary contents or step through the program via the pdb debugger).
If you want to protect something in your code with a password, you have to encrypt it with strong encryption and only store the encrypted version. The users's key/password is then used to decrypt the data.