Hash passwords con crypt() en GNU/Linux


Cuando se plantea utilizar diferentes sistemas de autenticación con contraseña en GNU/Linux hay que conocer cómo funcionan. En esta entrada se explica cómo utiliza la función crypt() de GNU/Linux las contraseñas de usuarios y cómo se puede reproducir el resultado con otras aplicaciones o lenguajes como python, php y slappasswd.

¿Qué es una función de hash en criptografía?

Una función de hash en criptografía traduce un bloque de datos variable (una contraseña por ejemplo) a una cadena de caracteres de cierto tamaño fijo y es además una función de una sola dirección (no reversible), por lo que no se pueden (o no se debería poder) obtener los datos originales a partir del hash.

Utilización de algoritmos de hash para almacenar contraseñas e GNU/Linux

El proceso de autenticación mediante contraseña en GNU/Linux se realiza utilizando la función crypt() y hace uso de diferentes funciones de hash (durante mucho tiempo DES y ahora normalmente MD5). Es decir, el sistema no almacena nunca la contraseña del usuario, sino el resultado de aplicar una función de hash a la misma. La forma de autenticar al usuario consiste en pedir la contraseña, aplicar la misma función de hash y comprobar que coincide con la almacenada.

Algoritmo DES (Data Encryption Standard)

Ha sido el algoritmo utilizado tradicionalmente en UNIX y GNU/Linux, aunque hoy en día no se considera suficientemente seguro debido al pequeño tamaño de la clave (56 bits) y no se recomienda su utilización. Crypt con DES sólo utiliza los 7 primeros bits de cada carácter (tabla ASCII por lo que no se pueden utilizar caracteres como ñ,á, …) y la contraseña tiene un tamaño máximo de 8 caracteres (si se escriben más se ignoran).

Habitualmente la función crypt() utiliza dos parámetros de entrada, la contraseña suministrada y algunos caracteres adicionales (denominados “sal”) con los que se añade mayor complejidad a la función, el resultado se presenta además como una cadena codificada en base64 (números, letras mayúsculas, minúsculas, “/” y “.” o como también se pone [a–zA–Z0–9./]).

Utilización de crypt DES en htpasswd (paquete apache2-utils)

Es fácil generar un hash con las mismas características al que se obtendría con crypt() con otras aplicaciones como htpasswd, por lo que partiremos de ésta para compararla con otros procedimientos.

Realizamos los siguientes pasos:

htpasswd -nd usuario
New password: (Tecleamos asdasd como contraseña)
Re-type new password: (Volvemos a teclear asdasd)
usuario:mbjXwldxJRm6I

El resultado es siempre una cadena de 13 caracteres en base64, donde los dos primeros (mb) representan la sal utilizada para cifrar la contraseña, que en este caso htpasswd ha elegido de forma aleatoria.

Utilización de crypt DES en python

Podemos reproducir el resultado anterior en python (utilizando la función crypt de este lenguaje), para lo que escribiremos las siguientes instrucciones en el intérprete de python:

>>> import crypt
>>> crypt.crypt('asdasd','mb')
'mbjXwldxJRm6I'

Utilización de crypt DES en slappasswd (paquete slapd)

slappasswd produce como resultado un hash en formato LDIF, que añade entre corchetes el nombre del mecanismo utilizado. Los pasos para reproducir el resultado anterior serían:

slappasswd -c 'mb'
New password: (tecleamos asdasd)
Re-enter new password: (volvemos a teclear asdasd)
{CRYPT}mbjXwldxJRm6I

Utilización de crypt DES en php

El código que reproduciría el resultado anterior en php sería:

<? echo crypt('asdasd','mb'); ?>

Algoritmo MD5 (Message Digest 5)

MD5 utiliza una clave de 128 bits, lo que permite utilizar contraseñas mucho más largas e incluir si se quiere caracteres no ASCII (como ñ,¡,¿,etc.). Es la base del algoritmo utilizado normalmente por crypt(), aunque ya se ha comprobado que este sistema no es totalmente seguro y se espera que poco a poco sea sustituido.

Utilización de MD5 en crypt()

La implementación de MD5 para hash de contraseñas en crypt() produce una salida que comienza por $1$ y la sal tiene un tamaño variable. Supongamos que añadimos un nuevo usuario a nuestro sistema con contraseña ‘asdasd’, producirá una entrada en el fichero /etc/shadow como ésta:

borrame:$1$yPpZhimP$iA4Og94pngLabw42vTz3x1:14335:0:99999:7:::

donde el único campo relevante para nuestra discusión es el segundo ($1$yPpZhimP$iA4Og94pngLabw42vTz3x1), una cadena de 31 caracteres que empieza por $1$ y tiene otro $ que delimita la sal, es decir, la sal de este hash es $1$yPpZhimP$, el resto de la cadena son caracteres en base64.

Utilización de crypt MD5 en python

Utilizamos de nuevo la biblioteca crypt de python, desde el intérprete de python basta con hacer:

import crypt
>>> crypt.crypt('asdasd','$1$yPpZhimP$')
'$1$yPpZhimP$iA4Og94pngLabw42vTz3x1'

Utilización de crypt MD5 en slappasswd

De forma totalmente similar a lo hecho con DES:

slappasswd -c '$1$yPpZhimP$'
New password: (tecleamos asdasd)
Re-enter new password: (volvemos a teclear asdasd)
{CRYPT}$1$yPpZhimP$iA4Og94pngLabw42vTz3x1

Es importante notar que el resultado se etiqueta con {CRYPT} ya que existe otro algoritmo que utiliza la función MD5 (etiquetado {MD5}) que no es compatible con éste, ya que no se especifica la sal y el resultado es diferente.

Utilización de crypt MD5 en php

Al igual que con crypt DES, podemos obtener el mismo resultado que en los casos anteriores con el siguiente código:

<? echo crypt('asdasd','$1$yPpZhimP$'); ?>

Algoritmo MD5 en python, slappasswd y php

Añadimos por último en este apartado la utilización del algoritmo md5 sin sal, como se utiliza habitualmente en muchos lenguajes y aplicaciones distintos de crypt().

Utilización de MD5 en slappasswd

Vamos a ver el resultado que obtendríamos para la misma contraseña (asdasd), pero utilizando

slappasswd -h {MD5}
New password: (tecleamos asdasd)
Re-enter new password: (volvemos a teclear asdasd)
{MD5}qPXxZ/RPSWTmyZje6CcRDA==

La salida es una cadena de 24 caracteres en base64, pero con el carácter (=) en lugar de (.) ya que hay diferentes esquemas en base64 que difieren en alguno de los dos caracteres extra que utilizan (/,+,-,.,=, etc.)

Utilización de MD5 en python

Para reproducir el resultado anterior en python, debemos utilizar la función md5 de la biblioteca hashlib y la función b64encode de la biblioteca base64. El resultado es:

>>> import base64
>>> import hashlib
>>> base64.b64encode(hashlib.md5('asdasd').digest())
'qPXxZ/RPSWTmyZje6CcRDA=='

Utilización de MD5 en php

El código que reproduciría el resultado anterior en php sería:

<? echo base64_encode(pack('H*',md5('asdasd'))); ?>

Referencias

, ,

  1. #1 por PacoC el 27-04-10 - 12:44 pm

    Hola, interesante articulo.
    Comentas en tu articulo que un hash MD5 tiene el formato $1$sal$codigo-en-base64. Sin embargo tenia entendido que un hash MD5 genera 32 caracteres en hexadecimal. Por cierto ¿que es la sal?
    Mis dudas surgen porque estoy intentando auntenticar un servidor pop3 courier contra una base de datos MySQL donde tengo los usuarios con sus contraseñas en formato MD5 (con 32 caracteres hexadecimales) y no lo consigo. Puedo autenticar contra texto plano o contra el hash de 13 caracteres que genera el CRYPT(), pero no contra el MD5.
    Si puedes echar una mano, seria bienvenida.
    Un saludo.

    Me gusta

    • #2 por albertomolina el 27-04-10 - 12:59 pm

      Hola Paco,

      El hash MD5 normal o sin sal genera una cadena de 512 bits, que se representan habitualmente por 32 caracteres hexadecimales o en algunos casos por 8 caracteres en base64. Este es el hash MD5 por defecto en muchas aplicaciones, entre otras en MySQL como tú dices.

      El hash MD5 con sal se utiliza para añadirle mayor complejidad al algoritmo y es el que se utiliza en GNU/Linux con la función crypt() para almacenar las contraseñas de los usuarios (aunque también se pueden utilizar otros muchos algoritmos). Para indicarle al sistema el tipo de algoritmo y la sal con la que se ha cifrado están los caracteres $1$…$.

      Con respecto al problema concreto con courier no puedo ayudarte pero espero que estas aclaraciones te sirvan.

      Me gusta

  2. #3 por PacoC el 28-04-10 - 8:58 am

    Hola, gracias por tu respuesta.
    Me funciona la autenticacion courier contra un campo de 13 caracteres en mysql cifrado con la funcion encript de phpmyadmin, ¿tienen alguna relacion ese campo de 13 caracteres que genera el phpmyadmin con los 32 de MD5?

    Me gusta

    • #4 por PacoC el 30-04-10 - 9:21 am

      Hola de nuevo, el problema de courier ya esta resueto. Lo pongo por si le viene bien a alguien. Courier autentica contra por defecto contra los 13 caracteres que genera la funcion crypt. Si queremos que autentique sobre una cadena almacena en MD5, hay que anteponer {MD5} a la cadena almacenada, y esta debe estar en codigo base64, no en hexadecimal.
      Un saludo.

      Me gusta

      • #5 por albertomolina el 30-04-10 - 12:16 pm

        Me alegro de que lo hayas solucionado Paco y gracias por el detalle de comentarlo aquí.

        Saludos

        Me gusta

  3. #6 por Antonio Melé el 29-07-10 - 2:39 pm

    ¡Gracias! Me ha sido de gran utilidad :)

    Me gusta

Responder

Introduce tus datos o haz clic en un icono para iniciar sesión:

Logo de WordPress.com

Estás comentando usando tu cuenta de WordPress.com. Cerrar sesión / Cambiar )

Imagen de Twitter

Estás comentando usando tu cuenta de Twitter. Cerrar sesión / Cambiar )

Foto de Facebook

Estás comentando usando tu cuenta de Facebook. Cerrar sesión / Cambiar )

Google+ photo

Estás comentando usando tu cuenta de Google+. Cerrar sesión / Cambiar )

Conectando a %s

A %d blogueros les gusta esto: