developer tip

사용자 이름과 비밀번호를 Python에 안전하게 저장해야합니다. 옵션은 무엇입니까?

optionbox 2020. 11. 2. 07:52
반응형

사용자 이름과 비밀번호를 Python에 안전하게 저장해야합니다. 옵션은 무엇입니까?


사용자 이름과 암호 콤보를 사용하여 타사 서비스에서 주기적으로 정보를 가져 오는 작은 Python 스크립트를 작성 중입니다. 100 % 방탄이되는 무언가를 만들 필요는 없지만 (100 %도 존재합니까?) 보안 조치를 취하고 싶으므로 적어도 누군가가 그것을 깨는 데 오랜 시간이 걸립니다.

이 스크립트는 GUI가 없으며에서 주기적으로 실행 cron되므로 암호를 해독하기 위해 실행할 때마다 암호를 입력하는 것은 실제로 작동하지 않으며 사용자 이름과 암호를 암호화 된 파일 또는 암호화 된 파일에 저장해야합니다. 어쨌든 SQLite를 사용할 것이므로 SQLite 데이터베이스에서 선호되며 어느 시점에서 암호를 편집해야 할 수도 있습니다 . 또한이 시점에서 Windows 전용이므로 전체 프로그램을 EXE로 래핑 할 것입니다.

cron작업을 통해 주기적으로 사용할 사용자 이름 및 비밀번호 콤보를 안전하게 저장하려면 어떻게 해야합니까?


ssh-agent 와 유사한 전략을 권장합니다 . ssh-agent를 직접 사용할 수없는 경우 이와 유사한 것을 구현하여 암호가 RAM에만 유지되도록 할 수 있습니다. 크론 작업은 에이전트가 실행될 때마다 에이전트에서 실제 암호를 가져오고 한 번 사용하고 del명령문을 사용하여 즉시 참조 해제하도록 자격 증명을 구성 할 수 있습니다 .

관리자는 부팅시 또는 무엇이든간에 ssh-agent를 시작하기 위해 여전히 암호를 입력해야하지만 이는 디스크에 일반 텍스트 암호가 저장되는 것을 방지하는 합리적인 절충안입니다.


라이브러리 열쇠 고리 파이썬 와 통합하고 CryptProtectDataWindows의 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)  

항목은 사용자의 운영 체제 자격 증명으로 암호화되므로 사용자 계정에서 실행중인 다른 응용 프로그램이 암호에 액세스 할 수 있습니다.

이 취약점을 조금 숨기려면 암호를 키링에 저장하기 전에 어떤 방식 으로든 암호화 / 난독화할 수 있습니다. 물론, 스크립트를 대상으로하는 사람은 누구나 소스를보고 암호를 해독 / 난독 화 해제하는 방법을 알아낼 수 있지만, 적어도 일부 응용 프로그램이 볼트의 모든 암호를 비우고 자신의 암호를 가져 오는 것을 방지 할 수 있습니다. .


이 질문과 관련 질문에 대한 답변을 살펴본 후 비밀 데이터를 암호화하고 숨기는 몇 가지 제안 된 방법을 사용하여 코드를 작성했습니다. 이 코드는 특히 사용자 개입없이 스크립트를 실행해야하는 경우를위한 것입니다 (사용자가 수동으로 시작하는 경우 암호를 입력하고이 질문에 대한 답변에서 알 수 있듯이 메모리에만 유지하는 것이 가장 좋습니다). 이 방법은 매우 안전하지 않습니다. 기본적으로 스크립트는 비밀 정보에 액세스 할 수 있으므로 전체 시스템 액세스 권한이있는 모든 사용자가 스크립트 및 관련 파일을 갖고 액세스 할 수 있습니다. 이것이 수행하는 작업 id는 일상적인 검사에서 데이터를 모호하게하고 데이터 파일을 개별적으로 검사하거나 스크립트없이 함께 검사하는 경우 데이터 파일 자체를 안전하게 유지합니다.

이것에 대한 동기는 거래를 모니터링하기 위해 은행 계좌 중 일부를 조사하는 프로젝트입니다. 1 ~ 2 분마다 비밀번호를 다시 입력하지 않고 백그라운드에서 실행해야합니다.

이 코드를 스크립트 상단에 붙여넣고 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 권한이 설정되어 있고 스크립트 자체가 컴파일되고 실행 가능 (읽을 수 없음)으로 표시된 경우이 방법의 보안이 크게 향상됩니다. 그 중 일부는 자동화 될 수 있지만 신경 쓰지 않았습니다. 스크립트에 대한 사용자를 설정하고 해당 사용자로 스크립트를 실행해야합니다 (그리고 스크립트 파일의 소유권을 해당 사용자에게 설정).

I'd love any suggestions, criticisms or other points of vulnerability that anyone can think of. I'm pretty new to writing crypto code so what I've done could almost certainly be improved.


There's not much point trying to encrypt the password: the person you're trying to hide it from has the Python script, which will have the code to decrypt it. The fastest way to get the password will be to add a print statement to the Python script just before it uses the password with the third-party service.

So store the password as a string in the script, and base64 encode it so that just reading the file isn't enough, then call it a day.


I think the best you can do is protect the script file and system it's running on.

Basically do the following:

  • Use file system permissions (chmod 400)
  • Strong password for owner's account on the system
  • Reduce ability for system to be compromised (firewall, disable unneeded services, etc)
  • Remove administrative/root/sudo privileges for those that do not need it

There are a few options for storing passwords and other secrets that a Python program needs to use, particularly a program that needs to run in the background where it can't just ask the user to type in the password.

Problems to avoid:

  1. Checking the password in to source control where other developers or even the public can see it.
  2. Other users on the same server reading the password from a configuration file or source code.
  3. Having the password in a source file where others can see it over your shoulder while you are editing it.

Option 1: SSH

This isn't always an option, but it's probably the best. Your private key is never transmitted over the network, SSH just runs mathematical calculations to prove that you have the right key.

In order to make it work, you need the following:

  • The database or whatever you are accessing needs to be accessible by SSH. Try searching for "SSH" plus whatever service you are accessing. For example, "ssh postgresql". If this isn't a feature on your database, move on to the next option.
  • Create an account to run the service that will make calls to the database, and generate an SSH key.
  • Either add the public key to the service you're going to call, or create a local account on that server, and install the public key there.

Option 2: Environment Variables

This one is the simplest, so it might be a good place to start. It's described well in the Twelve Factor App. The basic idea is that your source code just pulls the password or other secrets from environment variables, and then you configure those environment variables on each system where you run the program. It might also be a nice touch if you use default values that will work for most developers. You have to balance that against making your software "secure by default".

Here's an example that pulls the server, user name, and password from environment variables.

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)

Look up how to set environment variables in your operating system, and consider running the service under its own account. That way you don't have sensitive data in environment variables when you run programs in your own account. When you do set up those environment variables, take extra care that other users can't read them. Check file permissions, for example. Of course any users with root permission will be able to read them, but that can't be helped.

Option 3: Configuration Files

This is very similar to the environment variables, but you read the secrets from a text file. I still find the environment variables more flexible for things like deployment tools and continuous integration servers. If you decide to use a configuration file, Python supports several formats in the standard library, like JSON, INI, netrc, and XML. You can also find external packages like PyYAML and TOML. Personally, I find JSON and YAML the simplest to use, and YAML allows comments.

Three things to consider with configuration files:

  1. Where is the file? Maybe a default location like ~/.my_app, and a command-line option to use a different location.
  2. Make sure other users can't read the file.
  3. Obviously, don't commit the configuration file to source code. You might want to commit a template that users can copy to their home directory.

Option 4: Python Module

Some projects just put their secrets right into a Python module.

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

Then import that module to get the values.

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

db_connect(db_server, db_user, db_password)

One project that uses this technique is Django. Obviously, you shouldn't commit settings.py to source control, although you might want to commit a file called settings_template.py that users can copy and modify.

I see a few problems with this technique:

  1. Developers might accidentally commit the file to source control. Adding it to .gitignore reduces that risk.
  2. Some of your code is not under source control. If you're disciplined and only put strings and numbers in here, that won't be a problem. If you start writing logging filter classes in here, stop!

If your project already uses this technique, it's easy to transition to environment variables. Just move all the setting values to environment variables, and change the Python module to read from those environment variables.


operating systems often have support for securing data for the user. in the case of windows it looks like it's http://msdn.microsoft.com/en-us/library/aa380261.aspx

you can call win32 apis from python using http://vermeulen.ca/python-win32api.html

as far as i understand, this will store the data so that it can be accessed only from the account used to store it. if you want to edit the data you can do so by writing code to extract, change and save the value.


I used Cryptography because I had troubles installing (compiling) other commonly mentioned libraries on my system. (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)

My script is running in a physically secure system/room. I encrypt credentials with an "encrypter script" to a config file. And then decrypt when I need to use them. "Encrypter script" is not on the real system, only encrypted config file is. Someone who analyses the code can easily break the encryption by analysing the code, but you can still compile it into an EXE if necessary.

참고 URL : https://stackoverflow.com/questions/7014953/i-need-to-securely-store-a-username-and-password-in-python-what-are-my-options

반응형