私は小さなPythonスクリプトを書いています。これはユーザー名とパスワードのコンボを使用してサードパーティのサービスから定期的に情報を引き出します。100%防弾の何かを作成する必要はありません。 %さえ存在しますか?)、しかし、私は少なくとも誰かがそれを破るのに長い時間がかかるだろうので、セキュリティの良い手段を巻き込みたいです。
このスクリプトにはGUIがなく、cron
によって定期的に実行されるため、実行するたびにパスワードを入力して物事を解読することは実際には機能せず、ユーザー名とパスワードを保存する必要があります暗号化されたファイルまたはSQLiteデータベースで暗号化されたもののいずれか、とにかくSQLiteを使用することをお勧めし、Imightはパスワードを編集する必要がありますある時点で。さらに、この時点ではWindows専用であるため、プログラム全体をEXEでラップすることになるでしょう。
cron
ジョブを介して定期的に使用されるユーザー名とパスワードのコンボを安全に保存するにはどうすればよいですか?
ssh-agent に似た戦略をお勧めします。 ssh-agentを直接使用できない場合は、そのようなものを実装して、パスワードがRAMにのみ保持されるようにすることができます。 cronジョブは、実行するたびにエージェントから実際のパスワードを取得し、一度使用して、del
ステートメントを使用してすぐに参照解除するように資格情報を構成できます。
管理者は、ブート時など何でもssh-agentを起動するためにパスワードを入力する必要がありますが、これはディスク上のどこにでもプレーンテキストのパスワードを保存しないようにする合理的な妥協です。
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)
アイテムはユーザーのオペレーティングシステムの資格情報で暗号化されているため、ユーザーアカウントで実行されている他のアプリケーションはパスワードにアクセスできます。
その脆弱性を少しあいまいにするために、キーリングに保存する前に何らかの方法でパスワードを暗号化/難読化することができます。もちろん、スクリプトを対象とした人は誰でもソースを見てパスワードの暗号化を解除/解読する方法を理解することができますが、少なくとも一部のアプリケーションがボールト内のすべてのパスワードを吸い上げてパスワードを取得するのを防ぐことができます。
この質問と関連する質問への回答を調べた後、秘密データの暗号化と不明瞭化のために推奨されるいくつかの方法を使用して、いくつかのコードをまとめました。このコードは、ユーザーの介入なしにスクリプトを実行する必要がある場合に特に適しています(ユーザーが手動でスクリプトを開始する場合、パスワードを入力し、この質問の答えが示すようにメモリに保存することをお勧めします)。この方法は非常に安全ではありません。基本的に、スクリプトは秘密情報にアクセスできるため、システムに完全にアクセスできる人は誰でもスクリプトとそれに関連するファイルを持ち、それらにアクセスできます。 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許可が設定されている場合、およびスクリプト自体がコンパイルされ、実行可能のみ(読み取り不可)としてマークされている場合、大幅に改善されます。その一部は自動化できますが、気にしませんでした。おそらく、スクリプトのユーザーを設定し、そのユーザーとしてスクリプトを実行する必要があります(そして、そのユーザーにスクリプトのファイルの所有権を設定します)。
誰でも考えられる提案、批判、または脆弱性の他のポイントが欲しいです。私は暗号コードを書くのはかなり新しいので、私がやったことはほぼ確実に改善されるでしょう。
できる最善の方法は、スクリプトファイルとそれが実行されているシステムを保護することです。
基本的に次のことを行います。
Pythonプログラム、特にユーザーに入力を求めることができないバックグラウンドで実行する必要があるプログラム)が必要とするパスワードやその他の秘密を保存するためのオプションがいくつかありますパスワード。
避けるべき問題:
これは常にオプションとは限りませんが、おそらく最良の方法です。秘密鍵がネットワーク経由で送信されることはありません。SSHは数学計算を実行して、正しい鍵を持っていることを証明します。
動作させるには、次のものが必要です。
これは最も単純なので、開始するのに適した場所かもしれません。 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権限を持つユーザーはそれらを読むことができますが、それは仕方がありません。
これは環境変数に非常に似ていますが、テキストファイルから秘密を読み取ります。デプロイメントツールや継続的インテグレーションサーバーなどの環境変数は、より柔軟であることに変わりはありません。設定ファイルを使用することにした場合、Pythonは [〜#〜] json [〜#〜] 、 [〜#〜] ini [〜#〜] 、 netrc 、および [〜 #〜] xml [〜#〜] 。 PyYAML や [〜#〜] toml [ 〜#〜] 。個人的には、JSONとYAMLが最も簡単に使用でき、YAMLではコメントが許可されています。
構成ファイルで考慮すべき3つの事項:
~/.my_app
_のようなデフォルトの場所と、別の場所を使用するためのコマンドラインオプションかもしれません。一部のプロジェクトでは、秘密を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
_というファイルをコミットしたい場合があります。
この手法にはいくつかの問題があります。
.gitignore
_に追加すると、そのリスクが軽減されます。プロジェクトで既にこの手法を使用している場合、環境変数に簡単に移行できます。すべての設定値を環境変数に移動し、Pythonモジュールをこれらの環境変数から読み取るように変更します。
パスワードを暗号化しようとすることはあまり意味がありません:パスワードを隠そうとしている人はPythonスクリプトを持っています。これには暗号化を解除するコードがあります。サードパーティのサービスでパスワードを使用する直前に、Pythonスクリプトにprintステートメントを追加します。
そのため、パスワードを文字列としてスクリプトに保存し、base64でエンコードして、ファイルを読み取るだけでは不十分なため、1日で呼び出します。
オペレーティングシステムは、多くの場合、ユーザーのデータのセキュリティをサポートしています。 Windowsの場合、それは http://msdn.Microsoft.com/en-us/library/aa380261.aspx のように見えます
python http://vermeulen.ca/python-win32api.html を使用してwin32 apiを呼び出すことができます
私が理解している限り、これはデータを保存するので、保存に使用したアカウントからのみアクセスできます。データを編集する場合は、値を抽出、変更、保存するコードを記述することで編集できます。
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にコンパイルできます。