【问题标题】:Hiding a password in a python script (insecure obfuscation only)在 python 脚本中隐藏密码(仅限不安全的混淆)
【发布时间】:2010-09-14 13:11:32
【问题描述】:

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

是否有一种简单的方法可以在文件中隐藏此密码(只是在我编辑文件时没有人可以读取密码)?

【问题讨论】:

  • 请记住,运行此文件的用户至少具有读取权限,并且可以轻松获取密码。如果只能由您阅读,并且您担心人们会在您的肩膀上看到它,那么请注意,当普通观察者无法以足够快的速度记住事物以获取密码时,任何有权访问脚本和一点点技术诀窍和一点野心就可以获取您的密码。始终非常仔细地考虑安全性,这很重要。

标签: python security


【解决方案1】:

Base64 encoding 在标准库中,可以阻止肩冲浪者:

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

【讨论】:

  • 我同意。 base64 编码的密码看起来更加神秘。
  • 但是,运行它的用户必须可以读取脚本并且密码不能读取,这无济于事。
  • 我不认为base64 在这种情况下比rot13 更容易混淆。相反,base64 具有其典型特征(等号,...),因此比其他方法更容易检测到。但是,任何混淆都没有实际好处。真的很糟糕,这个答案的评价如此之高。它只是给人一种虚假的安全感......
  • 如果您正在记录密码以便脚本可以使用它,那么任何有权访问该脚本的人都可以获取密码,无论您使用哪种加密方法。这里的要求只是对打开脚本的人隐藏密码。在这种情况下,base64rot13 更可取,因为它在 Python 标准库中。
  • base64 不是加密。充其量只是混淆。
【解决方案2】:

这是一个简单的方法:

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

这应该比 base64 解码更安全一些 - 尽管它容易受到 py_to_pyc 反编译器的攻击。

【讨论】:

  • 这个还是有一些不足的地方,但其实和我想要的很接近。它将允许我演示包含用户/密码连接的 python 脚本,而无需在屏幕上显示密码,也不必在命令提示符中输入密码。导入 peekaboo import peekabo 后,密码为 peekaboo.password(如果 peekaboo.py 包含 password='secret'
  • 如果您想更进一步,您可以使用Cython 将任何 .py 文件编译为 C 并生成特定于平台的二进制文件(例如:.pyd 用于 windows,.so 用于macOS等)...通过cythonizing您的脚本并共享生成的二进制文件,您将受益于这个答案+添加另一层混淆,因为现在您已经反编译C代码来获取密码。这不是 100% 安全的,但要获取您想要隐藏的敏感数据需要花费大量精力。
  • cythonizing,完美
  • 问题:使用十六进制编辑器打开 .pyd 文件并查看密码字符串。
【解决方案3】:

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

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

【讨论】:

  • 如何在不提供 root 或管理员密码的情况下以提升的权限运行脚本?是否与设置 UID 位有关?
  • 没关系,我想通了。对于其他关心的人:如果脚本设置了 setuid 位,则操作系统会将 setuid 位 'pass' 传递给解释器。不幸的是,存在巨大的安全漏洞,因此大多数现代发行版都关闭了脚本的 setuid。
  • 我找不到关于 --password-from-file 选项的任何信息。你有什么例子吗?谢谢!
  • @pyramidface - 我的意思是你会编写这样的功能并添加从文件中读取密码的功能
  • @MartinBeckett 但就像 Youarefunny 说的那样,您必须在 python 上提高 setuid 才能让脚本 root 访问密码文件?
【解决方案4】:

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

这是一个小的用法示例:

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

【讨论】:

    【解决方案5】:

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

    【讨论】:

      【解决方案6】:

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

      为自动化任务提供脚本凭据始终是一个冒险的提议。您的脚本应该有自己的凭据,并且它使用的帐户应该没有任何访问权限,除非是必需的。至少密码应该长且随机。

      【讨论】:

      • 非常好的答案 - 谢谢。对于我正在编写的小脚本(无论如何都是维护脚本 - BASE64 编码就足够了)
      • 这听起来不错,但你能给出一个示例实现吗?现在它只是对一般做法的描述,对以前没有这样做过的人没有那么有用。
      【解决方案7】:

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

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

      【讨论】:

      • 到底什么是傻?!整个回复,还是不导入的部分?
      • Base64 只是增加了安全的假象。
      • 乔纳森,你好像没有读过这个问题。这是关于晦涩(而且是非常临时的),而不是安全,所以我不明白为什么你认为我的回答没有帮助。
      • 我不知道您可以这样做,而不必使用 base64 模块。还有很多编码也像 zlib 一样...有趣:)
      • @Dennis 使用 base64 模块是当今的首选方式。后者在较新版本的 Python 中不再适用。
      【解决方案8】:

      对于 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 如何处理字节(以及相应的字符串)的更多信息,请参阅official documentation

      【讨论】:

        【解决方案9】:

        我这样做的方法如下:

        在 python 外壳中:

        >>> 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 之类的东西),至少您的密码不会在十六进制编辑器中显示为纯文本。

        【讨论】:

        【解决方案10】:

        这是一个很常见的问题。通常你能做的最好的就是要么

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

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

        如果您的应用程序作为服务/守护程序(如网络服务器)运行,那么您可以将密钥放入受密码保护的密钥库中,并在服务启动过程中输入密码。管理员需要重新启动您的应用程序,但您的配置密码将得到很好的保护。

        【讨论】:

          【解决方案11】:

          这是我的sn-p。您基本上将函数导入或复制到您的代码中。 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()
          

          【讨论】:

            【解决方案12】:

            您的操作系统可能提供了安全加密数据的功能。例如,在 Windows 上有 DPAPI(数据保护 API)。为什么不在您第一次运行时询问用户他们的凭据,然后将它们加密保存以供后续运行?

            【讨论】:

              【解决方案13】:

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

              【讨论】:

                【解决方案14】:

                更多的本土化方法,而不是将身份验证/密码/用户名转换为加密的详细信息。 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])
                

                【讨论】:

                  【解决方案15】:

                  你知道坑吗?

                  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
                  

                  【讨论】:

                  • Pit 没有任何文档
                  • 正如@successhawk 所指出的 - 我在这些 github/pypi 链接中没有看到任何关于“pit”的文档 - 但上面的描述很清楚 - 总的来说,我喜欢这个“隐藏”凭据的解决方案从简单的角度来看......
                  • 我不愿意使用未维护的模块,并且在尝试按照指示使用时出现错误:/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))
                  【解决方案16】:

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

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

                  【讨论】:

                    【解决方案17】:

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

                    例如弗雷德.py

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

                    可以像这样运行

                    $ PASSWORD=password123 python fred.py
                    fred password123
                    

                    通过使用base64(如上所述)、在代码中使用不太明显的名称并进一步远离代码中的实际密码,可以实现额外的“安全性”层。

                    如果代码在存储库中,store secrets outside it 通常很有用,因此可以将其添加到 ~/.bashrc(或保险库或启动脚本,...)

                    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
                    

                    【讨论】:

                    • 这个答案的第一部分关于从环境变量中检索密码对于大多数情况来说已经足够了,包括命令行工具和 Jenkins 服务器作业。唯一的改进可能是建议在运行脚本之前执行一次export VAR=val,作为对屏幕截图/肩膀冲浪者的轻微防御。关于混淆环境变量内容的第二部分并没有给 IMNSHO 增加太多价值。
                    【解决方案18】:

                    为什么没有一个简单的异或?

                    优点:

                    • 看起来像二进制数据
                    • 没有人可以在不知道密钥的情况下阅读它(即使它是单个字符)

                    我已经到了可以识别常见单词的简单 b64 字符串和 rot13 的程度。 Xor 会使它变得更加困难。

                    【讨论】:

                      【解决方案19】:

                      网络上有几个用 Python 编写的 ROT13 实用程序 - 只需 google 即可。 ROT13 离线编码字符串,将其复制到源中,在传输点解码。

                      但这真的弱保护...

                      【讨论】:

                      • 请包含链接或示例代码,以使此答案更有用
                      【解决方案20】:

                      这并不能准确回答您的问题,但它是相关的。我打算添加为评论,但不允许。 我一直在处理同样的问题,我们决定将脚本公开给使用 Jenkins 的用户。这允许我们将 db 凭据存储在单独的文件中,该文件在服务器上被加密和保护,非管理员无法访问。 它还为我们提供了一些创建 UI 和限制执行的捷径。

                      【讨论】:

                        【解决方案21】:
                        import base64
                        print(base64.b64encode("password".encode("utf-8")))
                        print(base64.b64decode(b'cGFzc3dvcmQ='.decode("utf-8")))
                        

                        【讨论】:

                        猜你喜欢
                        • 1970-01-01
                        • 2021-10-13
                        • 1970-01-01
                        • 2011-10-09
                        • 2016-06-05
                        • 2012-03-24
                        • 1970-01-01
                        • 2013-04-16
                        相关资源
                        最近更新 更多