with set_password password change authenticated python django security encryption credentials

python - set_password - login with django user



Almacenamiento seguro de credenciales encriptadas en django (3)

Tal vez pueda confiar en un esquema multiusuario creando:

  • Un usuario que ejecuta Django (por ejemplo, django ) que no tiene permiso para acceder a las credenciales
  • Un usuario que tiene esos permisos (por ejemplo, sync ).

Ambos pueden estar en el grupo django , para permitirles acceder a la aplicación. Después de eso, manage.py sync-external un script (un comando de Django, como manage.py sync-external , por ejemplo) que sincronice lo que quieras.

De esta forma, el usuario de django tendrá acceso a la aplicación y al script de sincronización, pero no a las credenciales, porque solo el usuario de sync hace. Si alguien intenta ejecutar esa secuencia de comandos sin las credenciales, se producirá un error.

Confiar en el modelo de permiso de Linux es, en mi opinión, una "buena idea", pero no soy un experto en seguridad, así que tenlo en cuenta. Si alguien tiene algo que decir sobre lo que está arriba, ¡no lo dudes!

Estoy trabajando en una aplicación python / django que, entre otras cosas, sincroniza datos con una variedad de otros servicios, incluidos recursos compartidos de samba, servidores ssh (scp), aplicaciones de Google y otros. Como tal, necesita almacenar las credenciales para acceder a estos servicios. Almacenarlos como campos no encriptados sería, supongo, una mala idea, ya que un ataque de inyección SQL podría recuperar las credenciales. Entonces, necesitaría encriptar los creds antes del almacenamiento. ¿Hay bibliotecas confiables para lograr esto?

Una vez cifrados los creds, tendrían que descifrarse antes de poder usarlos. Hay dos casos de uso para mi aplicación:

  • Una es interactiva; en este caso, el usuario proporcionaría la contraseña para desbloquear las credenciales.
  • La otra es una sincronización automatizada, iniciada por un trabajo cron o similar. ¿Dónde guardaría la contraseña para minimizar el riesgo de explotar aquí?

¿O hay un enfoque diferente para este problema que debería tomar?


Primero almacenar en un servidor las credenciales suficientes para iniciar sesión en una multitud de sistemas parece una pesadilla. El código comprometedor en su servidor les filtrará todo lo que sea el cifrado.

Debe almacenar solo las credenciales que serían necesarias para realizar su tarea (es decir, sincronización de archivos). Para los servidores, debería considerar usar un servidor de sincronización como RSync , para Google los protocolos como OAuth, etc. De esta forma, si su servidor se ve comprometido, solo se filtrará la información, no el acceso a los sistemas.

Lo siguiente es encriptar estas credenciales. Para la criptografía te aconsejo que uses PYCrypto .

Para todos los números aleatorios que usaría en su criptografía, créelos con Crypto.Random (u otro método fuerte) para asegurarse de que son lo suficientemente fuertes.

No debe encriptar credenciales diferentes con la misma clave. El método que recomendaría es este:

  1. Su servidor debe tener su secreto maestro M (derivado de / dev / random). Guárdelo en el archivo propiedad de root y legible solo por root.
  2. Cuando su servidor comienza con privilegios de raíz, lee el archivo en la memoria y antes de atender a los clientes los descarta. Esa es una práctica normal para servidores web y otros demonios.
  3. Cuando va a escribir una nueva credencial (o actualizar una existente), genere un bloque aleatorio S. Tome la primera mitad y calcule hash K = H (S 1 , M) . Esa sería su clave de cifrado.
  4. Use el modo CBC para encriptar sus datos. Tome el vector de inicialización (IV) de S 2 .
  5. Almacene S junto con datos encriptados.

Cuando necesite descifrar, saque S, cree la K y descifre con la misma IV.

Para el hash, recomendaría SHA1, para encriptación - AES. Los hash y las cifras simétricas son lo suficientemente rápidas, por lo que no valdría la pena usar tamaños de clave más grandes.

Este esquema es un poco exagerado en algunos lugares, pero una vez más esto no estaría mal.

Pero recuerde nuevamente, la mejor manera de almacenar credenciales no es almacenar credenciales, y cuando sea necesario, use las menos privilegiadas que le permitirán realizar la tarea.


Tengo el mismo problema y he estado investigando esto los últimos días. La solución presentada por @Rostislav es bastante buena, pero está incompleta y un poco desactualizada.

En la capa de algoritmo

Primero, hay una nueva biblioteca para criptografía llamada, apropiadamente, Criptografía . Hay una buena cantidad de razones para usar esta biblioteca en lugar de PyCrypto, pero las principales que me atrajeron son:

  • Un objetivo central es que no puedas disparar en el pie. Por ejemplo, no tiene hash algos severamente obsoletos como MD2.
  • Tiene un fuerte apoyo institucional
  • ¡500,000 pruebas con integración continua en varias plataformas!
  • Su sitio web de documentación tiene una mejor configuración SSL ( puntaje A + casi perfecto en lugar de una calificación B mediocre )
  • Tienen una política de divulgación de vulnerabilidades.

Puede leer más sobre las razones para crear la nueva biblioteca en LWN .

En segundo lugar, la otra respuesta recomienda usar SHA1 como la clave de cifrado. SHA1 es peligrosamente débil y cada vez más débil . El reemplazo de SHA1 es SHA2, y además de eso, realmente deberías salar tu hash y estirarlo usando bcrypt o PBKDF2 . La salazón es importante como protección contra tablas de arcoiris y el estiramiento es una protección importante contra el forzamiento bruto.

(Bcrypt está menos probado, pero está diseñado para usar mucha memoria y PBKDF2 está diseñado para ser lento y es recomendado por NIST. En mi implementación, uso PBKDF2. Si desea más información sobre las diferencias, lea esto ).

Para el cifrado se debe utilizar AES en modo CBC con una clave de 128 bits, como se mencionó anteriormente, eso no ha cambiado, aunque ahora está enrollado en una especificación llamada Fernet . El vector de inicialización se generará para usted automáticamente en esta biblioteca, por lo que puede olvidarse con seguridad.

En la capa de generación de claves y almacenamiento

Las otras respuestas son bastante acertadas para sugerir que debe considerar cuidadosamente el manejo de claves y optar por algo como OAuth, si puede. Pero suponiendo que eso no sea posible (no está en mi implementación), tiene dos casos de uso: Cron jobs e Interactive.

El caso de uso de cron job se reduce al hecho de que necesita mantener una clave en un lugar seguro y usarla para ejecutar trabajos cron. No he estudiado esto, así que no opinaré aquí. Creo que hay muchas buenas formas de hacerlo, pero no sé de la manera más fácil.

Para el caso de uso interactivo, lo que debe hacer es recopilar la contraseña de un usuario, usarla para generar una clave y luego usar esa clave para descifrar las credenciales almacenadas.

Tráelo a casa

Así es como haría todo lo anterior, utilizando la biblioteca Criptografía:

from cryptography.fernet import Fernet from cryptography.hazmat.primitives.hashes import SHA256 from cryptography.hazmat.primitives.kdf.pbkdf2 import PBKDF2HMAC from cryptography.hazmat.backends import default_backend secret = "Some secret" # Generate a salt for use in the PBKDF2 hash salt = base64.b64encode(os.urandom(12)) # Recommended method from cryptography.io # Set up the hashing algo kdf = PBKDF2HMAC( algorithm=SHA256(), length=32, salt=str(salt), iterations=100000, # This stretches the hash against brute forcing backend=default_backend(), # Typically this is OpenSSL ) # Derive a binary hash and encode it with base 64 encoding hashed_pwd = base64.b64encode(kdf.derive(user_pwd)) # Set up AES in CBC mode using the hash as the key f = Fernet(hashed_pwd) encrypted_secret = f.encrypt(secret) # Store the safe inputs in the DB, but do NOT include a hash of the # user''s password, as that is the key to the encryption! Only store # the salt, the algo and the number of iterations. db.store( user=''some-user'', secret=encrypted_secret, algo=''pbkdf2_sha256'', iterations=''100000'', salt=salt )

El descifrado se ve así:

# Get the data back from your database encrypted_secret, algo, iterations, salt = db.get(''some-user'') # Set up the Key Derivation Formula (PBKDF2) kdf = PBKDF2HMAC( algorithm=SHA256(), length=32, salt=str(salt), iterations=int(iterations), backend=default_backend(), ) # Generate the key from the user''s password key = base64.b64encode(kdf.derive(user_pwd)) # Set up the AES encryption again, using the key f = Fernet(key) # Decrypt the secret! secret = f.decrypt(encrypted_secret) print(" Your secret is: %s" % secret)

¿Ataques?

Supongamos que su base de datos se filtró a Internet. ¿Qué puede hacer un atacante? Bueno, la clave que utilizamos para el cifrado tomó el hash SHA256 número 100,000 de la contraseña salada de su usuario. Almacenamos la sal y nuestro algoritmo de cifrado en su base de datos. Un atacante debe por lo tanto:

  • Intento de fuerza bruta del hash: combine la sal con cada contraseña posible y córtela 100.000 veces. Toma ese hash y pruébalo como la clave de descifrado. El atacante tendrá que hacer 100,000 hashes solo para probar una contraseña. Esto es básicamente imposible.
  • Pruebe cada hash posible directamente como la clave de descifrado. Esto es básicamente imposible.
  • Pruebe una tabla de arco iris con hashes pre calculados? No, no cuando se trata de sales aleatorias.

Creo que esto es bastante sólido.

Sin embargo, hay otra cosa en que pensar. PBKDF2 está diseñado para ser lento. Requiere mucho tiempo de CPU. Esto significa que te estás abriendo a ataques DDOS si hay una forma para que los usuarios generen hash PBKDF2. Prepárate para esto

Posdata

Dicho todo esto, creo que hay bibliotecas que harán algo de esto por ti. Busque en Google cosas como el campo encriptado django . No puedo hacer ninguna promesa sobre esas implementaciones, pero quizás aprendas algo sobre cómo otros lo han hecho.