Python'da bir kullanıcı adı ve parolayı güvenli bir şekilde saklamam gerekiyor, seçeneklerim nelerdir? [kapalı]


95

Bir kullanıcı adı ve şifre kombinasyonu kullanarak üçüncü taraf bir hizmetten periyodik olarak bilgi alacak küçük bir Python betiği yazıyorum. % 100 kurşun geçirmez bir şey yaratmaya ihtiyacım yok (% 100 var mı?), Ama iyi bir güvenlik önlemi dahil etmek istiyorum, bu yüzden en azından birinin onu kırması uzun zaman alacaktır.

Bu komut dosyası bir GUI'ye sahip olmayacak ve periyodik olarak çalıştırılacak cron, bu nedenle her çalıştırıldığında bir şeylerin şifresini çözmek için bir parola girmek gerçekten işe yaramayacak ve kullanıcı adı ve parolayı ya şifreli bir dosyada ya da şifreli olarak saklamam gerekecek. SQLite veritabanında, zaten SQLite kullanacağım için tercih edilir ve bir noktada şifreyi düzenlemem gerekebilir . Ek olarak, bu noktada yalnızca Windows için olduğundan, muhtemelen tüm programı bir EXE ile sarmalıyorum.

Bir croniş aracılığıyla periyodik olarak kullanılacak kullanıcı adı ve şifre kombinasyonunu nasıl güvenli bir şekilde saklayabilirim ?


Yanıtlar:


19

Ssh-agent'a benzer bir strateji öneririm . Ssh-agent'ı doğrudan kullanamıyorsanız, buna benzer bir şey uygulayabilirsiniz, böylece şifreniz yalnızca RAM'de saklanır. Cron işi, her çalıştığında aracıdan gerçek parolayı almak, bir kez kullanmak ve delifadeyi kullanarak hemen referansı kaldırmak için kimlik bilgilerini yapılandırmış olabilir .

Yöneticinin ssh-agent'ı başlatmak için yine de parolayı girmesi gerekir, ancak bu, düz metin parolanın diskte herhangi bir yerde depolanmasını önleyen makul bir uzlaşmadır.


2
+1, bu çok mantıklı. Bunun için her zaman bir kullanıcı arabirimi oluşturabilirim, bu kullanıcı önyükleme sırasında esasen parolasını sorar, bu şekilde asla diskte depolanmaz ve meraklı gözlerden korunur.
Naftuli Kay

55

Kütüphane anahtarlık piton entegrasyonu sayesinde CryptProtectDataWindows üzerinde API kullanıcının oturum açma kimlik bilgileriyle verileri şifreler (Mac ve Linux üzerinde ilgili API yıllardan ile birlikte).

Basit kullanım:

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

Kullanıcı adını anahtarlıkta saklamak istiyorsanız kullanın:

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)

Daha sonra anahtarlıktan bilgilerinizi almak için

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

Öğeler, kullanıcının işletim sistemi kimlik bilgileriyle şifrelenir, böylece kullanıcı hesabınızda çalışan diğer uygulamalar parolaya erişebilir.

Bu güvenlik açığını biraz gizlemek için, parolayı anahtarlığa kaydetmeden önce bir şekilde şifreleyebilir / gizleyebilirsiniz. Elbette, komut dosyanızı hedefleyen herhangi biri kaynağa bakabilir ve şifrenin şifresini nasıl çözeceğini / çözeceğini anlayabilir, ancak en azından bazı uygulamaların kasadaki tüm şifreleri süpürmesini ve sizinkini almasını engellemiş olursunuz. .


Kullanıcı adı nasıl saklanmalıdır? keyringHem kullanıcı adını hem de parolayı almayı destekliyor mu ?
Stevoisiak

1
@DustinWyatt get_passwordKullanıcı adı için akıllıca kullanım . Her ne kadar, sana özgün basitleştirilmiş örnekle cevap başlaması gerektiğini düşünüyorum keyring.set_password()vekeyring.get_password()
Stevoisiak

keyringpython standart kitaplığının bir parçası değil
Ciasto piekarz

@Ciastopiekarz Yanıtla ilgili bir şey sizi bunun standart kütüphanenin bir parçası olduğuna inandırdı mı?
Dustin Wyatt

Does keyringgünlükleri ve bellek afterwords şifreyi fırçalayın güvenle?
Kebman

26

Bu ve ilgili soruların cevaplarına baktıktan sonra, gizli verileri şifrelemek ve gizlemek için önerilen yöntemlerden birkaçını kullanarak bazı kodları bir araya getirdim. Bu kod, özellikle komut dosyasının kullanıcı müdahalesi olmadan çalışması gerektiği durumlar içindir (kullanıcı manuel olarak başlatırsa, en iyisi şifreyi girmesi ve bu sorunun cevabının önerdiği gibi yalnızca bellekte tutmasıdır). Bu yöntem süper güvenli değildir; temel olarak, komut dosyası gizli bilgilere erişebilir, böylece tam sistem erişimine sahip olan herkes komut dosyasına ve ilgili dosyalara sahip olur ve bunlara erişebilir. Bu, id'nin yaptığı şey, verileri rastgele incelemeden korur ve tek tek veya komut dosyası olmadan birlikte incelendiklerinde veri dosyalarını kendilerinin güvenliğini sağlar.

Bunun için motivasyonum, işlemleri izlemek için bazı banka hesaplarımı sorgulayan bir projedir - her dakika veya iki dakikada bir şifre girmeden arka planda çalışmasına ihtiyacım var.

Bu kodu komut dosyanızın en üstüne yapıştırın, saltSeed'i değiştirin ve ardından gerektiği gibi kodunuzda store () retrieve () ve require () kullanın:

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

Bu yöntemin güvenliği, gizli dosyalarda işletim sistemi izinleri yalnızca komut dosyasının kendisinin bunları okumasına izin verecek şekilde ayarlanmışsa ve komut dosyasının kendisi yalnızca yürütülebilir olarak (okunabilir değil) derlenmiş ve işaretlenmişse önemli ölçüde artırılacaktır. Bazıları otomatikleştirilebilir, ama ben rahatsız etmedim. Muhtemelen komut dosyası için bir kullanıcı ayarlamayı ve komut dosyasını o kullanıcı olarak çalıştırmayı (ve komut dosyasının dosyalarının sahipliğini o kullanıcıya ayarlamayı) gerektirir.

Herkesin aklına gelebilecek her türlü öneriyi, eleştiriyi veya diğer kırılgan noktaları çok isterim. Kripto kodu yazma konusunda oldukça yeniyim, bu yüzden yaptığım şey neredeyse kesinlikle geliştirilebilir.


26

Bir Python programının kullanması gereken şifreleri ve diğer sırları saklamak için birkaç seçenek vardır, özellikle de kullanıcıdan şifreyi yazmasını isteyemeyeceği arka planda çalışması gereken bir program.

Kaçınılması gereken sorunlar:

  1. Diğer geliştiricilerin ve hatta halkın görebileceği bir yerde kaynak kontrolünde şifreyi kontrol etmek.
  2. Bir yapılandırma dosyasından veya kaynak koddan parolayı okuyan aynı sunucudaki diğer kullanıcılar.
  3. Parolanın, siz düzenlerken başkalarının omzunuzun üzerinden görebileceği bir kaynak dosyada olması.

Seçenek 1: SSH

Bu her zaman bir seçenek değildir, ancak muhtemelen en iyisidir. Özel anahtarınız asla ağ üzerinden iletilmez, SSH sadece doğru anahtara sahip olduğunuzu kanıtlamak için matematiksel hesaplamalar yapar.

Çalışması için şunlara ihtiyacınız var:

  • Veritabanına veya eriştiğiniz her şeye SSH tarafından erişilebilir olmalıdır. "SSH" artı erişmekte olduğunuz hizmeti aramayı deneyin. Örneğin, "ssh postgresql" . Bu, veritabanınızda bir özellik değilse, sonraki seçeneğe geçin.
  • Veritabanına çağrı yapacak hizmeti çalıştırmak için bir hesap oluşturun ve bir SSH anahtarı oluşturun .
  • Genel anahtarı arayacağınız hizmete ekleyin veya o sunucuda yerel bir hesap oluşturun ve genel anahtarı oraya kurun.

Seçenek 2: Ortam Değişkenleri

Bu en basit olanı, bu yüzden başlamak için iyi bir yer olabilir. Twelve Factor Uygulamasında iyi tanımlanmıştır . Temel fikir, kaynak kodunuzun yalnızca parolayı veya diğer sırları ortam değişkenlerinden çekmesi ve ardından programı çalıştırdığınız her sistemde bu ortam değişkenlerini yapılandırmanızdır. Çoğu geliştirici için işe yarayacak varsayılan değerleri kullanırsanız, hoş bir dokunuş da olabilir. Bunu, yazılımınızı "varsayılan olarak güvenli" yapmakla dengelemeniz gerekir.

Ortam değişkenlerinden sunucuyu, kullanıcı adını ve parolayı çeken bir örnek aşağıda verilmiştir.

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)

İşletim sisteminizde ortam değişkenlerini nasıl ayarlayacağınıza bakın ve hizmeti kendi hesabı altında çalıştırmayı düşünün. Bu şekilde, kendi hesabınızda programları çalıştırdığınızda ortam değişkenlerinde hassas verileriniz olmaz. Bu ortam değişkenlerini kurduğunuzda, diğer kullanıcıların bunları okuyamamasına özellikle dikkat edin. Örneğin dosya izinlerini kontrol edin. Tabii ki, root iznine sahip herhangi bir kullanıcı bunları okuyabilir, ancak buna yardımcı olunamaz. Systemd kullanıyorsanız, servis birimine bakın ve herhangi bir sır EnvironmentFileyerine kullanmaya dikkat edin Environment. Environmentdeğerleri herhangi bir kullanıcı tarafından görüntülenebilir systemctl show.

Seçenek 3: Yapılandırma Dosyaları

Bu, ortam değişkenlerine çok benzer, ancak sırları bir metin dosyasından okursunuz. Ortam değişkenlerini, dağıtım araçları ve sürekli entegrasyon sunucuları gibi şeyler için daha esnek buluyorum. Bir yapılandırma dosyası kullanmaya karar verirseniz, Python, standart kitaplıkta JSON , INI , netrc ve XML gibi çeşitli biçimleri destekler . PyYAML ve TOML gibi harici paketleri de bulabilirsiniz . Şahsen, JSON ve YAML'yi kullanımı en basit buluyorum ve YAML yorumlara izin veriyor.

Yapılandırma dosyalarında dikkate alınması gereken üç nokta:

  1. Dosya nerede? Belki gibi varsayılan bir konum ~/.my_appve farklı bir konumu kullanmak için bir komut satırı seçeneği.
  2. Diğer kullanıcıların dosyayı okuyamadığından emin olun.
  3. Açıkçası, yapılandırma dosyasını kaynak koduna kaydetmeyin. Kullanıcıların kendi ana dizinlerine kopyalayabilecekleri bir şablon işlemek isteyebilirsiniz.

Seçenek 4: Python Modülü

Bazı projeler sırlarını doğrudan bir Python modülüne koyar.

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

Ardından değerleri almak için bu modülü içe aktarın.

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

db_connect(db_server, db_user, db_password)

Bu tekniği kullanan bir proje Django'dur . Açıkçası, kullanıcıların kopyalayıp değiştirebileceği settings.pybir dosyayı işlemek isteyebilirsiniz, ancak kaynak kontrolünü taahhüt etmemelisiniz settings_template.py.

Bu teknikle ilgili birkaç sorun görüyorum:

  1. Geliştiriciler dosyayı yanlışlıkla kaynak denetimine verebilir. Eklemek bu .gitignoreriski azaltır.
  2. Kodunuzun bir kısmı kaynak kontrolü altında değil. Disiplinliysen ve buraya sadece dizeler ve sayılar koyarsan, bu sorun olmaz. Buraya günlük filtre sınıfları yazmaya başlarsanız, durun!

Projeniz zaten bu tekniği kullanıyorsa, ortam değişkenlerine geçiş yapmak kolaydır. Tüm ayar değerlerini ortam değişkenlerine taşıyın ve Python modülünü bu ortam değişkenlerinden okumak için değiştirin.


Merhaba. Projeniz zaten bu tekniği kullanıyorsa, ortam değişkenlerine geçiş yapmak kolaydır. Windows 10'da ortam değişkenlerini manuel olarak nasıl ayarlayacağımı biliyorum, ancak bunlara python kodumdan erişebilirim os.getenv(). Kod paylaşılıyorsa bunu nasıl yapmalıyız? Kod başka bir geliştirici tarafından indirilirse, ortam değişkenlerinin kendisi için önceden ayarlanmış olduğundan nasıl emin olmalıdır?
a_sid

os.getenv()@A_sid'e makul bir varsayılan değer iletmeye çalışıyorum, böylece kod en azından ortam değişkenlerini ayarlamayan bir kullanıcı için çalışacak. İyi bir varsayılan değer yoksa, aldığınızda açık bir hata oluşturun None. Bunun dışında ayarlar dosyasına net açıklamalar koyun. Bir şeyi yanlış anladıysam, ayrı bir soru sormanızı öneririm.
Don Kirkby

8

Parolayı şifrelemenin pek bir anlamı yok: Parolayı gizlemeye çalıştığınız kişi, şifresini çözmek için koda sahip olacak Python betiğine sahip. Parolayı almanın en hızlı yolu, üçüncü taraf hizmetle parolayı kullanmadan hemen önce Python betiğine bir print ifadesi eklemektir.

Bu yüzden şifreyi komut dosyasında bir dize olarak saklayın ve base64 onu kodlayın, böylece sadece dosyayı okumak yeterli olmaz, sonra bir gün arayın.


Kullanıcı adını ve şifreyi periyodik olarak düzenlemem gerekecek ve her şeyi Windoze için bir EXE dosyasında yazacağım; Yazıyı bunu yansıtacak şekilde düzenledim. Onu depoladığım her yerde basitçe taban 64 yapmalı mıyım?
Naftuli Kay

Düz metin parolanın yine de otomatik bir şekilde elde edilmesi gerektiğinden ve bu nedenle saklanan her şeyden elde edilebilmesi gerektiğinden, parolayı "şifrelemenin" yardımcı olmadığını kabul ediyorum. Ancak uygulanabilir yaklaşımlar var.
wberry

Adınızı tanıdığımı sanıyordum, TalkPython'da yeni başlayanlar ve uzmanlar panelindeydiniz, yeni başlayan biri olarak mesajınız gerçekten benimle rezonansa girdi, teşekkürler!
Manakin

7

Bence yapabileceğiniz en iyi şey betik dosyasını ve üzerinde çalıştığı sistemi korumaktır.

Temel olarak şunları yapın:

  • Dosya sistemi izinlerini kullanın (chmod 400)
  • Sistemdeki sahibinin hesabı için güçlü şifre
  • Sistemin tehlikeye atılma yeteneğini azaltın (güvenlik duvarı, gereksiz hizmetleri devre dışı bırakın, vb.)
  • İhtiyaç duymayanlar için yönetici / kök / sudo ayrıcalıklarını kaldırın

Ne yazık ki, bu Windows, onu bir EXE ile paketleyeceğim ve şifreyi sık sık değiştirmem gerekecek, bu yüzden sabit kodlama bir seçenek olmayacak.
Naftuli Kay

1
Windows hala dosya sistemi izinlerine sahiptir. Parolayı harici bir dosyada saklayın ve kendi parolanız dışında herkesin erişimini kaldırın. Muhtemelen yönetici ayrıcalıklarını da kaldırmanız gerekir.
Corey D

Evet, izinleri kullanmak buradaki tek güvenilir güvenlik seçeneğidir. Açıkçası, herhangi bir yönetici yine de verilere erişebilir (en azından Windows / normal linux dağıtımlarında), ancak o zaman bu zaten kaybedilmiş bir savaş.
Voo

Bu doğru. Şifre çözme otomatikleştirildiğinde, bu düz metinli bir şifreye sahip olmak kadar iyidir. Gerçek güvenlik, kullanıcı hesabını erişim ile kilitlemektir. Yapılabilecek en iyi şey, yalnızca o kullanıcı hesabına salt okunur izinler vermektir. Muhtemelen, özel olarak ve yalnızca bu hizmet için özel bir kullanıcı oluşturun.
Eylül

1

işletim sistemleri genellikle kullanıcı için verilerin güvenliğini sağlamak için desteğe sahiptir. Windows söz konusu olduğunda http://msdn.microsoft.com/en-us/library/aa380261.aspx gibi görünür

http://vermeulen.ca/python-win32api.html kullanarak python'dan win32 apis'i çağırabilirsiniz

Anladığım kadarıyla, bu, verileri yalnızca depolamak için kullanılan hesaptan erişilebilmesi için depolayacak. Verileri düzenlemek istiyorsanız, bunu çıkarmak, değiştirmek ve değeri kaydetmek için kod yazarak yapabilirsiniz.


Bu bana en iyi seçenek gibi görünüyor, ancak gerçek örneklerden yoksun olduğu için bu cevabın kabul edilemeyecek kadar eksik olduğunu düşünüyorum.
ArtOfWarfare

1
Python'da bu işlevleri kullanmak için bazı örnekler burada: stackoverflow.com/questions/463832/using-dpapi-with-python
ArtOfWarfare

1

Kriptografi kullandım çünkü sistemime diğer yaygın olarak bahsedilen kitaplıkları kurarken (derlerken) sorunlar yaşadım. (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)

Komut dosyam fiziksel olarak güvenli bir sistem / odada çalışıyor. Kimlik bilgilerini bir yapılandırma dosyasına bir "şifreleme komut dosyası" ile şifrelerim. Ve onları kullanmam gerektiğinde şifresini çöz. "Şifreleme betiği" gerçek sistemde değil, sadece şifrelenmiş yapılandırma dosyası. Kodu analiz eden biri kodu analiz ederek şifrelemeyi kolayca kırabilir, ancak gerekirse yine de bir EXE'de derleyebilirsiniz.

Sitemizi kullandığınızda şunları okuyup anladığınızı kabul etmiş olursunuz: Çerez Politikası ve Gizlilik Politikası.
Licensed under cc by-sa 3.0 with attribution required.