web-dev-qa-db-ja.com

Pythonでユーザー名とパスワードを安全に保存する必要がありますが、私の選択肢は何ですか?

私は小さなPythonスクリプトを書いています。これはユーザー名とパスワードのコンボを使用してサードパーティのサービスから定期的に情報を引き出します。100%防弾の何かを作成する必要はありません。 %さえ存在しますか?)、しかし、私は少なくとも誰かがそれを破るのに長い時間がかかるだろうので、セキュリティの良い手段を巻き込みたいです。

このスクリプトにはGUIがなく、cronによって定期的に実行されるため、実行するたびにパスワードを入力して物事を解読することは実際には機能せず、ユーザー名とパスワードを保存する必要があります暗号化されたファイルまたはSQLiteデータベースで暗号化されたもののいずれか、とにかくSQLiteを使用することをお勧めし、Imightはパスワードを編集する必要がありますある時点で。さらに、この時点ではWindows専用であるため、プログラム全体をEXEでラップすることになるでしょう。

cronジョブを介して定期的に使用されるユーザー名とパスワードのコンボを安全に保存するにはどうすればよいですか?

67
Naftuli Kay

ssh-agent に似た戦略をお勧めします。 ssh-agentを直接使用できない場合は、そのようなものを実装して、パスワードがRAMにのみ保持されるようにすることができます。 cronジョブは、実行するたびにエージェントから実際のパスワードを取得し、一度使用して、delステートメントを使用してすぐに参照解除するように資格情報を構成できます。

管理者は、ブート時など何でもssh-agentを起動するためにパスワードを入力する必要がありますが、これはディスク上のどこにでもプレーンテキストのパスワードを保存しないようにする合理的な妥協です。

17
wberry

python keyring library は、ユーザーのログオン資格情報でデータを暗号化するWindowsの CryptProtectData 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)  

アイテムはユーザーのオペレーティングシステムの資格情報で暗号化されているため、ユーザーアカウントで実行されている他のアプリケーションはパスワードにアクセスできます。

その脆弱性を少しあいまいにするために、キーリングに保存する前に何らかの方法でパスワードを暗号化/難読化することができます。もちろん、スクリプトを対象とした人は誰でもソースを見てパスワードの暗号化を解除/解読する方法を理解することができますが、少なくとも一部のアプリケーションがボールト内のすべてのパスワードを吸い上げてパスワードを取得するのを防ぐことができます。

33
Dustin Wyatt

この質問と関連する質問への回答を調べた後、秘密データの暗号化と不明瞭化のために推奨されるいくつかの方法を使用して、いくつかのコードをまとめました。このコードは、ユーザーの介入なしにスクリプトを実行する必要がある場合に特に適しています(ユーザーが手動でスクリプトを開始する場合、パスワードを入力し、この質問の答えが示すようにメモリに保存することをお勧めします)。この方法は非常に安全ではありません。基本的に、スクリプトは秘密情報にアクセスできるため、システムに完全にアクセスできる人は誰でもスクリプトとそれに関連するファイルを持ち、それらにアクセスできます。 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許可が設定されている場合、およびスクリプト自体がコンパイルされ、実行可能のみ(読み取り不可)としてマークされている場合、大幅に改善されます。その一部は自動化できますが、気にしませんでした。おそらく、スクリプトのユーザーを設定し、そのユーザーとしてスクリプトを実行する必要があります(そして、そのユーザーにスクリプトのファイルの所有権を設定します)。

誰でも考えられる提案、批判、または脆弱性の他のポイントが欲しいです。私は暗号コードを書くのはかなり新しいので、私がやったことはほぼ確実に改善されるでしょう。

22
drodgers

できる最善の方法は、スクリプトファイルとそれが実行されているシステムを保護することです。

基本的に次のことを行います。

  • ファイルシステムのアクセス許可を使用する(chmod 400)
  • システム上の所有者のアカウントの強力なパスワード
  • システムが侵害される可能性を減らす(ファイアウォール、不要なサービスを無効にするなど)
  • 不要な管理者/ルート/ Sudo権限を削除します
6
Corey D

Pythonプログラム、特にユーザーに入力を求めることができないバックグラウンドで実行する必要があるプログラム)が必要とするパスワードやその他の秘密を保存するためのオプションがいくつかありますパスワード。

避けるべき問題:

  1. 他の開発者や一般の人も見ることができるソース管理へのパスワードのチェックイン。
  2. 同じサーバー上の他のユーザーが、構成ファイルまたはソースコードからパスワードを読み取ります。
  3. あなたがそれを編集している間、他の人があなたの肩越しにそれを見ることができるソースファイルにパスワードを持っています。

オプション1:SSH

これは常にオプションとは限りませんが、おそらく最良の方法です。秘密鍵がネットワーク経由で送信されることはありません。SSHは数学計算を実行して、正しい鍵を持っていることを証明します。

動作させるには、次のものが必要です。

  • データベースまたはアクセスするものはすべてSSHでアクセスできる必要があります。 「SSH」に加えて、アクセスしているサービスを検索してみてください。たとえば、 "ssh postgresql" 。これがデータベースの機能でない場合は、次のオプションに進みます。
  • データベースを呼び出して SSHキーを生成するサービスを実行するアカウントを作成します
  • 呼び出すサービスに公開キーを追加するか、そのサーバーにローカルアカウントを作成して、そこに公開キーをインストールします。

オプション2:環境変数

これは最も単純なので、開始するのに適した場所かもしれません。 Twelve Factor App で詳しく説明されています。基本的な考え方は、ソースコードが環境変数からパスワードまたはその他の秘密を取得し、プログラムを実行する各システムでそれらの環境変数を構成するというものです。また、ほとんどの開発者に有効なデフォルト値を使用する場合、いい感じかもしれません。ソフトウェアを「デフォルトでセキュア」にすることとバランスを取る必要があります。

環境変数からサーバー、ユーザー名、パスワードを取得する例を次に示します。

_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権限を持つユーザーはそれらを読むことができますが、それは仕方がありません。

オプション3:構成ファイル

これは環境変数に非常に似ていますが、テキストファイルから秘密を読み取ります。デプロイメントツールや継続的インテグレーションサーバーなどの環境変数は、より柔軟であることに変わりはありません。設定ファイルを使用することにした場合、Pythonは [〜#〜] json [〜#〜][〜#〜] ini [〜#〜]netrc 、および [〜 #〜] xml [〜#〜]PyYAML[〜#〜] toml [ 〜#〜] 。個人的には、JSONとYAMLが最も簡単に使用でき、YAMLではコメントが許可されています。

構成ファイルで考慮すべき3つの事項:

  1. ファイルはどこにありますか? _~/.my_app_のようなデフォルトの場所と、別の場所を使用するためのコマンドラインオプションかもしれません。
  2. 他のユーザーがファイルを読み取れないことを確認してください。
  3. 明らかに、構成ファイルをソースコードにコミットしないでください。ユーザーが自分のホームディレクトリにコピーできるテンプレートをコミットすることもできます。

オプション4:Python Module

一部のプロジェクトでは、秘密を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)
_

この手法を使用するプロジェクトの1つは、 Django です。明らかに、ソース管理に_settings.py_をコミットするべきではありませんが、ユーザーがコピーおよび変更できる_settings_template.py_というファイルをコミットしたい場合があります。

この手法にはいくつかの問題があります。

  1. 開発者が誤ってファイルをソース管理にコミットする場合があります。 _.gitignore_に追加すると、そのリスクが軽減されます。
  2. 一部のコードはソース管理下にありません。規律があり、ここに文字列と数字だけを入れれば、それは問題になりません。ここでロギングフィルタクラスの作成を開始する場合は、やめてください!

プロジェクトで既にこの手法を使用している場合、環境変数に簡単に移行できます。すべての設定値を環境変数に移動し、Pythonモジュールをこれらの環境変数から読み取るように変更します。

6
Don Kirkby

パスワードを暗号化しようとすることはあまり意味がありません:パスワードを隠そうとしている人はPythonスクリプトを持っています。これには暗号化を解除するコードがあります。サードパーティのサービスでパスワードを使用する直前に、Pythonスクリプトにprintステートメントを追加します。

そのため、パスワードを文字列としてスクリプトに保存し、base64でエンコードして、ファイルを読み取るだけでは不十分なため、1日で呼び出します。

4
Ned Batchelder

オペレーティングシステムは、多くの場合、ユーザーのデータのセキュリティをサポートしています。 Windowsの場合、それは http://msdn.Microsoft.com/en-us/library/aa380261.aspx のように見えます

python http://vermeulen.ca/python-win32api.html を使用してwin32 apiを呼び出すことができます

私が理解している限り、これはデータを保存するので、保存に使用したアカウントからのみアクセスできます。データを編集する場合は、値を抽出、変更、保存するコードを記述することで編集できます。

1
andrew cooke

Cryptography を使用しました。これは、システムに一般的に言及されている他のライブラリのインストール(コンパイル)に問題があったためです。 (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)

私のスクリプトは物理的に安全なシステム/部屋で実行されています。 「暗号化スクリプト」で資格情報を構成ファイルに暗号化します。そして、それらを使用する必要があるときに解読します。 「暗号化スクリプト」は実際のシステムではなく、暗号化された構成ファイルのみです。コードを分析する人は、コードを分析することで暗号化を簡単に破ることができますが、必要に応じてそれをEXEにコンパイルできます。

0
KRBA