Pergunta

Qual é a melhor maneira de garantir que uma senha fornecida pelo usuário seja uma senha forte em um formulário de registro ou alteração de senha?

Uma ideia que tive (em 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
Foi útil?

Solução

1:Elimine senhas usadas com frequência
Verifique as senhas inseridas em uma lista de senhas usadas com frequência (veja, por exemplo,as 100.000 principais senhas na lista de senhas vazadas do LinkedIn: http://www.adeptus-mechanicus.com/codex/linkhap/combo_not.zip), certifique-se de incluir substituições de Leetspeek:A@, E3, B8, S5, etc.
Remova partes da senha que batem nesta lista da frase inserida, antes de ir para a parte 2 abaixo.

2:Não force nenhuma regra ao usuário

A regra de ouro das senhas é que quanto mais tempo, melhor.
Esqueça o uso forçado de letras maiúsculas, números e símbolos porque (a grande maioria dos) usuários irá:- Coloque a primeira letra em maiúscula;- Coloque o número 1 no final;- Colocar um ! depois disso, se um símbolo for necessário.

Em vez disso, verifique a força da senha

Para um ponto de partida decente, consulte: http://www.passwordmeter.com/

Sugiro no mínimo as seguintes regras:

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

Apenas seguindo medidor de senha não é suficiente, porque com certeza seu algoritmo ingênuo vê Senha1! tão bom, enquanto é excepcionalmente fraco.Certifique-se de desconsiderar as letras maiúsculas iniciais ao pontuar, bem como os números e símbolos finais (de acordo com as últimas 3 regras).

Calculando a entropia de Shannon
Ver: Maneira mais rápida de calcular entropia em Python

3:Não permita senhas muito fracas
Em vez de forçar o usuário a obedecer a regras autodestrutivas, permita qualquer coisa que dê uma pontuação alta o suficiente.Quão alto depende do seu caso de uso.

E o mais importante
Quando você aceita a senha e a armazena em um banco de dados, certifique-se de salgar e picar!.

Outras dicas

Dependendo da linguagem, costumo utilizar expressões regulares para verificar se possui:

  • Pelo menos uma maiúscula e uma letra minúscula
  • Pelo menos um numero
  • Pelo menos um caractere especial
  • Um comprimento de pelo menos seis caracteres

Você pode exigir todos os itens acima ou usar um script do tipo medidor de força.Para meu medidor de força, se a senha tiver o comprimento correto, ela será avaliada da seguinte forma:

  • Uma condição atendida:senha fraca
  • Duas condições atendidas:senha média
  • Todas as condições atendidas:senha forte

Você pode ajustar o acima para atender às suas necessidades.

A abordagem orientada a objetos seria um conjunto de regras.Atribua um peso a cada regra e repita-as.Em psuedo-código:

abstract class Rule {

    float weight;

    float calculateScore( string password );

}

Calculando a pontuação 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;

}

Um exemplo de algoritmo de regra, baseado no número de classes 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;

}

As duas métricas mais simples de verificar são:

  1. Comprimento.Eu diria 8 caracteres no mínimo.
  2. Número de classes de caracteres diferentes que a senha contém.Geralmente são letras minúsculas, letras maiúsculas, números, pontuação e outros símbolos.Uma senha forte conterá caracteres de pelo menos três dessas classes;se você forçar um número ou outro caractere não alfabético, reduzirá significativamente a eficácia dos ataques de dicionário.

O Cracklib é ótimo e em pacotes mais recentes existe um módulo Python disponível para ele.No entanto, em sistemas que ainda não o possuem, como o CentOS 5, escrevi um wrapper ctypes para o sistema cryptlib.Isso também funcionaria em um sistema no qual você não pode instalar o python-libcrypt.Isto faz requer python com ctypes disponíveis, portanto, para CentOS 5 você deve instalar e usar o pacote python26.

Ele também tem a vantagem de poder pegar o nome de usuário e verificar senhas que o contenham ou que sejam substancialmente semelhantes, como a função "FascistGecos" da libcrypt, mas sem exigir que o usuário exista em /etc/passwd.

Meu A biblioteca ctypescracklib está disponível no github

Alguns exemplos de uso:

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

depois de ler as outras respostas úteis, é isso que vou fazer:

-1 igual ao nome de usuário
+0 contém nome de usuário
+1 em mais de 7 caracteres
+1 em mais de 11 caracteres
+1 contém dígitos
+1 combinação de letras maiúsculas e minúsculas
+1 contém pontuação
+1 caractere não imprimível

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

exemplo de uso:

import pwscore
    score = pwscore(username,passwd)
    if score < 3:
        return "weak password (score=" 
             + str(score) + "/"
             + str(pwscore.max_score)
             + "), try again."

provavelmente não é o mais eficiente, mas parece razoável.Não tenho certeza de que o fascistcheck => 'muito semelhante ao nome de usuário' vale a pena.

'abc123ABC!@£' = pontuação 6/6 se não for um superconjunto de nome de usuário

talvez isso deva pontuar mais baixo.

Existe o aberto e gratuito João, o Estripador cracker de senha, que é uma ótima maneira de verificar um banco de dados de senhas existente.

Bem, isso é o que eu 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;
} 

Além da abordagem padrão de misturar alfa, numérico e símbolos, percebi quando me registrei no MyOpenId na semana passada, o verificador de senha informa se sua senha é baseada em uma palavra do dicionário, mesmo se você adicionar números ou substituir alfas por números semelhantes (usando zero em vez de 'o', '1' em vez de 'i', etc.).

Fiquei bastante impressionado.

Eu escrevi um pequeno aplicativo Javascript.Dê uma olhada: Mais um medidor de senha.Você pode baixar a fonte e usá-la/modificá-la sob GPL.Divirta-se!

Não sei se alguém achará isso útil, mas gostei muito da ideia de um conjunto de regras sugerido por phear, então escrevi uma regra para a classe Python 2.6 (embora provavelmente seja compatível com 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 alguém ache isso útil!

Exemplo 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

ISENÇÃO DE RESPONSABILIDADE:Não testado em unidade

Se você tiver tempo, execute um cracker de senha nele.

Verificadores de força de senha, e se você tiver tempo + recursos (é justificado apenas se você estiver verificando mais do que algumas senhas), use Rainbow Tables.

Com uma série de verificações para garantir que atende aos critérios mínimos:

  • pelo menos 8 caracteres
  • contém pelo menos um símbolo não alfanumérico
  • não corresponde nem contém nome de usuário/e-mail/etc.
  • etc.

Aqui está um plugin jQuery que informa a força da senha (não tentei):http://phiras.wordpress.com/2007/04/08/password-strength-meter-a-jquery-plugin/

E a mesma coisa portada para PHP:http://www.alixaxel.com/wordpress/2007/06/09/php-password-strength-algorithm/

Qual é a melhor maneira de garantir que uma senha fornecida pelo usuário seja uma senha forte em um formulário de registro ou alteração de senha?

Não avalie a complexidade e/ou força, os usuários encontrarão uma maneira de enganar seu sistema ou ficarão tão frustrados que irão embora.Isso só vai te trazer situações assim.Apenas exija um determinado comprimento e que as senhas vazadas não sejam usadas.Pontos bônus:certifique-se de que tudo o que você implementa permite o uso de gerenciadores de senhas e/ou 2FA.

Licenciado em: CC-BY-SA com atribuição
Não afiliado a StackOverflow
scroll top