Ukrywanie hasła w skrypcie Pythona (tylko niezabezpieczone zaciemnianie)


127

Mam skrypt w Pythonie, który tworzy połączenie ODBC. Połączenie ODBC jest generowane za pomocą parametrów połączenia. W tym ciągu połączenia muszę podać nazwę użytkownika i hasło dla tego połączenia.

Czy istnieje łatwy sposób na ukrycie tego hasła w pliku (tylko, że nikt nie może odczytać hasła podczas edycji pliku)?


16
Pamiętaj tylko, że użytkownicy uruchamiający ten plik będą mieli co najmniej dostęp do niego do odczytu i mogą łatwo pobrać hasła. Jeśli coś może być odczytane tylko przez Ciebie i martwisz się, że ludzie zobaczą je przez ramię, ale ostrzegaj, podczas gdy przeciętny obserwator nie może zapamiętać rzeczy wystarczająco szybko, aby złapać hasło, każdy, kto ma dostęp do skryptu i niewielka wiedza techniczna i niewielka ambicja będą w stanie zdobyć Twoje hasła. Zawsze bardzo dokładnie przemyśl kwestie bezpieczeństwa, to ważne.
Youarefunny

Odpowiedzi:


117

Kodowanie Base64 znajduje się w standardowej bibliotece i powstrzyma surferów:

>>> import base64
>>>  print(base64.b64encode("password".encode("utf-8")))
cGFzc3dvcmQ=
>>> print(base64.b64decode("cGFzc3dvcmQ=").decode("utf-8"))
password

5
Zgadzam się. Hasło zakodowane w base64 wygląda o wiele bardziej tajemniczo.
Ed Haber

4
Nie pomaga to jednak fakt, że skrypt musi być czytelny dla użytkownika, który go uruchamia, a hasło nie może.
Martin Beckett

79
Nie sądzę, że base64jest to lepsze zaciemnianie niż rot13w tym kontekście. Wręcz przeciwnie, base64ma swoje typowe cechy (znak równości, ...) i dlatego jest łatwiejszy do wykrycia niż inne podejścia. Jednak jakiekolwiek zaciemnianie nie przynosi żadnych praktycznych korzyści. Naprawdę źle, że ta odpowiedź jest tak wysoko oceniana. Po prostu daje fałszywe poczucie bezpieczeństwa ...
schlamar

12
Jeśli nagrywasz hasło, aby mogło być użyte przez skrypt, każdy, kto ma dostęp do skryptu, będzie mógł uzyskać hasło, niezależnie od używanej metody szyfrowania. Wymaganiem było po prostu ukrycie hasła przed kimś, kto patrzy na skrypt, gdy był otwarty. W tym przypadku base64jest lepsze rot13niż w standardowej bibliotece Pythona.
Dave Webb

17
base64 NIE jest szyfrowaniem. w najlepszym przypadku jest to zaciemnianie.
csgeek

52

Douglas F Shearer jest ogólnie akceptowanym rozwiązaniem w systemie Unix, gdy trzeba określić hasło do zdalnego logowania.
Dodasz --password-z-pliku opcji, aby określić ścieżkę i czytać tekst jawny z pliku.
Plik może wtedy znajdować się w obszarze użytkownika chronionym przez system operacyjny. Umożliwia również różnym użytkownikom automatyczne pobieranie własnego pliku.

W przypadku haseł, których użytkownik skryptu nie może znać - możesz uruchomić skrypt z podwyższonymi uprawnieniami i mieć plik haseł, którego właścicielem jest ten użytkownik root / admin.


1
Jak dokładnie uruchomić skrypt z podwyższonymi uprawnieniami bez podawania hasła roota lub administratora? Czy jest to związane z ustawieniem bitów UID?
Youarefunny

4
Nieważne, że to rozgryzłem. Dla każdego, kogo to obchodzi: Jeśli skrypt ma ustawiony bit setuid, system operacyjny „przekaże” ten bit do interpretera. Niestety, istnieją ogromne, otwarte luki w zabezpieczeniach, więc większość nowoczesnych dystrybucji wyłącza setuid dla skryptów.
Youarefunny

Nie mogę znaleźć żadnych informacji na temat opcji --password-from-file. Czy masz jakieś przykłady? Dzięki!
pyramidface

@pyramidface - Miałem na myśli, że zakodujesz taką funkcję i dodasz możliwość odczytu hasła z pliku
Martin Beckett

@MartinBeckett, ale jak powiedział Youarefunny, musiałbyś podnieść setuid w pythonie, aby dać skryptowi root dostępu do pliku z hasłami?
pyramidface

51

Oto prosta metoda:

  1. Utwórz moduł Pythona - nazwijmy go peekaboo.py.
  2. W pliku peekaboo.py podaj zarówno hasło, jak i kod wymagający tego hasła
  3. Utwórz skompilowaną wersję - peekaboo.pyc - importując ten moduł (za pomocą wiersza poleceń Pythona itp.).
  4. Teraz usuń peekaboo.py.
  5. Możesz teraz szczęśliwie importować peekaboo, opierając się tylko na peekaboo.pyc. Ponieważ peekaboo.pyc jest kompilowany bajtowo, zwykły użytkownik nie może go odczytać.

Powinno to być nieco bezpieczniejsze niż dekodowanie base64 - chociaż jest podatne na działanie dekompilatora py_to_pyc.


2
To wciąż ma pewne wady, ale w rzeczywistości jest bardzo bliskie temu, czego chcę. Pozwoli mi to na demonstrację skryptów Pythona, które zawierają połączenia użytkownik / hasło bez ujawniania hasła na ekranie lub konieczności wpisywania go w wierszu poleceń. Po zaimportowaniu peekaboo import peekabohasło jest dostępne jako peekaboo.password(jeśli zawiera peekaboo.py password='secret')
Dannid

8
Jeśli chcesz pójść o krok dalej, możesz użyć Cythona, aby skompilować dowolny plik .py do C i wygenerować plik binarny specyficzny dla platformy (np. .Pyd dla systemu Windows, .so dla macOS itp.) ... Za pomocą cythonizingskryptu i udostępniając wygenerowany plik binarny, uzyskasz korzyść z tej odpowiedzi + dodaj kolejną warstwę zaciemniania, ponieważ teraz masz zdekompilowany kod C, aby dostać się do hasła. Nie jest to w 100% bezpieczne, ale uzyskanie poufnych danych, które chcesz ukryć, będzie wymagało dużo pracy.
Fnord

cytonizujący, doskonały
Arno

29

Jeśli pracujesz w systemie Unix, skorzystaj z modułu netrc w standardowej bibliotece Pythona. Odczytuje hasła z oddzielnego pliku tekstowego (.netrc), którego format jest opisany tutaj .

Oto mały przykład użycia:

import netrc

# Define which host in the .netrc file to use
HOST = 'mailcluster.loopia.se'

# Read from the .netrc file in your home directory
secrets = netrc.netrc()
username, account, password = secrets.authenticators( HOST )

print username, password

19

Najlepszym rozwiązaniem, przy założeniu, że nazwa użytkownika i hasło nie mogą być podane w czasie wykonywania przez użytkownika, jest prawdopodobnie oddzielny plik źródłowy zawierający tylko inicjalizację zmiennej dla nazwy użytkownika i hasła, które są importowane do głównego kodu. Ten plik wymagałby edycji tylko w przypadku zmiany poświadczeń. W przeciwnym razie, jeśli martwisz się tylko o surferów naramiennych ze średnią pamięcią, kodowanie base 64 jest prawdopodobnie najłatwiejszym rozwiązaniem. ROT13 jest po prostu zbyt łatwy do zdekodowania ręcznie, nie rozróżnia wielkości liter i zachowuje zbyt duże znaczenie w stanie zaszyfrowanym. Zakoduj swoje hasło i identyfikator użytkownika poza skryptem Pythona. Niech on dekoduje skrypt w czasie wykonywania do użycia.

Nadawanie skryptom danych uwierzytelniających do zadań automatycznych jest zawsze ryzykowną propozycją. Twój skrypt powinien mieć własne poświadczenia, a konto, którego używa, nie powinno mieć dostępu innego niż dokładnie to, co jest konieczne. Przynajmniej hasło powinno być długie i raczej losowe.


Bardzo ładna odpowiedź - dziękuję. Do małych skryptów, które piszę (które i tak są skryptami konserwacyjnymi - wystarczy kodowanie BASE64)
bernhardrusch

2
Brzmi dobrze, ale czy możesz podać przykładową implementację? W tej chwili to tylko opis ogólnej praktyki i nie jest tak przydatny, jeśli ktoś nie robił tego wcześniej.
Dannid

18

Co powiesz na zaimportowanie nazwy użytkownika i hasła z pliku zewnętrznego względem skryptu? W ten sposób, nawet jeśli ktoś zdobyłby skrypt, nie otrzymałby automatycznie hasła.


15

base64 to sposób na zaspokojenie twoich prostych potrzeb. Nie ma potrzeby importowania niczego:

>>> 'your string'.encode('base64')
'eW91ciBzdHJpbmc=\n'
>>> _.decode('base64')
'your string'

2
Co dokładnie jest głupie ?! Cała odpowiedź czy część nie importująca?
tzot

18
Base64 tylko dodaje iluzji bezpieczeństwa.
FlySwat

13
Jonathan, wygląda na to, że nie przeczytałeś pytania. Chodzi o niejasność (i to bardzo tymczasową), a nie o bezpieczeństwo , więc nie rozumiem, dlaczego uważasz moją odpowiedź za nieprzydatną.
tzot

1
Nie wiedziałem, że możesz to zrobić zamiast używać modułu base64. I jest też wiele kodowań, takich jak zlib też ... zabawa :)
Kiv.

1
@Dennis Używanie modułu base64 jest obecnie preferowanym sposobem. Ten ostatni nie działa już w nowszych wersjach Pythona.
Jeyekomon,

5

w przypadku zaciemniania python3 użycie base64jest wykonywane inaczej:

import base64
base64.b64encode(b'PasswordStringAsStreamOfBytes')

Co skutkuje w

b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM='

zwróć uwagę na nieformalną reprezentację ciągu, rzeczywisty ciąg jest w cudzysłowach

i dekodowanie z powrotem do oryginalnego ciągu

base64.b64decode(b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM=')
b'PasswordStringAsStreamOfBytes'

aby użyć tego wyniku, gdy wymagane są obiekty łańcuchowe, obiekt bajtów można przetłumaczyć

repr = base64.b64decode(b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM=')
secret = repr.decode('utf-8')
print(secret)

Aby uzyskać więcej informacji o tym, jak python3 obsługuje bajty (i odpowiednie ciągi znaków), zapoznaj się z oficjalną dokumentacją .


4

To dość powszechny problem. Zazwyczaj najlepsze, co możesz zrobić, to jedno lub drugie

A) stworzyć jakąś funkcję szyfrującą Cezara do kodowania / dekodowania (tylko nie rot13) lub

B) Preferowaną metodą jest użycie klucza szyfrowania, w zasięgu twojego programu, zakodowanie / odkodowanie hasła. W którym możesz użyć ochrony plików, aby zabezpieczyć dostęp do klucza.

Wzdłuż tych linii, jeśli Twoja aplikacja działa jako usługa / demon (jak serwer WWW), możesz umieścić swój klucz w chronionym hasłem magazynie kluczy, wprowadzając hasło podczas uruchamiania usługi. Ponowne uruchomienie aplikacji zajmie administrator, ale będziesz mieć naprawdę dobrą ochronę haseł konfiguracyjnych.


2

Twój system operacyjny prawdopodobnie zapewnia funkcje do bezpiecznego szyfrowania danych. Na przykład w systemie Windows istnieje DPAPI (API ochrony danych). Dlaczego nie poprosić użytkownika o ich poświadczenia przy pierwszym uruchomieniu, a następnie wyrzucić je zaszyfrowane do kolejnych uruchomień?


2

Bardziej domowa ocena zamiast konwersji uwierzytelniania / haseł / nazwy użytkownika na zaszyfrowane szczegóły. FTPLIB to tylko przykład. " pass.csv ” to nazwa pliku csv

Zapisz hasło w CSV, jak poniżej:

Nazwa Użytkownika

hasło użytkownika

(Bez nagłówka kolumny)

Czytanie pliku CSV i zapisywanie go na liście.

Używanie elementów listy jako szczegółów uwierzytelniania.

Pełny kod.

import os
import ftplib
import csv 
cred_detail = []
os.chdir("Folder where the csv file is stored")
for row in csv.reader(open("pass.csv","rb")):       
        cred_detail.append(row)
ftp = ftplib.FTP('server_name',cred_detail[0][0],cred_detail[1][0])

2

Oto mój fragment na taką rzecz. Zasadniczo importujesz lub kopiujesz funkcję do swojego kodu. getCredentials utworzy zaszyfrowany plik, jeśli nie istnieje i zwróci dictionaty, a updateCredential zostanie zaktualizowany.

import os

def getCredentials():
    import base64

    splitter='<PC+,DFS/-SHQ.R'
    directory='C:\\PCT'

    if not os.path.exists(directory):
        os.makedirs(directory)

    try:
        with open(directory+'\\Credentials.txt', 'r') as file:
            cred = file.read()
            file.close()
    except:
        print('I could not file the credentials file. \nSo I dont keep asking you for your email and password everytime you run me, I will be saving an encrypted file at {}.\n'.format(directory))

        lanid = base64.b64encode(bytes(input('   LanID: '), encoding='utf-8')).decode('utf-8')  
        email = base64.b64encode(bytes(input('   eMail: '), encoding='utf-8')).decode('utf-8')
        password = base64.b64encode(bytes(input('   PassW: '), encoding='utf-8')).decode('utf-8')
        cred = lanid+splitter+email+splitter+password
        with open(directory+'\\Credentials.txt','w+') as file:
            file.write(cred)
            file.close()

    return {'lanid':base64.b64decode(bytes(cred.split(splitter)[0], encoding='utf-8')).decode('utf-8'),
            'email':base64.b64decode(bytes(cred.split(splitter)[1], encoding='utf-8')).decode('utf-8'),
            'password':base64.b64decode(bytes(cred.split(splitter)[2], encoding='utf-8')).decode('utf-8')}

def updateCredentials():
    import base64

    splitter='<PC+,DFS/-SHQ.R'
    directory='C:\\PCT'

    if not os.path.exists(directory):
        os.makedirs(directory)

    print('I will be saving an encrypted file at {}.\n'.format(directory))

    lanid = base64.b64encode(bytes(input('   LanID: '), encoding='utf-8')).decode('utf-8')  
    email = base64.b64encode(bytes(input('   eMail: '), encoding='utf-8')).decode('utf-8')
    password = base64.b64encode(bytes(input('   PassW: '), encoding='utf-8')).decode('utf-8')
    cred = lanid+splitter+email+splitter+password
    with open(directory+'\\Credentials.txt','w+') as file:
        file.write(cred)
        file.close()

cred = getCredentials()

updateCredentials()

1

Umieść informacje konfiguracyjne w zaszyfrowanym pliku konfiguracyjnym. Zapytaj o te informacje w swoim kodzie za pomocą klucza. Umieść ten klucz w osobnym pliku dla każdego środowiska i nie przechowuj go z kodem.


1

Czy znasz pit?

https://pypi.python.org/pypi/pit (tylko py2 (wersja 0.3))

https://github.com/yoshiori/pit (będzie działać na py3 (aktualna wersja 0.4))

test.py

from pit import Pit

config = Pit.get('section-name', {'require': {
    'username': 'DEFAULT STRING',
    'password': 'DEFAULT STRING',
    }})
print(config)

Biegać:

$ python test.py
{'password': 'my-password', 'username': 'my-name'}

~ / .pit / default.yml:

section-name:
  password: my-password
  username: my-name

3
Pit nie ma żadnej dokumentacji
sukces

Jak zauważył @successhawk - nie widzę ŻADNEJ dokumentacji w tych linkach github / pypi dla "pit" - ale powyższy opis jest jasny - i ogólnie podoba mi się to rozwiązanie do "ukrywania" poświadczeń w łatwym widoku ...
netdesignate

Niechętnie używam modułu, który nie jest utrzymywany i otrzymuję błędy, gdy próbuję go używać zgodnie z instrukcją:/usr/lib/python3.7/site-packages/pit.py:93: YAMLLoadWarning: calling yaml.load() without Loader=... is deprecated, as the default Loader is unsafe. Please read https://msg.pyyaml.org/load for full details. return yaml.load(open(Pit._config))
netdesignate

1

Jeśli pracujesz w systemie Windows, możesz rozważyć użycie biblioteki win32crypt. Umożliwia przechowywanie i odzyskiwanie chronionych danych (kluczy, haseł) przez użytkownika, który uruchamia skrypt, dzięki czemu hasła nigdy nie są przechowywane w postaci zwykłego tekstu lub zaciemnionego w kodzie. Nie jestem pewien, czy istnieje równoważna implementacja dla innych platform, więc przy ścisłym użyciu win32crypt twój kod nie jest przenośny.

Myślę, że moduł można uzyskać tutaj: http://timgolden.me.uk/pywin32-docs/win32crypt.html


1

Sposób, w jaki to zrobiłem, jest następujący:

W powłoce Pythona:

>>> from cryptography.fernet import Fernet
>>> key = Fernet.generate_key()
>>> print(key)
b'B8XBLJDiroM3N2nCBuUlzPL06AmfV4XkPJ5OKsPZbC4='
>>> cipher = Fernet(key)
>>> password = "thepassword".encode('utf-8')
>>> token = cipher.encrypt(password)
>>> print(token)
b'gAAAAABe_TUP82q1zMR9SZw1LpawRLHjgNLdUOmW31RApwASzeo4qWSZ52ZBYpSrb1kUeXNFoX0tyhe7kWuudNs2Iy7vUwaY7Q=='

Następnie utwórz moduł z następującym kodem:

from cryptography.fernet import Fernet

# you store the key and the token
key = b'B8XBLJDiroM3N2nCBuUlzPL06AmfV4XkPJ5OKsPZbC4='
token = b'gAAAAABe_TUP82q1zMR9SZw1LpawRLHjgNLdUOmW31RApwASzeo4qWSZ52ZBYpSrb1kUeXNFoX0tyhe7kWuudNs2Iy7vUwaY7Q=='

# create a cipher and decrypt when you need your password
cipher = Fernet(key)

mypassword = cipher.decrypt(token).decode('utf-8')

Gdy to zrobisz, możesz albo bezpośrednio zaimportować moje hasło, albo możesz zaimportować token i szyfr w celu odszyfrowania w razie potrzeby.

Oczywiście podejście to ma pewne wady. Jeśli ktoś ma zarówno token, jak i klucz (tak jak gdyby miał skrypt), może łatwo odszyfrować. Jednak powoduje to zaciemnienie i jeśli skompilujesz kod (za pomocą czegoś takiego jak Nuitka), przynajmniej twoje hasło nie pojawi się jako zwykły tekst w edytorze szesnastkowym.


0

To nie jest dokładną odpowiedzią na twoje pytanie, ale jest powiązane. Zamierzałem dodać jako komentarz, ale nie pozwolono mi. Miałem do czynienia z tym samym problemem i zdecydowaliśmy się udostępnić skrypt użytkownikom korzystającym z Jenkinsa. Dzięki temu możemy przechowywać poświadczenia bazy danych w oddzielnym pliku, który jest zaszyfrowany i zabezpieczony na serwerze i niedostępny dla osób niebędących administratorami. Daje nam także trochę skrótu do tworzenia interfejsu użytkownika i ograniczania wykonywania.


0

Można również rozważyć możliwość przechowywania hasła poza skryptem i dostarczania go w czasie wykonywania

np. fred.py

import os
username = 'fred'
password = os.environ.get('PASSWORD', '')
print(username, password)

które można uruchomić jak

$ PASSWORD=password123 python fred.py
fred password123

Dodatkowe warstwy „bezpieczeństwa poprzez zaciemnienie” można osiągnąć za pomocą base64 (jak zasugerowano powyżej), używając mniej oczywistych nazw w kodzie i jeszcze bardziej oddalając rzeczywiste hasło od kodu.

Jeśli kod znajduje się w repozytorium, często warto przechowywać sekrety poza nim , więc można go dodać do ~/.bashrc(lub do skarbca lub skryptu uruchamiania, ...)

export SURNAME=cGFzc3dvcmQxMjM=

i zmień fred.pyna

import os
import base64
name = 'fred'
surname = base64.b64decode(os.environ.get('SURNAME', '')).decode('utf-8')
print(name, surname)

następnie zaloguj się ponownie i

$ python fred.py
fred password123

0

Dlaczego nie mieć prostego xora?

Zalety:

  • wygląda jak dane binarne
  • nikt nie może go przeczytać bez znajomości klucza (nawet jeśli jest to pojedynczy znak)

Dochodzę do punktu, w którym rozpoznaję proste ciągi b64 dla popularnych słów i również rot13. Xor znacznie utrudniłby sprawę.



-1

W sieci jest kilka narzędzi ROT13 napisanych w Pythonie - po prostu wygoogluj je. ROT13 koduje ciąg w trybie offline, kopiuje go do źródła, dekoduje w miejscu transmisji.

Ale to jest naprawdę słaba ochrona ...


dołącz link lub przykładowy kod, aby ta odpowiedź była bardziej użyteczna
Micah Stubbs
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.