Masquage d'un mot de passe dans un script python (obfuscation non sécurisée uniquement)


127

J'ai un script python qui crée une connexion ODBC. La connexion ODBC est générée avec une chaîne de connexion. Dans cette chaîne de connexion, je dois inclure le nom d'utilisateur et le mot de passe pour cette connexion.

Existe-t-il un moyen simple de masquer ce mot de passe dans le fichier (juste que personne ne peut lire le mot de passe lorsque j'édite le fichier)?


16
N'oubliez pas que les utilisateurs exécutant ce fichier auront au moins un accès en lecture et peuvent facilement saisir les mots de passe. Si les minces ne peuvent être lues que par vous et que vous craignez que les gens le voient par-dessus votre épaule, allez-y, mais soyez averti que l'observateur moyen ne peut pas mémoriser les choses assez rapidement pour saisir un mot de passe, toute personne ayant accès au script et un un petit peu de savoir-faire technique et un peu d'ambition seront en mesure de saisir vos mots de passe. Pensez toujours très attentivement à la sécurité, c'est important.
Youarefunny

Réponses:


117

L'encodage Base64 est dans la bibliothèque standard et fera pour arrêter les surfeurs d'épaule:

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

5
Je suis d'accord. Le mot de passe encodé en base64 semble beaucoup plus mystérieux.
Ed Haber

4
Mais cela n'aide pas le fait que le script doit être lisible par l'utilisateur qui l'exécute et le mot de passe ne doit pas.
Martin Beckett

79
Je ne pense pas que ce base64soit mieux obscurcissant que rot13dans ce contexte. Au contraire, base64a ses caractéristiques typiques (signe égal, ...) et est donc plus facilement détectable que d'autres approches. Cependant, toute obfuscation n'a aucun avantage pratique. Vraiment dommage que cette réponse soit très appréciée. Cela donne juste un faux sentiment de sécurité ...
schlamar

12
Si vous enregistrez le mot de passe afin qu'il puisse être utilisé par le script, toute personne ayant accès au script pourra obtenir le mot de passe, quelle que soit la méthode de cryptage que vous utilisez. L'exigence ici était simplement de cacher le mot de passe à quelqu'un qui regardait simplement le script pendant qu'il était ouvert. Dans ce cas, il base64est préférable de rot13le faire dans la bibliothèque standard Python.
Dave Webb

17
base64 n'est PAS un cryptage. c'est au mieux obscurcir.
csgeek

52

Douglas F Shearer est la solution généralement approuvée sous Unix lorsque vous devez spécifier un mot de passe pour une connexion à distance.
Vous ajoutez une option --password-from-file pour spécifier le chemin et lire le texte brut à partir d'un fichier.
Le fichier peut alors se trouver dans la propre zone de l'utilisateur protégée par le système d'exploitation. Il permet également à différents utilisateurs de récupérer automatiquement leur propre fichier.

Pour les mots de passe que l'utilisateur du script n'est pas autorisé à connaître - vous pouvez exécuter le script avec une autorisation prolongée et faire en sorte que le fichier de mots de passe appartienne à cet utilisateur root / admin.


1
Comment exécutez-vous exactement le script avec des autorisations élevées sans donner de mot de passe root ou admin? Est-ce lié à la définition des bits UID?
Youarefunny

4
Peu importe, je l'ai compris. Pour tous ceux qui s'en soucient: si un script a un bit setuid défini, le système d'exploitation «passera» le bit setuid à l'interpréteur. Malheureusement, il existe d'énormes trous de sécurité béants, de sorte que la plupart des distributions modernes désactivent setuid pour les scripts.
Youarefunny

Je ne trouve aucune information sur l'option --password-from-file. Avez-vous des exemples? Merci!
pyramidface

@pyramidface - Je voulais dire que vous coderiez une fonctionnalité comme celle-ci et ajouter la possibilité de lire un mot de passe à partir d'un fichier
Martin Beckett

@MartinBeckett mais comme Youarefunny l'a dit, vous devriez lever setuid sur python afin de donner au script un accès root au fichier de mot de passe?
pyramidface

51

Voici une méthode simple:

  1. Créez un module python - appelons-le peekaboo.py.
  2. Dans peekaboo.py, incluez à la fois le mot de passe et tout code nécessitant ce mot de passe
  3. Créez une version compilée - peekaboo.pyc - en important ce module (via la ligne de commande python, etc ...).
  4. Maintenant, supprimez peekaboo.py.
  5. Vous pouvez désormais importer facilement peekaboo en vous appuyant uniquement sur peekaboo.pyc. Puisque peekaboo.pyc est compilé par octets, il n'est pas lisible par l'utilisateur occasionnel.

Cela devrait être un peu plus sûr que le décodage base64 - bien qu'il soit vulnérable à un décompilateur py_to_pyc.


2
Cela a encore quelques lacunes, mais c'est en fait très proche de ce que je veux. Cela me permettra de faire une démonstration de scripts python qui incluent des connexions utilisateur / passwd sans révéler le mot de passe à l'écran, ni devoir le taper dans l'invite de commande. Après l'importation de peekaboo, import peekabole mot de passe est disponible sous la forme peekaboo.password(si peekaboo.py est contenu password='secret')
Dannid

8
Si vous voulez pousser cette idée un peu plus loin, vous pouvez utiliser Cython pour compiler n'importe quel fichier .py en C et générer un binaire spécifique à la plate-forme (ex: .pyd pour windows, .so pour macOS, etc) ... Par cythonizingvotre script et en partageant le binaire généré, vous bénéficierez de cette réponse + ajoutez une autre couche d'obfuscation, car maintenant vous avez décompilé le code C pour accéder au mot de passe. Ce n'est pas sûr à 100%, mais il faudra beaucoup de travail pour accéder aux données sensibles que vous souhaitez masquer.
Fnord

cythonisation, parfait
Arno

29

Si vous travaillez sur un système Unix, profitez du module netrc dans la bibliothèque Python standard. Il lit les mots de passe à partir d'un fichier texte séparé (.netrc), dont le format est décrit ici .

Voici un petit exemple d'utilisation:

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

La meilleure solution, en supposant que le nom d'utilisateur et le mot de passe ne peuvent pas être fournis au moment de l'exécution par l'utilisateur, est probablement un fichier source distinct contenant uniquement l'initialisation de variable pour le nom d'utilisateur et le mot de passe importés dans votre code principal. Ce fichier n'a besoin d'être modifié que lorsque les informations d'identification changent. Sinon, si vous ne vous inquiétez que des surfeurs d'épaule avec une mémoire moyenne, l'encodage en base 64 est probablement la solution la plus simple. ROT13 est tout simplement trop facile à décoder manuellement, n'est pas sensible à la casse et conserve trop de signification dans son état crypté. Encodez votre mot de passe et votre identifiant utilisateur en dehors du script python. Faites-le décoder le script au moment de l'exécution pour l'utiliser.

Donner des informations d'identification aux scripts pour les tâches automatisées est toujours une proposition risquée. Votre script doit avoir ses propres informations d'identification et le compte qu'il utilise ne doit avoir aucun accès autre que ce qui est exactement nécessaire. Au moins, le mot de passe doit être long et plutôt aléatoire.


Très belle réponse - merci. Pour les petits scripts que j'écris (qui sont de toute façon des scripts de maintenance - l'encodage BASE64 suffira)
bernhardrusch

2
Cela semble bien, mais pouvez-vous donner un exemple d'implémentation? Pour le moment, c'est juste une description d'une pratique générale, et pas aussi utile à quelqu'un qui n'a jamais fait cela auparavant.
Dannid

18

Que diriez-vous d'importer le nom d'utilisateur et le mot de passe d'un fichier externe au script? De cette façon, même si quelqu'un prenait possession du script, il n'obtiendrait pas automatiquement le mot de passe.


15

base64 est la solution idéale pour vos besoins simples. Il n'est pas nécessaire d'importer quoi que ce soit:

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

2
Qu'est-ce qui est exactement idiot?! La réponse entière, ou la partie non importée?
tzot

18
Base64 ajoute seulement l'illusion de la sécurité.
FlySwat

13
Jonathan, il semble que vous n'ayez pas lu la question. Il s'agit d' obscurité (et très temporaire), pas de sécurité , donc je ne comprends pas pourquoi vous considérez ma réponse comme inutile.
tzot

1
Je ne savais pas que vous pouviez faire cela au lieu d'avoir à utiliser le module base64. Et il y a aussi beaucoup d'encodages comme zlib aussi ... fun :)
Kiv

1
@Dennis L'utilisation du module base64 est la méthode préférée de nos jours. Ce dernier ne fonctionne plus dans les nouvelles versions de Python.
Jeyekomon

5

pour l' obfuscation python3, l' utilisation base64se fait différemment:

import base64
base64.b64encode(b'PasswordStringAsStreamOfBytes')

ce qui se traduit par

b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM='

notez la représentation sous forme de chaîne informelle, la chaîne réelle est entre guillemets

et décodage à la chaîne d'origine

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

pour utiliser ce résultat là où des objets chaîne sont requis, l'objet octets peut être traduit

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

pour plus d'informations sur la manière dont python3 gère les octets (et les chaînes en conséquence), veuillez consulter la documentation officielle .


4

C'est un problème assez courant. En règle générale, le mieux que vous puissiez faire est soit

A) créer une sorte de fonction de chiffrement ceasar pour encoder / décoder (mais pas rot13) ou

B) la méthode préférée est d'utiliser une clé de cryptage, à la portée de votre programme, encoder / décoder le mot de passe. Dans lequel vous pouvez utiliser la protection des fichiers pour protéger l'accès à la clé.

Dans le même esprit, si votre application s'exécute en tant que service / démon (comme un serveur Web), vous pouvez placer votre clé dans un magasin de clés protégé par mot de passe avec l'entrée du mot de passe dans le cadre du démarrage du service. Il faudra un administrateur pour redémarrer votre application, mais vous aurez une très bonne prétention pour vos mots de passe de configuration.


2

Votre système d'exploitation fournit probablement des fonctionnalités pour crypter les données en toute sécurité. Par exemple, sous Windows, il existe DPAPI (API de protection des données). Pourquoi ne pas demander à l'utilisateur ses informations d'identification la première fois que vous exécutez, puis écartez-les cryptées pour les exécutions suivantes?


2

Plus d'approche locale plutôt que de convertir l'authentification / mots de passe / nom d'utilisateur en détails cryptés. FTPLIB n'est que l'exemple. " pass.csv " est le nom du fichier csv

Enregistrez le mot de passe au format CSV comme ci-dessous:

Nom d'utilisateur

mot de passe de l'utilisateur

(Sans en-tête de colonne)

Lire le CSV et l'enregistrer dans une liste.

Utilisation des éléments de liste comme détails d'authentification.

Code complet.

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

Voici mon extrait de code pour une telle chose. Vous importez ou copiez essentiellement la fonction dans votre code. getCredentials créera le fichier chiffré s'il n'existe pas et renverra une diction, et updateCredential se mettra à jour.

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

Placez les informations de configuration dans un fichier de configuration chiffré. Recherchez ces informations dans votre code à l'aide d'une clé. Placez cette clé dans un fichier séparé par environnement et ne la stockez pas avec votre code.


1

Connaissez-vous pit?

https://pypi.python.org/pypi/pit (py2 uniquement (version 0.3))

https://github.com/yoshiori/pit (cela fonctionnera sur py3 (version actuelle 0.4))

test.py

from pit import Pit

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

Courir:

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

~ / .pit / default.yml:

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

3
Pit n'a pas de documentation
successhawk

Comme @successhawk l'a noté - je ne vois AUCUNE documentation dans ces liens github / pypi pour "pit" - mais la description ci-dessus est claire - et dans l'ensemble j'aime cette solution pour "cacher" les informations d'identification de la vue facile ...
netdesignate

Je suis réticent à utiliser un module qui n'est pas maintenu, et j'obtiens des erreurs lorsque j'essaye de l'utiliser comme indiqué:/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

Si vous utilisez Windows, vous pouvez envisager d'utiliser la bibliothèque win32crypt. Il permet le stockage et la récupération des données protégées (clés, mots de passe) par l'utilisateur qui exécute le script, ainsi les mots de passe ne sont jamais stockés en texte clair ou au format obscurci dans votre code. Je ne sais pas s'il existe une implémentation équivalente pour d'autres plates-formes, donc avec l'utilisation stricte de win32crypt, votre code n'est pas portable.

Je pense que le module peut être obtenu ici: http://timgolden.me.uk/pywin32-docs/win32crypt.html


1

Une façon dont j'ai fait cela est la suivante:

Au niveau du shell python:

>>> 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=='

Ensuite, créez un module avec le code suivant:

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

Une fois que vous avez fait cela, vous pouvez soit importer directement le mypassword, soit importer le jeton et le chiffrement à déchiffrer si nécessaire.

De toute évidence, cette approche présente certaines lacunes. Si quelqu'un a à la fois le jeton et la clé (comme ils le feraient s'ils avaient le script), ils peuvent déchiffrer facilement. Cependant, il obscurcit, et si vous compilez le code (avec quelque chose comme Nuitka) au moins votre mot de passe n'apparaîtra pas sous forme de texte brut dans un éditeur hexadécimal.


0

Cela ne répond pas précisément à votre question, mais c'est lié. J'allais ajouter comme commentaire mais je n'ai pas été autorisé. J'ai été confronté à ce même problème et nous avons décidé d'exposer le script aux utilisateurs utilisant Jenkins. Cela nous permet de stocker les informations d'identification de la base de données dans un fichier séparé qui est chiffré et sécurisé sur un serveur et non accessible aux non-administrateurs. Cela nous permet également de créer un raccourci vers la création d'une interface utilisateur et la limitation de l'exécution.


0

Vous pouvez également envisager la possibilité de stocker le mot de passe en dehors du script, et de le fournir au moment de l'exécution

par exemple fred.py

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

qui peut être exécuté comme

$ PASSWORD=password123 python fred.py
fred password123

Des couches supplémentaires de «sécurité par l'obscurité» peuvent être obtenues en utilisant base64(comme suggéré ci-dessus), en utilisant des noms moins évidents dans le code et en éloignant davantage le mot de passe réel du code.

Si le code est dans un référentiel, il est souvent utile de stocker des secrets en dehors de celui - ci , donc on pourrait l'ajouter à ~/.bashrc(ou à un coffre-fort, ou à un script de lancement, ...)

export SURNAME=cGFzc3dvcmQxMjM=

et changer fred.pypour

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

puis reconnectez-vous et

$ python fred.py
fred password123

0

Pourquoi ne pas avoir un simple xor?

Avantages:

  • ressemble à des données binaires
  • personne ne peut le lire sans connaître la clé (même s'il s'agit d'un seul caractère)

J'arrive au point où je reconnais de simples chaînes b64 pour les mots courants et rot13 également. Xor rendrait les choses beaucoup plus difficiles.



-1

Il existe plusieurs utilitaires ROT13 écrits en Python sur le Net - il suffit de google pour eux. ROT13 encode la chaîne hors ligne, copie-la dans la source, décode au point de transmission.

Mais c'est une protection vraiment faible ...


veuillez inclure un lien ou un exemple de code pour rendre cette réponse plus utile
Micah Stubbs
En utilisant notre site, vous reconnaissez avoir lu et compris notre politique liée aux cookies et notre politique de confidentialité.
Licensed under cc by-sa 3.0 with attribution required.