Pregunta

Estaba buscando un algoritmo efectivo que pueda darme una idea precisa de qué tan fuerte es una contraseña.

Descubrí que varios sitios web diferentes usan diferentes algoritmos a medida que obtengo diferentes clasificaciones de seguridad de contraseña en diferentes sitios web.

¿Fue útil?

Solución

Esto ha crecido hasta mi volcado general de mejores prácticas para trabajar con contraseñas en PHP / MySQL. Las ideas presentadas aquí generalmente no son mías, pero son las mejores de lo que he encontrado hasta la fecha.


Asegúrese de utilizar SSL para todas las operaciones relacionadas con la información del usuario. Todas las páginas que involucren estos formularios deben verificar que se están llamando a través de HTTPS y se niegan a trabajar de otra manera.

Puede eliminar la mayoría de los ataques simplemente limitando el número de inicios de sesión fallidos permitidos.

Permita contraseñas relativamente débiles, pero almacene la cantidad de inicios de sesión fallidos por usuario y requiera una verificación de captcha o contraseña por correo electrónico si la excede. Establecí mis fallas máximas a 5.

La presentación de fallas de inicio de sesión al usuario debe ser cuidadosamente pensada para no proporcionar información a los atacantes. Un inicio de sesión fallido debido a un usuario inexistente debe devolver el mismo mensaje que un inicio de sesión fallido debido a una contraseña incorrecta. Al proporcionar un mensaje diferente, los atacantes podrán determinar los inicios de sesión de usuarios válidos.

También asegúrate de devolver exactamente el mismo mensaje en caso de una falla por demasiados inicios de sesión con una contraseña válida, y una falla con demasiados inicios de sesión y una contraseña incorrecta. Proporcionar un mensaje diferente permitirá a los atacantes determinar las contraseñas de usuario válidas. Un número razonable de usuarios cuando se les obliga a restablecer su contraseña simplemente la devolverán a su estado actual.

Lamentablemente, no es práctico limitar el número de inicios de sesión permitidos por dirección IP. Varios proveedores, como AOL y la mayoría de las empresas dirigen sus solicitudes web. Imponer este límite eliminará efectivamente a estos usuarios.


Descubrí que la comprobación de las palabras del diccionario antes de enviar no es eficiente, ya que debe enviar un diccionario al cliente en JavaScript o enviar una solicitud de ajax por cambio de campo. Hice esto por un tiempo y funcionó bien, pero no me gustó el tráfico que generó.

La comprobación de contraseñas intrínsecamente débiles, menos las palabras del diccionario, es práctica del lado del cliente con un simple javascript.

Después de enviar, verifico las palabras del diccionario y el nombre de usuario que contiene la contraseña y viceversa, del lado del servidor. Muy buenos diccionarios son fácilmente descargables y la prueba contra ellos es simple. Una idea es que para probar una palabra del diccionario, debe enviar una consulta a la base de datos, que a su vez contiene la contraseña. La forma de solucionar esto fue cifrar mi diccionario de antemano con un simple cifrado y poner SALT en la posición final y luego probar la contraseña cifrada. No es ideal, pero es mejor que el texto sin formato y solo en el cable para las personas en sus máquinas físicas y subredes.

Una vez que esté satisfecho con la contraseña que eligieron, primero debe cifrarla con PHP y luego guardarla. La siguiente función de cifrado de contraseña tampoco es mi idea, pero resuelve varios problemas. El cifrado dentro de PHP evita que las personas en un servidor compartido intercepten sus contraseñas no cifradas. Agregar algo por usuario que no cambie (uso el correo electrónico ya que este es el nombre de usuario de mis sitios) y agregar un hash (SALT es una cadena corta y constante que cambio por sitio) aumenta la resistencia a los ataques. Debido a que la SALT se encuentra dentro de la contraseña, y la contraseña puede ser de cualquier longitud, resulta casi imposible atacarla con una tabla de arco iris. Alternativamente, también significa que las personas no pueden cambiar su correo electrónico y usted no puede cambiar la SALT sin invalidar la contraseña de todos.

EDITAR: ahora recomendaría usar PhPass en lugar de mi función de rollo aquí, o simplemente olvide por completo los inicios de sesión de los usuarios y utilice OpenID en su lugar.

function password_crypt($email,$toHash) {
  $password = str_split($toHash,(strlen($toHash)/2)+1);
  return hash('sha256', $email.$password[0].SALT.$password[1]); 
}

Mi medidor de contraseña del lado del cliente Jqueryish. El objetivo debe ser un div. El ancho cambiará entre 0 y 100 y el color de fondo cambiará según las clases indicadas en el script. Una vez más, en su mayoría robado de otras cosas que he encontrado:

$.updatePasswordMeter = function(password,username,target) {
$.updatePasswordMeter._checkRepetition = function(pLen,str) {
res = ""
for ( i=0; i<str.length ; i++ ) {
    repeated=true;
    for (j=0;j < pLen && (j+i+pLen) < str.length;j++)
        repeated=repeated && (str.charAt(j+i)==str.charAt(j+i+pLen));
    if (j<pLen) repeated=false;
    if (repeated) {
        i+=pLen-1;
        repeated=false;
    }
    else {
        res+=str.charAt(i);
    };
};
return res;
};
var score = 0;
var r_class = 'weak-password';
//password < 4
if (password.length < 4 || password.toLowerCase()==username.toLowerCase()) { 
target.width(score + '%').removeClass("weak-password okay-password good-password strong-password"
).addClass(r_class);
  return true;
} 
//password length
score += password.length * 4;
score += ( $.updatePasswordMeter._checkRepetition(1,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(2,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(3,password).length - password.length ) * 1;
score += ( $.updatePasswordMeter._checkRepetition(4,password).length - password.length ) * 1;
//password has 3 numbers
if (password.match(/(.*[0-9].*[0-9].*[0-9])/))  score += 5; 

//password has 2 symbols
if (password.match(/(.*[!,@,#,$,%,^,&,*,?,_,~].*[!,@,#,$,%,^,&,*,?,_,~])/)) score += 5; 

//password has Upper and Lower chars
if (password.match(/([a-z].*[A-Z])|([A-Z].*[a-z])/))  score += 10; 

//password has number and chars
if (password.match(/([a-zA-Z])/) && password.match(/([0-9])/))  score += 15; 
//
//password has number and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([0-9])/))  score += 15; 

//password has char and symbol
if (password.match(/([!,@,#,$,%,^,&,*,?,_,~])/) && password.match(/([a-zA-Z])/))  score += 15; 

//password is just a nubers or chars
if (password.match(/^\w+$/) || password.match(/^\d+$/) )  score -= 10; 

//verifing 0 < score < 100
score = score * 2;
if ( score < 0 )  score = 0;
if ( score > 100 )  score = 100;
if (score > 25 ) r_class = 'okay-password';
if (score > 50  ) r_class = 'good-password';
if (score > 75 ) r_class = 'strong-password';
target.width(score + '%').removeClass("weak-password okay-password good-password strong-password"
).addClass(r_class);
return true;
};

Otros consejos

Fundamentalmente, usted quiere prevenir los principales tipos de ataques

  • ataques del diccionario
  • Ataques de fuerza bruta

Para evitar la primera, debe considerar las contraseñas que contienen palabras comunes débiles. Para evitar el segundo, desea alentar contraseñas de una longitud razonable (8+ caracteres es común) y con un conjunto de caracteres razonablemente grande (incluya letras, números y caracteres especiales). Si considera que las letras minúsculas y mayúsculas son diferentes, eso aumenta sustancialmente el conjunto de caracteres. Sin embargo, esto crea un problema de usabilidad para algunas comunidades de usuarios, por lo que debe equilibrar esa consideración.

Una búsqueda rápida en Google mostró soluciones que explican los ataques de fuerza bruta (contraseña compleja) pero no los ataques de diccionario. Medidor de fortaleza de contraseña de PHP de esta lista Los comprobadores de fuerza ejecutan la verificación del lado del servidor, por lo que podría extenderse para revisar un diccionario.

EDITAR:

Por cierto ... también debe limitar el número de intentos de inicio de sesión por usuario. Esto hará que ambos tipos de ataques sean menos probables. Efectivo pero no fácil de usar es bloquear una cuenta después de X intentos incorrectos y requerir un restablecimiento de contraseña. Más fácil de usar, pero más esfuerzo es acelerar el tiempo entre los intentos de inicio de sesión. También puede solicitar CAPTCHA después de los primeros intentos de inicio de sesión (que es algo que Stack Overflow requiere después de demasiadas ediciones , o para usuarios muy nuevos).

Básicamente, es probable que desee utilizar expresiones regulares para validar la longitud y la complejidad de la contraseña.

Puede encontrar un buen ejemplo haciendo esto usando javascript aquí:

http://marketingtechblog.com/programming/javascript-password-strength/

Como lo señaló Daren Schwenke, será mejor que trabaje en la seguridad y no ponga esto en manos de los usuarios .

Pero es bueno proporcionar algunas sugerencias al usuario sobre qué tan segura es su contraseña, porque la mejor manera de obtener una contraseña sigue siendo una actividad social .

Para poder piratear un pequeño script del lado del cliente que verifica la fortaleza de la contraseña del usuario como un indicador de cortesía, en tiempo real. No bloquea nada, pero le da una buena sensación de calor cuando se pone verde :-)

Básicamente, lo que debe verificar es sentido común: verifique si la contraseña contiene letras, números y caracteres no alfabéticos, en una cantidad razonable.

Puedes hackear tu propio algo muy fácilmente: solo marca 10/10:

  • 0 es una contraseña de longitud cero;
  • +2 por cada 8 caracteres en la contraseña (se supone que 15 es una longitud segura);
  • +1 para el uso de una letra, +2 para el uso de 2 letras;
  • +1 para el uso de un número, +2 para el uso de 2 números;
  • +1 para el uso de caracteres no alfabéticos, +2 para 2.

No necesita verificar contraseñas divinas (hay letras en mayúscula, dónde se ubican los caracteres especiales, etc.), sus usuarios no están en la industria de películas de python del banco / servicio militar / secreto / mensual, ¿verdad?

Puedes codificar eso en una hora sin habilidades locas de javascript.

Y de todos modos, valide la contraseña y mueva todo el código de seguridad en el lado del servidor. Si puede delegar la autenticación (por ejemplo: abrir ID), aún mejor.

No puedo pensar en un algoritmo específico para verificar la fuerza de una contraseña. Lo que hacemos es definir varios criterios y cuando la contraseña respeta un criterio, agregamos 1 a su puntaje. Cuando la contraseña alcanza un umbral, la contraseña es segura. De lo contrario es débil.

Puede definir muchos niveles diferentes de fuerza si con un dominio diferente, o puede definir un valor diferente para un criterio específico. Por ejemplo, si una contraseña tiene 5 caracteres, agregamos 1, pero si obtuvo 10, agregamos 2.

aquí hay una lista de criterios para verificar

Longitud (8 a 12 está bien, más es mejor) Contiene letra minúscula Contiene letra mayúscula La letra mayúscula NO es la primera. Contiene numero Contiene simbolos El último carácter NO es un símbolo parecido a un ser humano (por ejemplo: o!) No parece una sola palabra. Algunos crack de contraseña inteligente contienen una biblioteca de sustitutos de palabras y letras (como Biblioteca - > L1br @ ry)

Espero que la ayuda.

¡No hagas tu propio rollo!

Los expertos en criptografía desalientan la criptografía de roll-your-own Por razones que deberían ser obvias.

Por las mismas razones, uno no debe intentar rodar su propia solución al problema de medir la fortaleza de una contraseña; es en gran medida un problema criptográfico.

No te metas en el feo negocio de crear una expresión regular masiva para este propósito; es probable que no tenga en cuenta varios factores que influyen en la fortaleza general de una contraseña.

Es un problema difícil

Hay una dificultad considerable inherente al problema de medir la fortaleza de una contraseña. Cuanta más investigación realice sobre este tema, más me doy cuenta de que esto es un " unidireccional " problema; es decir, uno no puede medir la " dificultad " (costo computacional) de descifrar una contraseña eficientemente . Más bien, es más eficiente proporcionar requisitos de complejidad y medir la capacidad de la contraseña para cumplirlos.

Cuando consideramos el problema de manera lógica, un " índice de crackability " No tiene mucho sentido, tan conveniente como suena. Hay muchos factores que impulsan el cálculo, la mayoría de los cuales se relacionan con los recursos computacionales dedicados al proceso de craqueo, por lo que no son prácticos.

Imagine que se enfrenta a John the Ripper (o una herramienta similar) con la contraseña en cuestión; podría tomar días descifrar una contraseña decente, meses para descifrar una buena contraseña y hasta que el sol se queme para descifrar una contraseña excepcional. Este no es un medio práctico para medir la seguridad de la contraseña.

Abordar el problema desde la otra dirección es mucho más manejable: si proporcionamos un conjunto de requisitos de complejidad, es posible juzgar la fuerza relativa de una contraseña muy rápidamente. Obviamente, los requisitos de complejidad suministrados deben evolucionar con el tiempo, pero hay muchas menos variables para tener en cuenta si abordamos el problema de esta manera.

Una solución viable

Hay una utilidad independiente disponible de Openwall titulada passwdqc (probablemente , representando Password Quality Checker ). El desarrollador de Openwall, Solar Designer, parece ser un experto en criptografía de buena fe (sus trabajos hablan por sí mismos), por lo que está calificado para crear dicha herramienta.

Para mi caso de uso particular, esta es una solución mucho más atractiva que usar un fragmento de JavaScript mal concebido que vive en algún rincón oscuro de la Web.

Lo más difícil es establecer parámetros para sus necesidades particulares. La implementación es la parte fácil.

Un ejemplo práctico

Ofrezco una implementación simple en PHP para proporcionar un inicio rápido. Se aplican exenciones de responsabilidad estándar.

Este ejemplo asume que estamos suministrando una lista completa de contraseñas al script PHP. No hace falta decir que si está haciendo esto con contraseñas reales (por ejemplo, las eliminadas de un administrador de contraseñas), se debe tener mucho cuidado con respecto al manejo de contraseñas. ¡Simplemente la escritura del volcado de contraseña sin cifrar en el disco pone en peligro la seguridad de sus contraseñas!

passwords.csv :

"Title","Password"
"My Test Password","password123"
"Your Test Password","123456!!!"
"A strong password","NFYbCoHC5S7dngitqCD53tvQkAu3dais"

password-check.php :

<?php

//A few handy examples from other users:
//http://php.net/manual/en/function.str-getcsv.php#117692

$csv = array_map('str_getcsv', file('passwords.csv'), [',']);

array_walk($csv, function(&$a) use ($csv) {
    $a = array_combine($csv[0], $a);
});

//Remove column header.

array_shift($csv);

//Define report column headers.

$results[] = [
    'Title',
    'Result',
    'Exit Code',
];

$i = 1;

foreach ($csv as $p) {
    $row['title'] = $p['Title'];

    //If the value contains a space, it's considered a passphrase.

    $isPassphrase = stristr($p['Password'], ' ') !== false ? true : false;

    $cmd = 'echo ' . escapeshellarg($p['Password']) . ' | pwqcheck -1 min=32,24,22,20,16 max=128';

    if ($isPassphrase) {
        $cmd .= ' passphrase=3';
    }
    else {
        $cmd .= ' passphrase=0';
    }

    $output = null;
    $exitCode = null;

    $stdOut = exec($cmd, $output, $exitCode);

    //Exit code 0 represents an unacceptable password (not an error).
    //Exit code 1 represents an acceptable password (it meets the criteria).

    if ($exitCode === 0 || $exitCode === 1) {
        $row['result'] = trim($stdOut);
        $row['exitCode'] = $exitCode;
    }
    else {
        $row['result'] = 'An error occurred while calling pwqcheck';
        $row['exitCode'] = null;
    }

    $results[$i] = $row;

    $i++;
}

$reportFile = 'report.csv';

$fp = @fopen($reportFile, 'w');

if ($fp !== false) {
    foreach ($results as $p) {
        fputcsv($fp, $p);
    }

    fclose($fp);
}
else {
    die($reportFile . ' could not be opened for writing (destination is not writable or file is in use)');
}

exit;

report.csv resultante:

Title,Result,"Exit Code"
"My Test Password","Bad passphrase (too short)",1
"Your Test Password","Bad passphrase (too short)",1
"A strong password",OK,0

Conclusión

Todavía tengo que encontrar una solución más completa en la Web; No hace falta decir que agradezco cualquier otra recomendación.

Obviamente, este enfoque no es ideal para ciertos casos de uso (por ejemplo, un "medidor de seguridad de contraseñas" implementado por el lado del cliente). Aun así, sería trivial realizar una llamada AJAX a un recurso del lado del servidor que devuelva una respuesta de aprobación / falla utilizando el enfoque descrito anteriormente, pero tal enfoque debería asumir el potencial de abuso (por ejemplo, ataques DoS) y requeriría comunicación segura entre el cliente y el servidor, así como la aceptación de los riesgos asociados con la transmisión de la contraseña sin hash.

Licenciado bajo: CC-BY-SA con atribución
No afiliado a StackOverflow
scroll top