在python脚本中隐藏密码(仅用于不安全的混淆)

问题:在python脚本中隐藏密码(仅用于不安全的混淆)

我有一个python脚本正在创建ODBC连接。ODBC连接是使用连接字符串生成的。在此连接字符串中,我必须包含此连接的用户名和密码。

有没有一种简便的方法来隐藏文件中的此密码(只是在我编辑文件时没人能读取该密码)?

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) ?


回答 0

Base64编码在标准库中,并且可以阻止肩膀冲浪者:

>>> import base64
>>>  print(base64.b64encode("password".encode("utf-8")))
cGFzc3dvcmQ=
>>> print(base64.b64decode("cGFzc3dvcmQ=").decode("utf-8"))
password

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

回答 1

当您需要为远程登录指定密码时,Douglas F Shearer’s是Unix中公认的解决方案。
您添加–password-from-file选项来指定路径并从文件中读取纯文本。
然后,该文件可以位于受操作系统保护的用户自己的区域中。它还允许不同的用户自动选择自己的文件。

对于不允许脚本用户知道的密码-您可以使用高级权限运行脚本,并让该root / admin用户拥有密码文件。

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.


回答 2

这是一个简单的方法:

  1. 创建一个python模块-我们称之为peekaboo.py。
  2. 在peekaboo.py中,同时包含密码和需要该密码的任何代码
  3. 通过导入此模块(通过python命令行等)创建一个编译版本-peekaboo.pyc。
  4. 现在,删除peekaboo.py。
  5. 现在,您可以仅依靠peekaboo.pyc来愉快地导入peekaboo。由于peekaboo.pyc是字节编译的,因此临时用户无法读取。

尽管它容易受到py_to_pyc反编译器的攻击,但它应该比base64解码更加安全。

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.


回答 3

如果您在Unix系统上工作,请利用标准Python库中的netrc模块。它从单独的文本文件(.netrc)中读取密码,该文件的格式在此处描述

这是一个小用法示例:

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

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

回答 4

假设用户无法在运行时提供用户名和密码,最好的解决方案可能是单独的源文件,其中仅包含导入到您的主代码中的用户名和密码的变量初始化。仅在凭据更改时才需要编辑此文件。否则,如果您只担心具有平均记忆的冲浪者,那么base 64编码可能是最简单的解决方案。ROT13太容易手动解码,不区分大小写,并且在加密状态下保留了太多含义。在python脚本之外对您的密码和用户ID进行编码。让他在运行时对脚本进行解码以供使用。

为自动化任务提供脚本凭证始终是一个冒险的建议。您的脚本应具有其自己的凭据,并且所使用的帐户应完全不需要访问权限。至少密码应该是长且相当随机的。

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.


回答 5

如何从脚本外部的文件中导入用户名和密码?这样,即使有人掌握了该脚本,他们也不会自动获得密码。

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.


回答 6

base64是满足您简单需求的方法。无需导入任何内容:

>>> 'your string'.encode('base64')
'eW91ciBzdHJpbmc=\n'
>>> _.decode('base64')
'your string'

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'

回答 7

对于python3混淆,使用base64方式有所不同:

import base64
base64.b64encode(b'PasswordStringAsStreamOfBytes')

导致

b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM='

注意非正式的字符串表示形式,实际的字符串用引号引起来

并解码回原始字符串

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

在需要字符串对象的地方使用此结果,可以翻译字节对象

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

有关python3如何处理字节(以及相应的字符串)的更多信息,请参见官方文档

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.


回答 8

这是一个很常见的问题。通常,您能做的最好的就是

A)创建某种ceasar密码函数来进行编码/解码(但不是rot13)或

B)首选方法是在程序可及的范围内使用加密密钥对密码进行编码/解码。您可以在其中使用文件保护来保护访问密钥。

如果您的应用程序作为服务/守护程序(例如Web服务器)运行,则可以将密钥放入密码保护的密钥库中,并在服务启动过程中输入密码。管理员需要重新启动您的应用程序,但是您对配置密码的保护非常好。

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.


回答 9

您的操作系统可能提供了用于安全加密数据的工具。例如,在Windows上有DPAPI(数据保护API)。为什么不在第一次运行时要求用户提供其凭据,然后将其松散加密以进行后续运行?

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?


回答 10

更多本地化的方式,而不是将身份验证/密码/用户名转换为加密的详细信息。FTPLIB只是示例。“ pass.csv ”是csv文件名

将密码保存为CSV格式,如下所示:

用户名

用户密码

(无列标题)

读取CSV并将其保存到列表中。

使用列表元素作为认证详细信息。

完整代码。

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])

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])

回答 11

这是我的摘录。您基本上是将函数导入或复制到代码中。如果加密文件不存在,getCredentials将创建该加密文件并返回命令,updateCredential将更新。

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()

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()

回答 12

将配置信息放置在加密的配置文件中。使用键在代码中查询此信息。将该密钥放在每个环境的单独文件中,不要将其与代码一起存储。

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.


回答 13

你知道坑吗?

https://pypi.python.org/pypi/pit(仅适用于py2(0.3版))

https://github.com/yoshiori/pit(它将在py3上运行(当前版本0.4))

test.py

from pit import Pit

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

跑:

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

〜/ .pit / default.yml:

section-name:
  password: my-password
  username: my-name

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

回答 14

如果在Windows上运行,则可以考虑使用win32crypt库。它允许运行脚本的用户存储和检索受保护的数据(键,密码),因此,密码永远不会以明文或混淆格式存储在代码中。我不确定其他平台是否有等效的实现,因此如果严格使用win32crypt,您的代码将无法移植。

我相信可以在这里获得该模块:http : //timgolden.me.uk/pywin32-docs/win32crypt.html

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


回答 15

我执行此操作的方法如下:

在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=='

然后,使用以下代码创建一个模块:

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')

完成此操作后,您可以直接导入mypassword,也可以导入令牌和密码以根据需要进行解密。

显然,这种方法有一些缺点。如果某人同时拥有令牌和密钥(就像他们拥有脚本一样),则他们可以轻松解密。但是,它的确模糊不清,如果您编译代码(使用Nuitka之类的代码),则至少您的密码不会在十六进制编辑器中显示为纯文本。

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.


回答 16

这并不能完全回答您的问题,但却是相关的。我本来想添加评论,但不允许。我一直在处理同一问题,因此我们决定使用Jenkins将脚本公开给用户。这使我们可以将数据库凭据存储在单独的文件中,该文件在服务器上已加密并受保护,并且非管理员无法访问。它还为我们提供了一些创建UI和限制执行的捷径。

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.


回答 17

您还可以考虑将密码存储在脚本外部并在运行时提供密码的可能性

例如fred.py

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

可以像

$ PASSWORD=password123 python fred.py
fred password123

可以通过使用base64(如上所述),在代码中使用不太明显的名称以及使实际密码与代码之间的距离进一步达到“通过模糊性实现安全性” 的目的。

如果代码位于存储库中,通常将机密存储在存储库之外很有用,因此可以将其添加到~/.bashrc(或添加到Vault或启动脚本中,…)

export SURNAME=cGFzc3dvcmQxMjM=

并更改fred.py

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

然后重新登录并

$ python fred.py
fred password123

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

回答 18

为什么不拥有简单的异或?

优点:

  • 看起来像二进制数据
  • 任何人都无法在不知道键的情况下读取它(即使它是一个字符)

我到了可以识别普通单词和rot13的简单b64字符串的地步。Xor会让它变得更加困难。

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.


回答 19

import base64
print(base64.b64encode("password".encode("utf-8")))
print(base64.b64decode(b'cGFzc3dvcmQ='.decode("utf-8")))
import base64
print(base64.b64encode("password".encode("utf-8")))
print(base64.b64decode(b'cGFzc3dvcmQ='.decode("utf-8")))

回答 20

在网上有几种用Python编写的ROT13实用程序-只是谷歌搜索它们。ROT13离线编码字符串,将其复制到源中,然后在传输点解码。

但这确实是薄弱的保护…

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…