¿Cuál es la mejor manera de garantizar que una contraseña proporcionada por el usuario sea una contraseña segura en un formulario de registro o cambio de contraseña?
Una idea que tuve (en Python)
def validate_password(passwd):
conditions_met = 0
conditions_total = 3
if len(passwd) >= 6:
if passwd.lower() != passwd: conditions_met += 1
if len([x for x in passwd if x.isdigit()]) > 0: conditions_met += 1
if len([x for x in passwd if not x.isalnum()]) > 0: conditions_met += 1
result = False
print conditions_met
if conditions_met >= 2: result = True
return result
1: Eliminar las contraseñas de uso frecuente
Verifique las contraseñas ingresadas en una lista de contraseñas de uso frecuente (consulte, por ejemplo, las 100.000 contraseñas principales en la lista de contraseñas filtradas de LinkedIn: http://www.adeptus-mechanicus.com/codex/linkhap/ combo_not.Zip ), asegúrese de incluir sustituciones leetspeek : A @, E3, B8, S5, etc.
Elimine las partes de la contraseña que golpearon contra esta lista de la frase ingresada, antes de pasar a la parte 2 a continuación.
2: No fuerce ninguna regla sobre el usuario
La regla de oro de las contraseñas es que más tiempo es mejor.
Olvídate del uso forzado de mayúsculas, números y símbolos porque (la gran mayoría de) los usuarios: - Convertirán la primera letra en mayúscula; - Pon el número 1
al final; - Poner un !
después de eso si se requiere un símbolo.
En cambio, verifique la seguridad de la contraseña
Para un punto de partida decente, consulte: http://www.passwordmeter.com/
Sugiero como mínimo las siguientes reglas:
Additions (better passwords)
-----------------------------
- Number of Characters Flat +(n*4)
- Uppercase Letters Cond/Incr +((len-n)*2)
- Lowercase Letters Cond/Incr +((len-n)*2)
- Numbers Cond +(n*4)
- Symbols Flat +(n*6)
- Middle Numbers or Symbols Flat +(n*2)
- Shannon Entropy Complex *EntropyScore
Deductions (worse passwords)
-----------------------------
- Letters Only Flat -n
- Numbers Only Flat -(n*16)
- Repeat Chars (Case Insensitive) Complex -
- Consecutive Uppercase Letters Flat -(n*2)
- Consecutive Lowercase Letters Flat -(n*2)
- Consecutive Numbers Flat -(n*2)
- Sequential Letters (3+) Flat -(n*3)
- Sequential Numbers (3+) Flat -(n*3)
- Sequential Symbols (3+) Flat -(n*3)
- Repeated words Complex -
- Only 1st char is uppercase Flat -n
- Last (non symbol) char is number Flat -n
- Only last char is symbol Flat -n
Simplemente seguir passwordmeter no es suficiente, porque efectivamente su ingenuo algoritmo ve Password1! tan bueno, mientras que es excepcionalmente débil. Asegúrese de no tener en cuenta las letras mayúsculas iniciales al puntuar, así como los números y símbolos finales (según las últimas 3 reglas).
Cálculo de la entropía de Shannon
Ver: La forma más rápida de calcular la entropía en Python
3: No permita contraseñas que sean demasiado débiles
En lugar de obligar al usuario a doblegarse a las reglas autodestructivas, permita cualquier cosa que otorgue una puntuación lo suficientemente alta. Qué tan alto depende de su caso de uso.
Y lo más importante
Cuando aceptas la contraseña y la almacenas en una base de datos, ¡asegúrate de ponerle sal y hash! .
Dependiendo del idioma, generalmente uso expresiones regulares para verificar si tiene:
Puede requerir todo lo anterior, o usar un tipo de script de medidor de fuerza. Para mi medidor de fuerza, si la contraseña tiene la longitud correcta, se evalúa de la siguiente manera:
Puede ajustar lo anterior para satisfacer sus necesidades.
El enfoque orientado a objetos sería un conjunto de reglas. Asigne un peso a cada regla e itere a través de ellas. En psuedo-code:
abstract class Rule {
float weight;
float calculateScore( string password );
}
Cálculo de la puntuación total:
float getPasswordStrength( string password ) {
float totalWeight = 0.0f;
float totalScore = 0.0f;
foreach ( rule in rules ) {
totalWeight += weight;
totalScore += rule.calculateScore( password ) * rule.weight;
}
return (totalScore / totalWeight) / rules.count;
}
Un algoritmo de regla de ejemplo, basado en el número de clases de caracteres presentes:
float calculateScore( string password ) {
float score = 0.0f;
// NUMBER_CLASS is a constant char array { '0', '1', '2', ... }
if ( password.contains( NUMBER_CLASS ) )
score += 1.0f;
if ( password.contains( UPPERCASE_CLASS ) )
score += 1.0f;
if ( password.contains( LOWERCASE_CLASS ) )
score += 1.0f;
// Sub rule as private method
if ( containsPunctuation( password ) )
score += 1.0f;
return score / 4.0f;
}
Las dos métricas más simples para verificar son:
Cracklib es excelente, y en los paquetes más nuevos hay un módulo Python disponible para él. Sin embargo, en sistemas que aún no lo tienen, como CentOS 5, he escrito un contenedor de tipos para el sistema cryptlib. Esto también funcionaría en un sistema que no puede instalar python-libcrypt. requiere requiere python con ctypes disponibles, por lo que para CentOS 5 debe instalar y usar el paquete python26.
También tiene la ventaja de que puede tomar el nombre de usuario y buscar contraseñas que lo contengan o sean sustancialmente similares, como la función libcrypt "FascistGecos" pero sin requerir que el usuario exista en/etc/passwd.
My la biblioteca ctypescracklib está disponible en github
Algunos ejemplos de usos:
>>> FascistCheck('jafo1234', 'jafo')
'it is based on your username'
>>> FascistCheck('myofaj123', 'jafo')
'it is based on your username'
>>> FascistCheck('jxayfoxo', 'jafo')
'it is too similar to your username'
>>> FascistCheck('cretse')
'it is based on a dictionary Word'
después de leer las otras respuestas útiles, esto es con lo que voy:
-1 igual que el nombre de usuario
+ 0 contiene nombre de usuario
+ 1 más de 7 caracteres
+ 1 más de 11 caracteres
+ 1 contiene dígitos
+ 1 mezcla de mayúsculas y minúsculas
+ 1 contiene puntuación
+ 1 char no imprimible
pwscore.py:
import re
import string
max_score = 6
def score(username,passwd):
if passwd == username:
return -1
if username in passwd:
return 0
score = 0
if len(passwd) > 7:
score+=1
if len(passwd) > 11:
score+=1
if re.search('\d+',passwd):
score+=1
if re.search('[a-z]',passwd) and re.search('[A-Z]',passwd):
score+=1
if len([x for x in passwd if x in string.punctuation]) > 0:
score+=1
if len([x for x in passwd if x not in string.printable]) > 0:
score+=1
return score
ejemplo de uso:
import pwscore
score = pwscore(username,passwd)
if score < 3:
return "weak password (score="
+ str(score) + "/"
+ str(pwscore.max_score)
+ "), try again."
probablemente no sea el más eficiente, pero parece razonable. No estoy seguro de que FascistCheck => 'demasiado similar al nombre de usuario' valga la pena.
'abc123ABC! @ £' = puntaje 6/6 si no es un superconjunto de nombre de usuario
tal vez eso debería puntuar más bajo.
Existe el cracker de contraseña abierto y gratuito John the Ripper que es una excelente manera de verificar una base de datos de contraseñas existente.
Bueno, esto es lo que uso:
var getStrength = function (passwd) {
intScore = 0;
intScore = (intScore + passwd.length);
if (passwd.match(/[a-z]/)) {
intScore = (intScore + 1);
}
if (passwd.match(/[A-Z]/)) {
intScore = (intScore + 5);
}
if (passwd.match(/\d+/)) {
intScore = (intScore + 5);
}
if (passwd.match(/(\d.*\d)/)) {
intScore = (intScore + 5);
}
if (passwd.match(/[!,@#$%^&*?_~]/)) {
intScore = (intScore + 5);
}
if (passwd.match(/([!,@#$%^&*?_~].*[!,@#$%^&*?_~])/)) {
intScore = (intScore + 5);
}
if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/)) {
intScore = (intScore + 2);
}
if (passwd.match(/\d/) && passwd.match(/\D/)) {
intScore = (intScore + 2);
}
if (passwd.match(/[a-z]/) && passwd.match(/[A-Z]/) && passwd.match(/\d/) && passwd.match(/[!,@#$%^&*?_~]/)) {
intScore = (intScore + 2);
}
return intScore;
}
Escribí una pequeña aplicación Javascript. Eche un vistazo: Sin embargo, otro medidor de contraseña . Puede descargar la fuente y usarla/modificarla bajo GPL. ¡Que te diviertas!
No sé si alguien encontrará esto útil, pero realmente me gustó la idea de un conjunto de reglas como lo sugiere phear, así que fui y escribí una regla Python 2.6 clase (aunque probablemente sea compatible con 2.5):
import re
class SecurityException(Exception):
pass
class Rule:
"""Creates a rule to evaluate against a string.
Rules can be regex patterns or a boolean returning function.
Whether a rule is inclusive or exclusive is decided by the sign
of the weight. Positive weights are inclusive, negative weights are
exclusive.
Call score() to return either 0 or the weight if the rule
is fufilled.
Raises a SecurityException if a required rule is violated.
"""
def __init__(self,rule,weight=1,required=False,name=u"The Unnamed Rule"):
try:
getattr(rule,"__call__")
except AttributeError:
self.rule = re.compile(rule) # If a regex, compile
else:
self.rule = rule # Otherwise it's a function and it should be scored using it
if weight == 0:
return ValueError(u"Weights can not be 0")
self.weight = weight
self.required = required
self.name = name
def exclusive(self):
return self.weight < 0
def inclusive(self):
return self.weight >= 0
exclusive = property(exclusive)
inclusive = property(inclusive)
def _score_regex(self,password):
match = self.rule.search(password)
if match is None:
if self.exclusive: # didn't match an exclusive rule
return self.weight
Elif self.inclusive and self.required: # didn't match on a required inclusive rule
raise SecurityException(u"Violation of Rule: %s by input \"%s\"" % (self.name.title(), password))
Elif self.inclusive and not self.required:
return 0
else:
if self.inclusive:
return self.weight
Elif self.exclusive and self.required:
raise SecurityException(u"Violation of Rule: %s by input \"%s\"" % (self.name,password))
Elif self.exclusive and not self.required:
return 0
return 0
def score(self,password):
try:
getattr(self.rule,"__call__")
except AttributeError:
return self._score_regex(password)
else:
return self.rule(password) * self.weight
def __unicode__(self):
return u"%s (%i)" % (self.name.title(), self.weight)
def __str__(self):
return self.__unicode__()
¡Espero que alguien encuentre esto útil!
Ejemplo de uso:
rules = [ Rule("^foobar",weight=20,required=True,name=u"The Fubared Rule"), ]
try:
score = 0
for rule in rules:
score += rule.score()
except SecurityException e:
print e
else:
print score
DESCARGO DE RESPONSABILIDAD: No se ha probado la unidad
Además del enfoque estándar de mezclar alfa, numérico y símbolos, noté que cuando me registré con MyOpenId la semana pasada, el verificador de contraseñas le dice si su contraseña se basa en una palabra del diccionario, incluso si agrega números o reemplaza alfa con números similares. (usando cero en lugar de 'o', '1' en lugar de 'i', etc.).
Estaba muy impresionado.