为了账号安全,请及时绑定邮箱和手机立即绑定

我需要在Python中安全地存储用户名和密码,我有哪些选择?

/ 猿问

我需要在Python中安全地存储用户名和密码,我有哪些选择?

慕工程0101907 2019-08-06 17:30:41

我需要在Python中安全地存储用户名和密码,我有哪些选择?

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

这个脚本没有GUI,并且会定期运行cron,因此每次运行密码时输入密码都不会真正起作用,我必须将用户名和密码存储在加密文件中或加密在SQLite数据库中,这将是更好的,因为我将使用SQLite,我可能需要在某些时候编辑密码。另外,我可能会将整个程序包装在EXE中,因为此时它专门用于Windows。

如何安全地存储用户名和密码组合以通过cron作业定期使用?


查看完整描述

3 回答

?
一只名叫tom的猫

该蟒蛇钥匙圈库与整合CryptProtectData进行加密与用户的登录凭据数据在Windows API(兼容Mac和Linux相关的API一起)。


用法简单:


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)  

项目使用用户的操作系统凭据进行加密,因此在您的用户帐户中运行的其他应用程序将能够访问密码。


为了模糊该漏洞,您可以在将密码存储到密钥环之前以某种方式加密/混淆密码。当然,任何针对您的脚本的人都可以查看源代码并找出如何解密/取消混淆密码,但是您至少会阻止某些应用程序清空Vault中的所有密码并获取您的密码。 。


查看完整回答
反对 回复 2019-08-06
?
慕容森

在查看了这个问题和相关问题的答案后,我使用一些建议的加密和模糊秘密数据的方法汇总了一些代码。此代码专门用于脚本必须在没有用户干预的情况下运行的情况(如果用户手动启动它,最好将它们放入密码并仅将其保留在内存中,因为此问题的答案表明)。这种方法不是超级安全的; 从根本上说,脚本可以访问秘密信息,因此任何拥有完整系统访问权限的人都可以访问脚本及其相关文件。这样做会使得来自临时检查的数据变得模糊,如果单独检查数据文件或者没有脚本一起检查数据文件本身就是安全的。

我的动机是一个项目,它调查我的一些银行账户来监控交易 - 我需要它在后台运行,而不是每隔一两分钟重新输入密码。

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

from getpass import getpassfrom pbkdf2 import PBKDF2from Crypto.Cipher import AESimport osimport base64import pickle### Settings ###saltSeed = 'mkhgts465wef4fwtdd' # MAKE THIS YOUR OWN RANDOM STRINGPASSPHRASE_FILE = './secret.p'SECRETSDB_FILE = './secrets'PASSPHRASE_SIZE = 64 # 512-bit passphraseKEY_SIZE = 32 # 256-bit keyBLOCK_SIZE = 16  # 16-bit blocksIV_SIZE = 16 # 128-bits to initialiseSALT_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 valuedef 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 encryptdef 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 IOErrorexcept 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: passelse:
    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 IOErrorexcept (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')printprint '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权限,只允许脚本本身读取它们,并且脚本本身被编译并标记为仅可执行(不可读),则此方法的安全性将得到显着提高。其中一些可以自动化,但我没有打扰。可能需要为脚本设置用户并以该用户身份运行脚本(并将脚本文件的所有权设置为该用户)。

我喜欢任何人都能想到的任何建议,批评或其他漏洞。我是编写加密代码的新手,所以我所做的几乎肯定会得到改进。


查看完整回答
反对 回复 2019-08-06

添加回答

回复

举报

0/150
提交
取消
意见反馈 帮助中心 APP下载
官方微信