Saya perlu menyimpan nama pengguna dan kata sandi dengan aman di Python, apa saja pilihan saya? [Tutup]

95

Saya sedang menulis skrip Python kecil yang secara berkala akan menarik informasi dari layanan pihak ketiga menggunakan kombinasi nama pengguna dan kata sandi. Saya tidak perlu membuat sesuatu yang 100% antipeluru (apakah 100% benar-benar ada?), Tetapi saya ingin melibatkan ukuran keamanan yang baik sehingga setidaknya akan membutuhkan waktu lama bagi seseorang untuk memecahkannya.

Skrip ini tidak memiliki GUI dan akan dijalankan secara berkala cron, jadi memasukkan kata sandi setiap kali dijalankan untuk mendekripsi tidak akan benar-benar berfungsi, dan saya harus menyimpan nama pengguna dan kata sandi dalam file terenkripsi atau terenkripsi dalam database SQLite, yang lebih disukai karena saya akan tetap menggunakan SQLite, dan saya mungkin perlu mengedit kata sandi di beberapa titik. Selain itu, saya mungkin akan membungkus seluruh program dalam EXE, karena ini khusus untuk Windows pada saat ini.

Bagaimana saya bisa menyimpan kombinasi nama pengguna dan kata sandi dengan aman untuk digunakan secara berkala melalui cronpekerjaan?

Naftuli Kay
sumber
Lihat juga : stackoverflow.com/questions/157938
dreftymac

Jawaban:

19

Saya merekomendasikan strategi yang mirip dengan ssh-agent . Jika Anda tidak dapat menggunakan ssh-agent secara langsung, Anda dapat menerapkan sesuatu seperti itu, sehingga kata sandi Anda hanya disimpan di RAM. Tugas cron bisa saja mengonfigurasi kredensial untuk mendapatkan kata sandi sebenarnya dari agen setiap kali dijalankan, gunakan sekali, dan segera hapus referensi dengan menggunakan delpernyataan.

Administrator masih harus memasukkan kata sandi untuk memulai ssh-agent, pada saat boot atau apa pun, tetapi ini adalah kompromi yang wajar yang menghindari penyimpanan kata sandi teks biasa di mana saja di disk.

wberry
sumber
2
+1, itu sangat masuk akal. Saya selalu bisa membangun UI untuk itu yang pada dasarnya meminta kata sandi pengguna saat boot, dengan cara itu tidak pernah disimpan di disk dan aman dari pengintaian.
Naftuli Kay
56

The python keyring perpustakaan terintegrasi dengan CryptProtectDataAPI pada Windows (bersama dengan API yang relevan pada Mac dan Linux) yang mengenkripsi data dengan kredensial masuk pengguna.

Penggunaan sederhana:

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

Penggunaan jika Anda ingin menyimpan nama pengguna di keyring:

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)

Nanti untuk mendapatkan info Anda dari keyring

# 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)  

Item dienkripsi dengan kredensial sistem operasi pengguna, sehingga aplikasi lain yang berjalan di akun pengguna Anda dapat mengakses sandi.

Untuk sedikit mengaburkan kerentanan itu, Anda dapat mengenkripsi / mengaburkan kata sandi dengan beberapa cara sebelum menyimpannya di keyring. Tentu saja, siapa pun yang menargetkan skrip Anda hanya akan dapat melihat sumbernya dan mencari cara untuk membatalkan enkripsi / menyamarkan kata sandi, tetapi Anda setidaknya akan mencegah beberapa aplikasi menyedot semua kata sandi di lemari besi dan mendapatkan milik Anda juga. .

Dustin Wyatt
sumber
Bagaimana seharusnya nama pengguna disimpan? Apakah keyringmendukung pengambilan nama pengguna dan kata sandi?
Stevoisiak
1
@DustinWyatt Pintar menggunakan get_passworduntuk nama pengguna. Meskipun, saya pikir Anda harus memulai jawabannya dengan contoh asli yang disederhanakan keyring.set_password()dankeyring.get_password()
Stevoisiak
keyringbukan bagian dari pustaka standar python
Ciasto piekarz
@Ciastopiekarz Apakah ada sesuatu tentang jawaban yang membuat Anda percaya bahwa itu adalah bagian dari perpustakaan standar?
Dustin Wyatt
Apakah keyringdengan aman menghapus kata sandi dari log dan memori setelah kata sandi?
Kebman
26

Setelah melihat jawaban untuk ini dan pertanyaan terkait, saya telah mengumpulkan beberapa kode menggunakan beberapa metode yang disarankan untuk mengenkripsi dan menyembunyikan data rahasia. Kode ini khusus untuk saat skrip harus dijalankan tanpa campur tangan pengguna (jika pengguna memulainya secara manual, yang terbaik adalah memasukkannya ke dalam kata sandi dan hanya menyimpannya di memori seperti yang disarankan oleh jawaban atas pertanyaan ini). Metode ini tidak super aman; pada dasarnya, skrip dapat mengakses info rahasia sehingga siapa pun yang memiliki akses sistem penuh memiliki skrip dan file yang terkait serta dapat mengaksesnya. Apa yang dilakukannya ini mengaburkan data dari inspeksi biasa dan membiarkan file data itu sendiri aman jika diperiksa satu per satu, atau bersama-sama tanpa skrip.

Motivasi saya untuk ini adalah proyek yang mengumpulkan beberapa rekening bank saya untuk memantau transaksi - saya membutuhkannya untuk berjalan di latar belakang tanpa saya memasukkan kembali kata sandi setiap satu atau dua menit.

Cukup tempelkan kode ini di bagian atas skrip Anda, ubah saltSeed, lalu gunakan store () mengambil () dan require () di kode Anda sesuai kebutuhan:

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

Keamanan metode ini akan ditingkatkan secara signifikan jika izin os ditetapkan pada file rahasia untuk hanya mengizinkan skrip itu sendiri untuk membacanya, dan jika skrip itu sendiri dikompilasi dan ditandai sebagai hanya dapat dieksekusi (tidak dapat dibaca). Beberapa di antaranya bisa otomatis, tapi saya tidak ambil pusing. Ini mungkin memerlukan pengaturan pengguna untuk skrip dan menjalankan skrip sebagai pengguna itu (dan pengaturan kepemilikan file skrip untuk pengguna itu).

Saya menyukai saran, kritik, atau poin kerentanan lainnya yang dapat dipikirkan oleh siapa pun. Saya cukup baru dalam menulis kode kripto jadi apa yang telah saya lakukan hampir pasti dapat ditingkatkan.

drodgers
sumber
26

Ada beberapa opsi untuk menyimpan kata sandi dan rahasia lain yang perlu digunakan oleh program Python, terutama program yang perlu dijalankan di latar belakang di mana ia tidak bisa hanya meminta pengguna untuk mengetikkan kata sandi.

Masalah yang harus dihindari:

  1. Memeriksa sandi untuk mengontrol sumber di mana pengembang lain atau bahkan publik dapat melihatnya.
  2. Pengguna lain di server yang sama membaca sandi dari file konfigurasi atau kode sumber.
  3. Memiliki kata sandi dalam file sumber di mana orang lain dapat melihatnya dari balik bahu Anda saat Anda mengeditnya.

Opsi 1: SSH

Ini tidak selalu menjadi pilihan, tapi mungkin yang terbaik. Kunci pribadi Anda tidak pernah dikirim melalui jaringan, SSH hanya menjalankan penghitungan matematis untuk membuktikan bahwa Anda memiliki kunci yang benar.

Untuk membuatnya berfungsi, Anda membutuhkan yang berikut:

  • Basis data atau apa pun yang Anda akses harus dapat diakses oleh SSH. Coba telusuri "SSH" plus layanan apa pun yang Anda akses. Misalnya, "ssh postgresql" . Jika ini bukan fitur di database Anda, lanjutkan ke opsi berikutnya.
  • Buat akun untuk menjalankan layanan yang akan melakukan panggilan ke database, dan buat kunci SSH .
  • Tambahkan kunci publik ke layanan yang akan Anda panggil, atau buat akun lokal di server itu, dan instal kunci publik di sana.

Opsi 2: Variabel Lingkungan

Yang ini adalah yang paling sederhana, jadi ini mungkin tempat yang baik untuk memulai. Ini dijelaskan dengan baik di Aplikasi Dua Belas Faktor . Ide dasarnya adalah bahwa kode sumber Anda hanya menarik kata sandi atau rahasia lain dari variabel lingkungan, dan kemudian Anda mengkonfigurasi variabel lingkungan tersebut di setiap sistem tempat Anda menjalankan program. Mungkin juga sentuhan yang bagus jika Anda menggunakan nilai default yang akan berfungsi untuk sebagian besar pengembang. Anda harus menyeimbangkannya dengan membuat perangkat lunak Anda "aman secara default".

Berikut adalah contoh yang menarik server, nama pengguna, dan kata sandi dari variabel lingkungan.

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)

Cari tahu cara menyetel variabel lingkungan di sistem operasi Anda, dan pertimbangkan untuk menjalankan layanan dengan akunnya sendiri. Dengan begitu Anda tidak memiliki data sensitif dalam variabel lingkungan saat Anda menjalankan program di akun Anda sendiri. Saat Anda menyiapkan variabel lingkungan tersebut, berhati-hatilah agar pengguna lain tidak dapat membacanya. Periksa izin file, misalnya. Tentu saja setiap pengguna dengan izin root akan dapat membacanya, tetapi itu tidak dapat membantu. Jika Anda menggunakan systemd, lihat unit layanan , dan berhati-hatilah untuk menggunakan EnvironmentFiledaripada Environmentmencari rahasia. Environmentnilai dapat dilihat oleh setiap pengguna dengan systemctl show.

Opsi 3: File Konfigurasi

Ini sangat mirip dengan variabel lingkungan, tetapi Anda membaca rahasia dari file teks. Saya masih menemukan variabel lingkungan lebih fleksibel untuk hal-hal seperti alat penerapan dan server integrasi berkelanjutan. Jika Anda memutuskan untuk menggunakan file konfigurasi, Python mendukung beberapa format di pustaka standar, seperti JSON , INI , netrc , dan XML . Anda juga dapat menemukan paket eksternal seperti PyYAML dan TOML . Secara pribadi, saya menemukan JSON dan YAML yang paling sederhana untuk digunakan, dan YAML mengizinkan komentar.

Tiga hal yang perlu dipertimbangkan dengan file konfigurasi:

  1. Dimana filenya? Mungkin lokasi default seperti ~/.my_app, dan opsi baris perintah untuk menggunakan lokasi yang berbeda.
  2. Pastikan pengguna lain tidak dapat membaca file.
  3. Jelas, jangan memasukkan file konfigurasi ke kode sumber. Anda mungkin ingin memasukkan template yang dapat disalin pengguna ke direktori home mereka.

Opsi 4: Modul Python

Beberapa proyek hanya memasukkan rahasia mereka ke dalam modul Python.

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

Kemudian impor modul itu untuk mendapatkan nilainya.

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

db_connect(db_server, db_user, db_password)

Satu proyek yang menggunakan teknik ini adalah Django . Jelas, Anda tidak boleh berkomitmen settings.pypada kontrol sumber, meskipun Anda mungkin ingin memasukkan file bernama settings_template.pyyang dapat disalin dan dimodifikasi pengguna.

Saya melihat beberapa masalah dengan teknik ini:

  1. Pengembang mungkin tidak sengaja memasukkan file ke kontrol sumber. Menambahkannya untuk .gitignoremengurangi risiko itu.
  2. Beberapa kode Anda tidak di bawah kendali sumber. Jika Anda disiplin dan hanya memasukkan string dan angka di sini, itu tidak akan menjadi masalah. Jika Anda mulai menulis kelas filter logging di sini, berhenti!

Jika proyek Anda sudah menggunakan teknik ini, mudah untuk beralih ke variabel lingkungan. Pindahkan saja semua nilai pengaturan ke variabel lingkungan, dan ubah modul Python untuk membaca dari variabel lingkungan tersebut.

Don Kirkby
sumber
Halo. Jika proyek Anda sudah menggunakan teknik ini, mudah untuk beralih ke variabel lingkungan. Saya tahu cara mengatur variabel lingkungan di Windows 10 secara manual tetapi dapat mengaksesnya dari kode python saya menggunakan os.getenv(). Bagaimana kita harus melakukan ini jika kode tersebut dibagikan? Jika kode diunduh oleh pengembang lain, bagaimana dia harus memastikan variabel lingkungan sudah ditetapkan untuknya?
a_sid
Saya mencoba untuk mengirimkan nilai default yang masuk akal ke os.getenv(), @a_sid, jadi kode setidaknya akan berjalan untuk pengguna yang belum mengatur variabel lingkungan. Jika tidak ada nilai default yang baik, naikkan kesalahan yang jelas saat Anda mendapatkannya None. Selain itu, berikan komentar yang jelas di file pengaturan. Jika saya salah paham, saya sarankan Anda mengajukan pertanyaan terpisah.
Don Kirkby
8

Tidak ada gunanya mencoba mengenkripsi kata sandi: orang yang Anda coba sembunyikan memiliki skrip Python, yang akan memiliki kode untuk mendekripsinya. Cara tercepat untuk mendapatkan kata sandi adalah dengan menambahkan pernyataan cetak ke skrip Python tepat sebelum menggunakan kata sandi dengan layanan pihak ketiga.

Jadi simpan kata sandi sebagai string dalam skrip, dan base64 menyandikannya sehingga hanya membaca file saja tidak cukup, lalu sebut saja sehari.

Ned Batchelder
sumber
Saya perlu mengedit nama pengguna dan kata sandi secara berkala dan saya akan membungkus semuanya dalam EXE untuk Windoze; Saya telah mengedit postingan untuk mencerminkan ini. Haruskah saya melakukan base64 di mana pun saya akhirnya menyimpannya?
Naftuli Kay
Saya setuju bahwa "mengenkripsi" kata sandi tidak membantu, karena kata sandi teks biasa bagaimanapun juga harus diperoleh secara otomatis, dan karena itu harus dapat diperoleh dari apa pun yang disimpan. Tetapi ada pendekatan yang layak.
wberry
Pikir saya mengenali nama Anda, Anda berada di panel pemula & ahli di TalkPython, sebagai pemula, pesan Anda benar-benar selaras dengan saya, terima kasih!
Manakin
7

Saya pikir hal terbaik yang dapat Anda lakukan adalah melindungi file skrip dan sistem yang menjalankannya.

Pada dasarnya lakukan hal berikut:

  • Gunakan izin sistem file (chmod 400)
  • Kata sandi yang kuat untuk akun pemilik di sistem
  • Mengurangi kemampuan sistem untuk disusupi (firewall, nonaktifkan layanan yang tidak dibutuhkan, dll)
  • Hapus hak akses administratif / root / sudo bagi mereka yang tidak membutuhkannya
Corey D
sumber
Sayangnya, ini adalah Windows, saya akan membungkusnya dengan EXE, dan saya harus sering mengubah kata sandi, jadi hard-coding itu tidak akan menjadi pilihan.
Naftuli Kay
1
Windows masih memiliki izin sistem file. Simpan kata sandi dalam file eksternal dan hapus akses semua orang kecuali Anda sendiri. Anda mungkin juga harus menghapus hak administratif mereka.
Corey D
Ya, menggunakan izin adalah satu-satunya opsi keamanan yang dapat diandalkan di sini. Tentunya administrator mana pun masih dapat mengakses data (setidaknya di windows / distribusi linux biasa) tetapi kemudian itu adalah pertempuran yang sudah hilang.
Voo
Itu benar. Ketika dekripsi kata sandi diotomatiskan, itu sama baiknya dengan memiliki kata sandi teks biasa. Keamanan sebenarnya adalah mengunci akun pengguna dengan akses. Yang terbaik yang bisa dilakukan adalah memberikan izin hanya-baca hanya untuk akun pengguna itu. Mungkin membuat pengguna khusus, khusus dan hanya untuk layanan itu.
Sepero
1

sistem operasi seringkali memiliki dukungan untuk mengamankan data bagi pengguna. dalam kasus windows, sepertinya itu http://msdn.microsoft.com/en-us/library/aa380261.aspx

Anda dapat memanggil win32 apis dari python menggunakan http://vermeulen.ca/python-win32api.html

Sejauh yang saya mengerti, ini akan menyimpan data sehingga hanya dapat diakses dari akun yang digunakan untuk menyimpannya. jika Anda ingin mengedit data Anda dapat melakukannya dengan menulis kode untuk mengekstrak, mengubah, dan menyimpan nilainya.

andrew cooke
sumber
Ini sepertinya pilihan terbaik bagi saya, tetapi saya merasa jawaban ini terlalu tidak lengkap untuk menerimanya, mengingat tidak ada contoh sebenarnya.
ArtOfWarfare
1
Ada beberapa contoh untuk menggunakan fungsi ini dengan Python di sini: stackoverflow.com/questions/463832/using-dpapi-with-python
ArtOfWarfare
1

Saya menggunakan Kriptografi karena saya mengalami masalah saat menginstal (mengompilasi) pustaka lain yang biasa disebutkan di sistem saya. (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)

Skrip saya berjalan di sistem / ruangan yang aman secara fisik. Saya mengenkripsi kredensial dengan "skrip enkripsi" ke file konfigurasi. Dan kemudian mendekripsi saat saya perlu menggunakannya. "Encrypter script" tidak ada di sistem sebenarnya, hanya file konfigurasi yang dienkripsi. Seseorang yang menganalisis kode dapat dengan mudah memecahkan enkripsi dengan menganalisis kode tersebut, tetapi Anda masih dapat mengkompilasinya menjadi EXE jika perlu.

KRBA
sumber