Ocultar una contraseña en un script de Python (solo ofuscación insegura)

127

Tengo un script de Python que está creando una conexión ODBC. La conexión ODBC se genera con una cadena de conexión. En esta cadena de conexión tengo que incluir el nombre de usuario y la contraseña para esta conexión.

¿Hay una manera fácil de ocultar esta contraseña en el archivo (solo que nadie puede leer la contraseña cuando estoy editando el archivo)?

bernhardrusch
fuente
16
Solo recuerde que los usuarios que ejecutan este archivo tendrán al menos acceso de lectura y podrán obtener fácilmente las contraseñas. Si solo usted puede leer cosas delgadas y le preocupa que la gente lo vea por encima del hombro, hágalo, pero tenga en cuenta que el observador promedio no puede memorizar las cosas lo suficientemente rápido como para obtener una contraseña, cualquier persona con acceso al guión y un Un poco de conocimiento técnico y una pequeña cantidad de ambición podrán obtener sus contraseñas. Siempre piense en la seguridad con mucho cuidado, es importante.
Estás divertido el

Respuestas:

117

La codificación Base64 está en la biblioteca estándar y servirá para detener a los surfistas de hombro:

>>> import base64
>>>  print(base64.b64encode("password".encode("utf-8")))
cGFzc3dvcmQ=
>>> print(base64.b64decode("cGFzc3dvcmQ=").decode("utf-8"))
password
Dave Webb
fuente
55
Estoy de acuerdo. La contraseña codificada en base64 parece mucho más misteriosa.
Ed Haber
44
Pero no ayuda el hecho de que el script debe ser legible por el usuario que lo ejecuta y la contraseña no.
Martin Beckett
79
No creo que base64sea ​​mejor ofuscar que rot13en este contexto. Por el contrario, base64tiene sus características típicas (signo igual, ...) y, por lo tanto, es más fácil de detectar que otros enfoques. Sin embargo, cualquier ofuscación no tiene ningún beneficio práctico. Realmente malo que esta respuesta sea tan altamente calificada. Simplemente da una falsa sensación de seguridad ...
schlamar
12
Si está grabando la contraseña para que el script pueda usarla, cualquier persona con acceso al script podrá obtener la contraseña, sin importar el método de cifrado que utilice. El requisito aquí era solo ocultar la contraseña a alguien que solo miraba el script mientras estaba abierto. En este caso base64es preferible a rot13como está en la biblioteca estándar de Python.
Dave Webb
17
base64 NO es cifrado. es ofuscación en el mejor de los casos.
csgeek
52

Douglas F Shearer's es la solución generalmente aprobada en Unix cuando necesita especificar una contraseña para un inicio de sesión remoto.
Agrega una opción --password-from-file para especificar la ruta y leer el texto sin formato de un archivo.
El archivo puede estar en el área del usuario protegida por el sistema operativo. También permite a diferentes usuarios recoger automáticamente su propio archivo.

Para las contraseñas que el usuario de la secuencia de comandos no puede saber, puede ejecutar la secuencia de comandos con permiso extendido y tener el archivo de contraseña propiedad de ese usuario raíz / administrador.

Martin Beckett
fuente
1
¿Cómo ejecuta exactamente el script con permisos elevados sin dar una contraseña de administrador o root? ¿Está relacionado con los bits de UID establecidos?
Estás divertido el
44
No importa, lo descubrí. Para cualquier otra persona que se preocupe: si un script tiene un bit setuid establecido, el sistema operativo "pasará" el bit setuid al intérprete. Desafortunadamente, hay enormes agujeros de seguridad enormes, por lo que la mayoría de las distribuciones modernas desactivan el setuid para los scripts.
Estás divertido el
No puedo encontrar ninguna información sobre la opción --password-from-file. ¿Tienes algún ejemplo? ¡Gracias!
pyramidface
@pyramidface - Quise decir que codificaría una característica como esta y agregaría la capacidad de leer una contraseña de un archivo
Martin Beckett
@MartinBeckett, pero como dijo Youarefunny, ¿tendría que aumentar setuid en python para dar acceso a la raíz del script al archivo de contraseña?
pyramidface
51

Aquí hay un método simple:

  1. Cree un módulo de Python, llamémoslo peekaboo.py.
  2. En peekaboo.py, incluya tanto la contraseña como cualquier código que necesite esa contraseña
  3. Cree una versión compilada, peekaboo.pyc, importando este módulo (a través de la línea de comandos de Python, etc.).
  4. Ahora, elimine peekaboo.py.
  5. Ahora puede importar felizmente peekaboo confiando solo en peekaboo.pyc. Como peekaboo.pyc está compilado en bytes, no es legible para el usuario casual.

Esto debería ser un poco más seguro que la decodificación base64, aunque es vulnerable a un descompilador py_to_pyc.

manyPartsAreEdible
fuente
2
Esto todavía tiene algunas deficiencias, pero en realidad está muy cerca de lo que quiero. Me permitirá hacer una demostración de los scripts de Python que incluyen conexiones de usuario / contraseña sin revelar la contraseña en pantalla o tener que escribirla en el símbolo del sistema. Después de importar peekaboo, import peekabola contraseña está disponible como peekaboo.password(si peekaboo.py contiene password='secret')
Dannid
8
Si desea llevar esta idea un paso más allá, puede usar Cython para compilar cualquier archivo .py en C y generar un binario específico de la plataforma (por ejemplo: .pyd para Windows, .so para macOS, etc.) ... según cythonizingsu secuencia de comandos y al compartir el binario generado obtendrá el beneficio de esta respuesta + agregará otra capa de ofuscación, porque ahora tiene que descompilar el código C para acceder a la contraseña. Esto no es 100% seguro, pero tomará mucho trabajo llegar a los datos confidenciales que desea ocultar.
Fnord el
cythonizing, perfecto
Arno
29

Si está trabajando en un sistema Unix, aproveche el módulo netrc en la biblioteca estándar de Python. Lee las contraseñas de un archivo de texto separado (.netrc), que tiene el formato descrito aquí .

Aquí hay un pequeño ejemplo de uso:

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
jonasberg
fuente
19

La mejor solución, suponiendo que el usuario no pueda proporcionar el nombre de usuario y la contraseña en tiempo de ejecución, es probablemente un archivo fuente separado que contenga solo una inicialización variable para el nombre de usuario y la contraseña que se importan a su código principal. Este archivo solo necesitaría edición cuando cambian las credenciales. De lo contrario, si solo le preocupan los surfistas de hombro con recuerdos promedio, la codificación de base 64 es probablemente la solución más fácil. ROT13 es demasiado fácil de decodificar manualmente, no distingue entre mayúsculas y minúsculas y conserva demasiado significado en su estado cifrado. Codifique su contraseña e identificación de usuario fuera del script de Python. Haga que el script decodifique en tiempo de ejecución para su uso.

Dar credenciales de guiones para tareas automatizadas siempre es una propuesta arriesgada. Su secuencia de comandos debe tener sus propias credenciales y la cuenta que utiliza no debe tener otro acceso que no sea exactamente el necesario. Al menos la contraseña debe ser larga y bastante aleatoria.

tduehr
fuente
Muy buena respuesta, gracias. Para los pequeños scripts que estoy escribiendo (que de todos modos son scripts de mantenimiento, la codificación BASE64 será suficiente)
bernhardrusch
2
Esto suena bien, pero ¿puedes dar un ejemplo de implementación? En este momento es solo una descripción de una práctica general, y no es tan útil para alguien que no haya hecho esto antes.
Dannid
18

¿Qué hay de importar el nombre de usuario y la contraseña de un archivo externo al script? De esa manera, incluso si alguien obtuviera el script, no obtendría automáticamente la contraseña.

Douglas F Shearer
fuente
15

base64 es el camino a seguir para sus necesidades simples. No hay necesidad de importar nada:

>>> 'your string'.encode('base64')
'eW91ciBzdHJpbmc=\n'
>>> _.decode('base64')
'your string'
tzot
fuente
2
¿Qué es exactamente una tontería? ¿Toda la respuesta, o la parte que no importa?
tzot
18
Base64 solo agrega la ilusión de seguridad.
FlySwat
13
Jonathan, parece que no leíste la pregunta. Se trata de la oscuridad (y muy temporal), no de la seguridad , por lo que no entiendo por qué consideras que mi respuesta no es útil.
Tzot
1
No sabía que podía hacer esto en lugar de tener que usar el módulo base64. Y también hay muchas codificaciones como zlib ... divertido :)
Kiv el
1
@Dennis Usar el módulo base64 es la forma preferida hoy en día. Este último ya no funciona en las versiones más recientes de Python.
Jeyekomon
5

para la ofuscación de python3, el uso base64se realiza de manera diferente:

import base64
base64.b64encode(b'PasswordStringAsStreamOfBytes')

lo que resulta en

b'UGFzc3dvcmRTdHJpbmdBc1N0cmVhbU9mQnl0ZXM='

tenga en cuenta la representación informal de la cadena, la cadena real está entre comillas

y decodificando de nuevo a la cadena original

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

para usar este resultado donde se requieren objetos de cadena, el objeto de bytes se puede traducir

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

Para obtener más información sobre cómo python3 maneja los bytes (y las cadenas en consecuencia), consulte la documentación oficial .

estar nervioso
fuente
4

Este es un problema bastante común. Por lo general, lo mejor que puedes hacer es

A) cree algún tipo de función de cifrado ceasar para codificar / decodificar (simplemente no rot13) o

B) el método preferido es utilizar una clave de cifrado, al alcance de su programa, codificar / decodificar la contraseña. En el que puede utilizar la protección de archivos para proteger el acceso a la clave.

En ese sentido, si su aplicación se ejecuta como un servicio / demonio (como un servidor web), puede colocar su clave en un almacén de claves protegido por contraseña con la entrada de contraseña como parte del inicio del servicio. Se necesitará un administrador para reiniciar su aplicación, pero tendrá una buena pretensión para sus contraseñas de configuración.

Mikhail_Sam
fuente
2

Su sistema operativo probablemente proporciona facilidades para encriptar datos de forma segura. Por ejemplo, en Windows hay DPAPI (API de protección de datos). ¿Por qué no pedirle al usuario sus credenciales la primera vez que ejecuta y luego guardarlas encriptadas para las ejecuciones posteriores?

Jamie Eisenhart
fuente
2

Más appraoch de cosecha propia en lugar de convertir autenticación / contraseñas / nombre de usuario en detalles cifrados. FTPLIB es solo el ejemplo. " pass.csv " es el nombre del archivo csv

Guardar contraseña en CSV como a continuación:

nombre_usuario

contraseña de usuario

(Sin encabezado de columna)

Leer el CSV y guardarlo en una lista.

Uso de elementos de lista como detalles de autenticación.

Código completo

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])
Alma solitaria
fuente
2

Aquí está mi fragmento para tal cosa. Básicamente importa o copia la función a su código. getCredentials creará el archivo cifrado si no existe y devolverá una dicción, y updateCredential se actualizará.

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()
Mahmoud Alhyari
fuente
1

Coloque la información de configuración en un archivo de configuración cifrado. Consulte esta información en su código con una clave. Coloque esta clave en un archivo separado por entorno y no la almacene con su código.

FlySwat
fuente
1

¿Conoces hoyo?

https://pypi.python.org/pypi/pit (solo py2 (versión 0.3))

https://github.com/yoshiori/pit (funcionará en py3 (versión actual 0.4))

prueba.py

from pit import Pit

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

Correr:

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

~ / .pit / default.yml:

section-name:
  password: my-password
  username: my-name
TakesxiSximada
fuente
3
Pit no tiene ninguna documentación
successhawk
Como ha señalado @successhawk: no veo NINGUNA documentación en esos enlaces github / pypi para "pit", pero la descripción anterior es clara, y en general me gusta esta solución para "ocultar" credenciales de una vista fácil ...
netdesignate
Soy reacio a usar un módulo que no se mantiene, y recibo errores cuando trato de usarlo según las instrucciones:/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 se ejecuta en Windows, podría considerar usar la biblioteca win32crypt. Permite el almacenamiento y la recuperación de datos protegidos (claves, contraseñas) por parte del usuario que ejecuta el script, por lo tanto, las contraseñas nunca se almacenan en texto claro o en formato ofuscado en su código. No estoy seguro de si hay una implementación equivalente para otras plataformas, por lo que con el uso estricto de win32crypt su código no es portátil.

Creo que el módulo se puede obtener aquí: http://timgolden.me.uk/pywin32-docs/win32crypt.html

VilleLipponen
fuente
1

Una forma de hacerlo es la siguiente:

En el caparazón de 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=='

Luego, cree un módulo con el siguiente código:

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

Una vez que haya hecho esto, puede importar mypassword directamente o puede importar el token y el cifrado para descifrarlos según sea necesario.

Obviamente, hay algunas deficiencias en este enfoque. Si alguien tiene tanto el token como la clave (como lo haría si tuviera el script), puede descifrarlo fácilmente. Sin embargo, se ofusca, y si compila el código (con algo como Nuitka) al menos su contraseña no aparecerá como texto sin formato en un editor hexadecimal.

Dr_Z2A
fuente
0

Esto no responde con precisión a su pregunta, pero está relacionado. Iba a agregar como comentario pero no estaba permitido. He estado lidiando con este mismo problema, y ​​hemos decidido exponer el script a los usuarios que usan Jenkins. Esto nos permite almacenar las credenciales de db en un archivo separado que está encriptado y protegido en un servidor y que no es accesible para personas que no son administradores. También nos permite un poco de acceso directo para crear una interfaz de usuario y acelerar la ejecución.

Joe Hayes
fuente
0

También podría considerar la posibilidad de almacenar la contraseña fuera del script y proporcionarla en tiempo de ejecución

por ejemplo fred.py

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

que se puede ejecutar como

$ PASSWORD=password123 python fred.py
fred password123

Se pueden lograr capas adicionales de "seguridad a través de la oscuridad" usando base64(como se sugirió anteriormente), usando nombres menos obvios en el código y distanciando aún más la contraseña real del código.

Si el código está en un repositorio, a menudo es útil almacenar secretos fuera de él , por lo que podría agregar esto a ~/.bashrc(o a una bóveda, o un script de lanzamiento, ...)

export SURNAME=cGFzc3dvcmQxMjM=

y cambiar fred.pya

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

luego vuelva a iniciar sesión y

$ python fred.py
fred password123
jalanb
fuente
0

¿Por qué no tener un simple xor?

Ventajas:

  • parece datos binarios
  • nadie puede leerlo sin conocer la clave (incluso si se trata de un solo personaje)

Llego al punto en el que reconozco cadenas b64 simples para palabras comunes y rot13 también. Xor lo haría mucho más difícil.

SL
fuente
0
import base64
print(base64.b64encode("password".encode("utf-8")))
print(base64.b64decode(b'cGFzc3dvcmQ='.decode("utf-8")))
pradyot
fuente
Esta es la solución de la respuesta aceptada .
Sergey Shubin
-1

Hay varias utilidades ROT13 escritas en Python en la red, solo google para ellas. ROT13 codifica la cadena fuera de línea, la copia en la fuente, decodifica en el punto de transmisión.

Pero esta es una protección realmente débil ...

Kevin Little
fuente
incluya un enlace o un código de muestra para que esta respuesta sea más útil
Micah Stubbs