在Python中如何安全存储用户名和密码?

135 投票
8 回答
165624 浏览
提问于 2025-04-16 23:18

我正在写一个小的Python脚本,这个脚本会定期从一个第三方服务获取信息,需要用到用户名和密码。我并不需要这个脚本做到万无一失(真的有那种东西吗?),但我希望能有一定的安全性,至少让别人破解它需要花费很长时间。

这个脚本不会有图形界面(GUI),会通过cron定期运行,所以每次运行时输入密码来解密是不太可行的。我需要把用户名和密码存储在一个加密的文件里,或者加密存储在SQLite数据库中。后者更好,因为我本来就会使用SQLite,而且我可能在某个时候需要修改密码。此外,我可能会把整个程序打包成一个EXE文件,因为现在这个程序只适用于Windows。

我该如何安全地存储用户名和密码,以便通过cron作业定期使用呢?

8 个回答

47

对于一个Python程序来说,存储密码和其他秘密信息有几种选择,尤其是那些需要在后台运行的程序,因为它们不能随时要求用户输入密码。

需要避免的问题:

  1. 不要把密码放到源代码管理中,这样其他开发者甚至公众都能看到。
  2. 同一服务器上的其他用户可以从配置文件或源代码中读取密码。
  3. 在编辑源文件时,其他人可以从你肩膀上看到密码。

选项1:SSH

这并不总是可行,但可能是最好的选择。你的私钥不会通过网络传输,SSH只是通过数学计算来证明你拥有正确的密钥。

要使其工作,你需要:

  • 你要访问的数据库或服务需要支持SSH。可以搜索“SSH”加上你要访问的服务,比如“ssh postgresql”。如果你的数据库不支持这个功能,就换下一个选项。
  • 创建一个账户来运行将调用数据库的服务,并生成一个SSH密钥
  • 要么把公钥添加到你要调用的服务中,要么在那个服务器上创建一个本地账户,并把公钥安装到那里。

选项2:环境变量

这是最简单的选项,所以可以从这里开始。这个方法在十二因素应用中描述得很好。基本思路是你的源代码从环境变量中获取密码或其他秘密信息,然后你在每个运行程序的系统上配置这些环境变量。如果能使用一些默认值,适合大多数开发者,那也是个不错的选择。但要注意,这样做可能会影响软件的“默认安全性”。

下面是一个从环境变量中获取服务器、用户名和密码的例子。

import os

server = os.getenv('MY_APP_DB_SERVER', 'localhost')
user = os.getenv('MY_APP_DB_USER', 'myapp')
password = os.getenv('MY_APP_DB_PASSWORD', '')

db_connect(server, user, password)

查一下如何在你的操作系统中设置环境变量,并考虑在自己的账户下运行服务。这样在你自己的账户中运行程序时,就不会有敏感数据在环境变量里。当你设置这些环境变量时,要特别小心,确保其他用户无法读取它们。比如检查文件权限。当然,拥有root权限的用户是可以读取的,这没办法。如果你使用systemd,可以查看服务单元,并小心使用EnvironmentFile而不是Environment来存放任何秘密信息。Environment的值可以被任何有systemctl show权限的用户查看。

选项3:配置文件

这与环境变量非常相似,但你是从文本文件中读取秘密信息。我发现环境变量在部署工具和持续集成服务器等方面更灵活。如果你决定使用配置文件,Python的标准库支持几种格式,比如TOMLJSONINInetrcXML。你还可以找到像PyYAML这样的外部包。个人而言,我觉得JSON和YAML最简单,YAML还允许注释。TOML在3.11版本中被加入到核心库,但我还没试过。

使用配置文件时要考虑三件事:

  1. 文件放在哪里?可以选择一个默认位置,比如~/.my_app,并提供一个命令行选项来使用不同的位置。
  2. 确保其他用户无法读取这个文件。
  3. 显然,不要把配置文件提交到源代码中。你可以提交一个模板,让用户可以复制到他们的主目录。

选项4:Python模块

有些项目直接把秘密信息放在一个Python模块里。

# settings.py
db_server = 'dbhost1'
db_user = 'my_app'
db_password = 'correcthorsebatterystaple'

然后导入这个模块来获取值。

# my_app.py
from settings import db_server, db_user, db_password

db_connect(db_server, db_user, db_password)

使用这种技术的一个项目是Django。显然,你不应该把settings.py提交到源代码管理中,虽然你可以提交一个名为settings_template.py的文件,用户可以复制并修改。

我觉得这种方法有几个问题:

  1. 开发者可能会不小心把文件提交到源代码管理中。把它添加到.gitignore可以降低这个风险。
  2. 你的一些代码不在源代码管理下。如果你很有纪律,只在这里放字符串和数字,那就没问题。如果你开始在这里写日志过滤类,那就停下吧!

如果你的项目已经使用了这种方法,转向使用环境变量会很简单。只需把所有设置值移动到环境变量中,并更改Python模块以从这些环境变量中读取。

80

python keyring库可以和Windows系统上的CryptProtectData接口配合使用(在Mac和Linux上也有相关接口),它的作用是用用户的登录信息来加密数据。

简单的用法:

import keyring

# the service is just a namespace for your app
service_id = 'IM_YOUR_APP!'

keyring.set_password(service_id, 'dustin', 'my secret password')
password = keyring.get_password(service_id, 'dustin') # retrieve password

如果你想把用户名存储在钥匙串里,可以这样用:

import keyring

MAGIC_USERNAME_KEY = 'im_the_magic_username_key'

# the service is just a namespace for your app
service_id = 'IM_YOUR_APP!'  

username = 'dustin'

# save password
keyring.set_password(service_id, username, "password")

# optionally, abuse `set_password` to save username onto keyring
# we're just using some known magic string in the username field
keyring.set_password(service_id, MAGIC_USERNAME_KEY, username)

之后你可以从钥匙串中获取你的信息:

# again, abusing `get_password` to get the username.
# after all, the keyring is just a key-value store
username = keyring.get_password(service_id, MAGIC_USERNAME_KEY)
password = keyring.get_password(service_id, username)  

这些信息是用用户的操作系统凭证加密的,因此在你账户下运行的其他应用程序也能访问这些密码。

为了稍微减少这个风险,你可以在把密码存储到钥匙串之前,先对它进行加密或混淆。当然,如果有人专门针对你的脚本,他们还是可以查看源代码,找到解密或解混淆的方法,但至少可以防止一些应用程序把钥匙串里的所有密码都抓取到,包括你的密码。

21

我建议使用一种类似于 ssh-agent 的策略。如果你不能直接使用 ssh-agent,可以实现一个类似的东西,这样你的密码就只会保存在内存中。定时任务可以配置好凭证,每次运行时从这个代理那里获取实际的密码,使用一次后,立刻用 del 语句将其删除。

管理员在启动 ssh-agent 时,还是需要输入密码,不管是在开机时还是其他时候,但这样做是一个合理的折中方案,可以避免在硬盘上存储明文密码。

撰写回答