我需要在Python中安全地存储用户名和密码,我有什么select?

我正在写一个小的Python脚本,它将使用用户名和密码组合周期性地从第三方服务中提取信息。 我不需要创造一个100%防弹的东西(甚至100%是否存在?),但是我想涉及到一个很好的安全措施,所以至less需要很长时间来打破它。

这个脚本没有GUI,并且会被cron定期运行,所以每次运行解密的时候input密码都不行,我必须把用户名和密码保存在encryption文件中,或者encryption在一个SQLite数据库,这将是可取的,因为我将使用SQLite无论如何,我可能需要在某个时候编辑密码。 另外,我可能会把整个程序封装在一个EXE中,因为它现在是专门用于Windows的。

我怎样才能安全地存储用户名和密码组合定期通过cron工作使用?

我推荐一个类似于ssh-agent的策略。 如果你不能直接使用ssh-agent你可以实现类似的东西,所以你的密码只能保存在RAM中。 cron作业可以configuration凭据,以便每次运行时从代理获取实际密码,使用一次,并立即使用del语句取消引用。

pipe理员仍然需要在启动时input密码才能启动ssh-agent,但是这是一个合理的妥协,可以避免在磁盘上的任何地方存储明文密码。

在查看了这个问题和相关问题的答案之后,我使用了一些build议的方法来encryption和隐藏秘密数据。 这段代码专门用于脚本在没有用户干预的情况下运行(如果用户手动启动它,最好让他们把密码保存在内存中,因为这个问题的答案就是这样)。 这种方法不是超安全的; 从根本上讲,脚本可以访问秘密信息,以便具有完整系统访问权限的任何人都拥有脚本及其相关文件并可以访问它们。 这样做确实掩盖了偶然检查的数据,如果单独检查数据文件,或者在没有脚本的情况下一起检查,数据文件本身就是安全的。

我的动机是一个项目,轮询我的一些银行账户来监控交易 – 我需要它在后台运行,而不必每隔一两分钟重新input密码。

只需将此代码粘贴到脚本顶部,更改saltSeed,然后根据需要在代码中使用store()retrieve()和require():

 from getpass import getpass from pbkdf2 import PBKDF2 from Crypto.Cipher import AES import os import base64 import pickle ### Settings ### saltSeed = 'mkhgts465wef4fwtdd' # MAKE THIS YOUR OWN RANDOM STRING PASSPHRASE_FILE = './secret.p' SECRETSDB_FILE = './secrets' PASSPHRASE_SIZE = 64 # 512-bit passphrase KEY_SIZE = 32 # 256-bit key BLOCK_SIZE = 16 # 16-bit blocks IV_SIZE = 16 # 128-bits to initialise SALT_SIZE = 8 # 64-bits of salt ### System Functions ### def getSaltForKey(key): return PBKDF2(key, saltSeed).read(SALT_SIZE) # Salt is generated as the hash of the key with it's own salt acting like a seed value def encrypt(plaintext, salt): ''' Pad plaintext, then encrypt it with a new, randomly initialised cipher. Will not preserve trailing whitespace in plaintext!''' # Initialise Cipher Randomly initVector = os.urandom(IV_SIZE) # Prepare cipher key: key = PBKDF2(passphrase, salt).read(KEY_SIZE) cipher = AES.new(key, AES.MODE_CBC, initVector) # Create cipher return initVector + cipher.encrypt(plaintext + ' '*(BLOCK_SIZE - (len(plaintext) % BLOCK_SIZE))) # Pad and encrypt def decrypt(ciphertext, salt): ''' Reconstruct the cipher object and decrypt. Will not preserve trailing whitespace in the retrieved value!''' # Prepare cipher key: key = PBKDF2(passphrase, salt).read(KEY_SIZE) # Extract IV: initVector = ciphertext[:IV_SIZE] ciphertext = ciphertext[IV_SIZE:] cipher = AES.new(key, AES.MODE_CBC, initVector) # Reconstruct cipher (IV isn't needed for edecryption so is set to zeros) return cipher.decrypt(ciphertext).rstrip(' ') # Decrypt and depad ### User Functions ### def store(key, value): ''' Sore key-value pair safely and save to disk.''' global db db[key] = encrypt(value, getSaltForKey(key)) with open(SECRETSDB_FILE, 'w') as f: pickle.dump(db, f) def retrieve(key): ''' Fetch key-value pair.''' return decrypt(db[key], getSaltForKey(key)) def require(key): ''' Test if key is stored, if not, prompt the user for it while hiding their input from shoulder-surfers.''' if not key in db: store(key, getpass('Please enter a value for "%s":' % key)) ### Setup ### # Aquire passphrase: try: with open(PASSPHRASE_FILE) as f: passphrase = f.read() if len(passphrase) == 0: raise IOError except IOError: with open(PASSPHRASE_FILE, 'w') as f: passphrase = os.urandom(PASSPHRASE_SIZE) # Random passphrase f.write(base64.b64encode(passphrase)) try: os.remove(SECRETSDB_FILE) # If the passphrase has to be regenerated, then the old secrets file is irretrievable and should be removed except: pass else: passphrase = base64.b64decode(passphrase) # Decode if loaded from already extant file # Load or create secrets database: try: with open(SECRETSDB_FILE) as f: db = pickle.load(f) if db == {}: raise IOError except (IOError, EOFError): db = {} with open(SECRETSDB_FILE, 'w') as f: pickle.dump(db, f) ### Test (put your code here) ### require('id') require('password1') require('password2') print print 'Stored Data:' for key in db: print key, retrieve(key) # decode values on demand to avoid exposing the whole database in memory # DO STUFF 

如果在秘密文件上设置了os权限,只允许脚本本身读取它们,并且如果脚本本身被编译并标记为仅可执行(不可读),则此方法的安全性将显着提高。 其中一些可能是自动的,但我没有打扰。 这可能需要为该脚本设置用户并以该用户身份运行脚本(并将脚本文件的所有权设置为该用户)。

我喜欢任何人都能想到的build议,批评或其他弱点。 我对编写encryption代码相当陌生,所以我所做的几乎肯定可以改进。

我目前正在研究这个话题,以下似乎是我的需求的最佳解决scheme…也许这将为其他人碰到这个问题。

Python密钥环库与Windows上的CryptProtectData API集成,后者使用用户login凭据encryption数据。

用法很简单:

 keyring.set_password("system", "username", "password") keyring.get_password("system", "username") 

项目使用用户Windows凭据进行encryption,因此用户帐户中运行的其他应用程序将能够访问密码。

为了掩盖这个漏洞,您可以在将密码存储到密钥环之前以某种方式对密码进行encryption/模糊处理。 当然,任何以你的脚本为目标的人都可以查看源代码,并找出如何解密/解密密码,但是至less可以防止某些应用程序清空保险箱中的所有密码, 。

我认为你能做的最好的就是保护脚本文件和运行的系统。

基本上做到以下几点:

  • 使用文件系统权限(chmod 400)
  • 系统上所有者帐户的强密码
  • 降低系统受到攻击的能力(防火墙,禁用不需要的服务等)
  • 删除那些不需要它们的pipe理/ root / sudo权限

试图对密码进行encryption并没有多大意义:您试图隐藏密码的人拥有Python脚本,该脚本将拥有解密代码。 获取密码的最快方法是在与第三方服务一起使用密码之前向Python脚本添加打印语句。

因此,将密码作为string存储在脚本中,base64对它进行编码,以便只读取文件不够,然后每天调用它。

操作系统通常支持为用户保护数据。 在Windows的情况下,它看起来像是http://msdn.microsoft.com/en-us/library/aa380261.aspx

你可以使用http://vermeulen.ca/python-win32api.html从python调用win32 apis

据我所知,这将存储的数据,以便它只能从用于存储它的帐户访问。 如果要编辑数据,可以通过编写代码来提取,更改和保存值。

我使用密码术,因为我有麻烦安装(编译)我的系统上其他常见的库。 (Win7 x64,Python 3.5)

 from cryptography.fernet import Fernet key = Fernet.generate_key() cipher_suite = Fernet(key) cipher_text = cipher_suite.encrypt(b"password = scarybunny") plain_text = cipher_suite.decrypt(cipher_text) 

我的脚本运行在一个物理上安全的系统/房间里。 我使用“encryption程序脚本”将证书encryption到configuration文件中。 然后解密,当我需要使用它们。 “encryption脚本”不在真实的系统上,只有encryption的configuration文件是。 分析代码的人可以通过分析代码轻易破解encryption,但是如果需要的话,仍然可以将其编译为EXE。