Armazenando senhas de forma segura

Dica ao leitor: não deixe de ler a seção “O mecanismo correto”.

Diga aí, você anota a senha do seu email em um post-it e deixa ele colado em seu monitor, visível a qualquer um? Aposto que não, pois seria muito fácil para alguém acessar sua conta e mandar emails engraçadinhos para seus contatos. Da mesma forma que você protege sua senha, os aplicativos que utilizam informações de login e senha para autenticar usuários (como o seu serviço de email) também devem cuidar das senhas armazenadas neles, não devendo nunca guardá-las em texto puro. Vamos ver nesse post como funciona a autenticação, e as melhores práticas para implementar esse serviço na sua aplicação de forma razoavelmente segura.

Autenticação

Você já parou para pensar em como funciona a autenticação em um serviço de email? Primeiro, você digita o seu nome de usuário e senha em um formulário web, como o exemplo abaixo:

Usuário: pythonhelp
  Senha: *******

Esses dados são então enviados para o servidor que está fornecendo o serviço de autenticação do seu email. Lá dentro, o serviço de autenticação irá procurar por um usuário chamado pythonhelp no banco de dados de usuários. Se encontrá-lo, irá realizar uma comparação (mais para frente veremos que não é uma simples comparação de duas strings) para verificar se a senha fornecida no formulário web corresponde à senha do usuário. Em caso positivo, a autenticação ocorre com sucesso e você pode então acessar sua conta de email. Em caso negativo, aquela mensagenzinha chata avisando que você errou seu nome de usuário ou sua senha aparece na tela. (A propósito, você já percebeu que a maioria dos serviços não informa se o que erramos foi o nome de usuário ou se foi a senha? Esse tipo de informação é usualmente interessante para um invasor em potencial.)

Deixando de lado alguns detalhes, a autenticação funciona basicamente da forma descrita acima. Antes de vermos como isso tudo poderia ser implementado, veremos como NÃO deve ser implementado um serviço de autenticação.

Como NÃO implementar autenticação

Vamos desenvolver o serviço de autenticação para a nossa aplicação. Para isso, criamos uma tabela no banco de dados chamada USUARIOS, que contém duas informações sobre cada usuário: seu nome de usuário e sua senha. Como você pode ver abaixo, armazenamos a senha dos usuários em texto puro, ou seja, as senhas estão visíveis a qualquer pessoa que obtiver acesso ao banco de dados.

+--------------+
| USUARIOS     |
+--------------+------------------------+
|    NOME      |        SENHA           |
+--------------+------------------------+
| joaozinho    | teste                  |
+--------------+------------------------+
| pedrinho     | teste123               |
+--------------+------------------------+
| maria        | t35t3                  |
+--------------+------------------------+

Para fazer a autenticação, basta que o usuário forneça a sua senha e que comparemos a senha fornecida com a que está armazenada no BD.

Isso até funciona, mas eu é que não forneceria a minha senha para um sistema meia-boca desses que vai armazená-la em texto puro no banco de dados. Sabe por quê? Porque uma vez que alguém obtenha acesso ao banco de dados do sistema, basta isso para quebrar a privacidade de todos os usuários:

user@host:~/$ sqlite usuarios.db
sqlite> select * from usuarios;
joaozinho|teste
pedrinho|teste123
maria|t35t3

You're doing it wrong!

Que coisa, não? As senhas estão expostas. Falha de segurança gravíssima! Sabendo que a maioria dos usuários usa a mesma senha para os logins em vários sites, dá pra ter uma idéia do estrago né?

Lição número 1: JAMAIS ARMAZENE SENHAS EM TEXTO PURO!

Um jeito melhor

Agora que você já sabe como não fazer, vamos ver uma forma um pouquinho melhor (ainda não a correta) de implementar um mecanismo de autenticação.

Dessa vez nós não vamos armazenar as senhas dos usuários no BD. O que vamos armazenar é uma informação relacionada à senha e gerada a partir dela, o chamado hash da senha.

O que é o Hash?

O hash de um valor é o resultado da aplicação de uma função de hashing a tal valor. Esse resultado é, em geral, muito diferente do valor original. Uma função de hashing H recebe como entrada um valor x e retorna como resultado o hash h correspondente àquele valor:

H(x) -> h

Vamos calcular o hash do valor 'teste123':

H('teste123') -> 'aa1bf4646de67fd9086cf6c79007026c'

Vamos agora calcular o hash do valor 'teste12':

H('teste12') -> '0940004e70ce8d82b440d3c1244dfdee'

Vamos calcular novamente o hash do valor 'teste123':

H('teste123') -> 'aa1bf4646de67fd9086cf6c79007026c'

Agora vamos aplicar a função de hash ao valor 'aa1bf4646de67fd9086cf6c79007026c' (que é o hash de 'teste123'):

H(‘aa1bf4646de67fd9086cf6c79007026c’) -> ‘0728a200630cec4b33e33e20646bc54a’

Observando com atenção, você pode notar algumas coisas sobre as funções de hash:

  1. A função de hash gera um resultado cujo valor é muito diferente do valor original.
  2. Quando alteramos levemente o valor de entrada para a função de hash, o valor retornado por ela muda completamente (veja os exemplos de aplicação nas entradas 'teste123' e 'teste12'). Isso é chamado de efeito avalanche.
  3. Quando aplicamos H novamente à entrada 'teste123', obtivemos o valor idêntico ao obtido pela primeira vez. Ou seja, a função de hash é determinística.
  4. O último exemplo nos mostra que uma função de hash não é reversível, isto é, dado o hash de um valor, não conseguiremos descobrir o valor original através desse hash.

A partir das observações acima, podemos inferir algumas propriedades que as funções de hash possuem:

  1. É improvável (muito improvável mesmo) que você modifique a entrada da função sem modificar o resultado dela.
  2. É impossível gerar o valor de entrada a partir do resultado.
  3. É muito difícil (muito mesmo) encontrar dois valores para os quais a função de hashing produza o mesmo resultado.
  4. Sempre que aplicada ao mesmo valor x, uma mesma função H(x) irá retornar o mesmo resultado h.

* Nos exemplos acima, usei o algoritmo de hashing MD5, embora existam vários outros que poderiam ser igualmente usados, como SHA-1, SHA-256, etc.

Autenticação com hash

Conhecendo as propriedades acima, podemos criar um mecanismo de autenticação mais seguro. O objetivo é armazenar as informações de senha de forma mais protegida, em vez de deixá-la exposta em texto puro.

A primeira idéia pode ser simplesmente armazenar apenas o hash da senha. Parece uma boa idéia, afinal, a propriedade 2 diz que se alguém roubar o valor do hash da sua senha, não conseguirá obter a senha propriamente dita, e a propriedade 4 nos possibilita verificar se a senha está correta comparando o hash do que o usuário digitou com o hash armazenado no banco de dados.

Veja abaixo como ficaria o nosso novo BD de usuários. Para cada usuário, armazenamos o hash da senha que ele forneceu no cadastro.

+-----------+
| USUARIOS  |                                  
+-----------+----------------------------------+
| NOME      |         HASH DA SENHA            |
+-----------+----------------------------------+
| joaozinho | 698dc19d489c4e4db73e28a713eab07b |
+-----------+----------------------------------+
| pedrinho  | aa1bf4646de67fd9086cf6c79007026c |
+-----------+----------------------------------+
| maria     | c7ac7410983dc7efbb2e5c062c515b7d |
+-----------+----------------------------------+

Quando o usuário quiser se autenticar no sistema, teremos em mãos a senha fornecida por ele na tela de login, mas não podemos compará-la diretamente ao valor armazenado no banco, pois o que está armazenado é o hash da senha. O serviço de autenticação deverá aplicar a função de hash sobre a senha fornecida pelo usuário e comparar o resultado com o que está armazenado no BD. Se o valor obtido for idêntico ao hash armazenado no banco de dados para aquele usuário, a autenticação ocorre com sucesso. Caso contrário, erro de autenticação.

Agora as senhas estarão um pouquinho mais seguras. Isso mesmo, somente um pouco, pois existem meios para descobrir o valor da senha original através do hash dela. A próxima seção vai descrever um pouco isso.

Ataques ao banco de dados de senhas (ou hashes delas)

Uma coisa que acontece com frequência é um sistema sofrer uma invasão e os invasores realizarem uma cópia do seu banco de dados. Considere que os invasores roubaram o seguinte banco de dados:

+-----------+
| USUARIOS  |                                  
+-----------+----------------------------------+
| NOME      |         HASH DA SENHA            |
+-----------+----------------------------------+
| joaozinho | 698dc19d489c4e4db73e28a713eab07b |  <-- hash de "teste"
+-----------+----------------------------------+
| pedrinho  | aa1bf4646de67fd9086cf6c79007026c |  <-- hash de "teste123"
+-----------+----------------------------------+
| maria     | 2a1bdbdad93b1081007abc4b419d8f0b |  <-- hash de "t35t3"
+-----------+----------------------------------+
|    ...    |               ...                |
+-----------+----------------------------------+

O que eles poderiam fazer com esses dados? De acordo com as propriedades que vimos sobre as funções de hashing, não seria possível extrair o valor original que gerou o hash, então você imaginaria que as senhas estariam seguras dessa maneira.

Teoricamente, sim. Mas agora imagine que o invasor (uma pessoa muuuuuuito paciente), de posse do hash da senha do usuário joaozinho (698dc19d489c4e4db73e28a713eab07b), comece a fazer alguns testes com valores comumente usados como senhas:

>>> import hashlib
>>> print hashlib.md5('123').hexdigest()
202cb962ac59075b964b07152d234b70
>>> print hashlib.md5('abcde').hexdigest()
ab56b4d92b40713acc5af89985d4b786
>>> print hashlib.md5('bla').hexdigest()
128ecf542a35ac5270a87dc740918404
>>> print hashlib.md5('teste').hexdigest()
698dc19d489c4e4db73e28a713eab07b

Opa! O invasor acabou de descobrir a senha do joaozinho (teste), pois o hash obtido para essa string é o mesmo hash que está armazenado para o joaozinho no BD. Se ele continuar testando valores que considera prováveis de serem usados como senha, ele pode, eventualmente, acabar fazendo:

>>> print hashlib.md5('teste123').hexdigest()
aa1bf4646de67fd9086cf6c79007026c

Então ele terá descoberto a senha do usuário pedrinho, pois o hash gerado para teste123 possui o mesmo valor que está armazenado para esse usuário. A esse tipo de ataque, chamamos de Ataque por força bruta.

Imagino que você esteja pensando que o invasor terá muito trabalho para descobrir a senha do usuário maria, testando milhões de possibilidades antes de chegar em t35t3. Pois é. Se ele for testando manualmente, dificilmente irá descobrir a senha. Mas imagine agora que o invasor tenha escrito um programa que gere uma tabela gigantesca contendo possíveis senhas e seus hashes:

+----------------------------------+---------------------+
|              HASH                |       SENHA         |
+----------------------------------+---------------------+
| ...                              | ...                 |
+----------------------------------+---------------------+
| 698dc19d489c4e4db73e28a713eab07b | teste               |
+----------------------------------+---------------------+
| e959088c6049f1104c84c9bde5560a13 | teste1              |
+----------------------------------+---------------------+
| 38851536d87701d2191990e24a7f8d4e | teste2              |
+----------------------------------+---------------------+
| 56c1056afb34f0d5ad809821d417a52b | t3st3               |
+----------------------------------+---------------------+
| 2a1bdbdad93b1081007abc4b419d8f0b | t35t3               |
+----------------------------------+---------------------+
| ...                              | ...                 |
+----------------------------------+---------------------+
| 128ecf542a35ac5270a87dc740918404 | bla                 |
+----------------------------------+---------------------+
| 14a310c18e7ee2627b3de4ff82b11e76 | bl4                 |
+----------------------------------+---------------------+
| ...                              | ...                 |
+----------------------------------+---------------------+

Tendo essa tabela pré-calculada, uma vez que o invasor esteja de posse do hash da senha da maria (2a1bdbdad93b1081007abc4b419d8f0b), basta pesquisar por esse hash na tabela de hashes. Se encontrar um registro que possua tal hash, basta obter a senha que o acompanha.

É evidente que essa tabela deve ser gigantesca, gigantesca mesmo, para conter uma boa quantidade de combinações de valores de senhas. Para “facilitar o trabalho”, invasores do mundo todo colaboram na criação e busca em tabelas desse tipo. Essas tabelas são conhecidas como Rainbow Tables.

Não só isso, os próprios usuários colaboram com os invasores, usando as mesmas senhas em vários lugares, ou usando palavras simples de adivinhar, de forma que bem antes duma tabela dessas cobrir todas as possibilidades de hash, ela já pode ser extremamente útil para os invasores.

Lição número 2: O HASH SOZINHO NÃO FAZ MILAGRE!

Evitando as Rainbow Tables

Para lidar com isso, existe uma técnica chamada de salgar senhas (tradução literal do inglês “salting passwords”), que consiste em adicionar um “temperinho” na senha antes de armazenar. A idéia é gerar uma string contendo alguns valores aleatórios e concatenar essa string à senha do usuário na hora gerar o hash. Assim, ao invés de tomar somente a senha do usuário como entrada, a função de hashing passa a tomar como entrada também a string aleatória (chamada de salt, ou sal). Esse sal também é armazenado no BD para que posteriormente seja possível que o serviço de autenticação realize a verificação.

O que isso traz de segurança para o sistema? Vamos visualizar uma tabela que armazena o nome do usuário, o hash da concatenação entre senha e sal, e o sal. A senha de cada usuário é a mesma senha mostrada lá no início do post.

+-----------+
| USUARIOS  |                                  
+-----------+----------------------------------+-----------+
| NOME      |       HASH DE (SENHA+SAL)        |    SAL    |
+-----------+----------------------------------+-----------+
| joaozinho | e3e923b2c0d3890270a2cb6d52b13bf6 |   h6ja8   |
+-----------+----------------------------------+-----------+
| pedrinho  | 046b8f04069efab205d9f7bcc099e3d3 |   5uoWB   |
+-----------+----------------------------------+-----------+
| maria     | 960e18e4dc393660fdf3caba8634f38e |   jtm7a   |
+-----------+----------------------------------+-----------+

Veja como foram gerados os hashes armazenados:

>>> print hashlib.md5('teste'+'h6ja8').hexdigest()
e3e923b2c0d3890270a2cb6d52b13bf6
>>> print hashlib.md5('teste123'+'5uoWB').hexdigest()
046b8f04069efab205d9f7bcc099e3d3
>>> print hashlib.md5('t35t3'+'jtm7a').hexdigest()
960e18e4dc393660fdf3caba8634f38e

Está achando estranho o fato de termos armazenado o sal em texto puro no BD? Pois é, se o invasor roubar nossa base, ele terá de lambuja o valor do sal. Mas isso não é um problema, pois o objetivo principal de salgarmos a senha é impossibilitar a utilização de rainbow tables, afinal a idéia principal por trás dessas tabelas é o cálculo prévio dos hashes de vários valores. Ao roubar o sal e o hash de uma senha, o invasor teria que recalcular toda a rainbow table para poder descobrir a senha do usuário. E essa tarefa é muito custosa.

Assim, se cada usuário possuir um sal diferente, a rainbow table terá que ser recalculada para cada usuário, tornando essa atividade praticamente impossível.

Considere que o invasor possui a rainbow table abaixo, bem como o hash (e3e923b2c0d3890270a2cb6d52b13bf6) e o sal (h6ja8) do usuário joaozinho (roubados da base de dados de usuários).

+----------------------------------+---------------------+
|              HASH                |       SENHA         |
+----------------------------------+---------------------+
| ...                              | ...                 |
+----------------------------------+---------------------+
| 698dc19d489c4e4db73e28a713eab07b | teste               |
+----------------------------------+---------------------+
| e959088c6049f1104c84c9bde5560a13 | teste1              |
+----------------------------------+---------------------+
| 38851536d87701d2191990e24a7f8d4e | teste2              |
+----------------------------------+---------------------+
| 507eb04c9c427e9f961e47a7204fac41 | teste3              |
+----------------------------------+---------------------+
| ...                              | ...                 |
+----------------------------------+---------------------+
| 128ecf542a35ac5270a87dc740918404 | bla                 |
+----------------------------------+---------------------+
| df5ea29924d39c3be8785734f13169c6 | blabla              |
+----------------------------------+---------------------+
| ...                              | ...                 |
+----------------------------------+---------------------+

Para obter a senha do usuário, o invasor terá que recalcular toda a tabela. Por exemplo, terá que calcular o hash de “testeh6ja8”, de “teste1h6ja8”, e assim por diante, até encontrar o hash correspondente. Considerando que ela pode ter zilhões de registros, essa é uma tarefa bastante demorada. Imagine recalcular toda a tabela para todas as possíveis combinações de valores no sal.

Perceba que o sal que utilizamos é bastante curto. Quanto mais longo for o sal, mais protegidas contra rainbow tables as senhas estarão, pois o número de combinações possíveis de valores para o sal aumentam exponencialmente.

Dessa forma, o sal derruba a maior força das rainbow tables, que é a busca rápida por elementos (que ocorre graças ao cálculo prévio dos valores). Ao usar valores de sal suficientemente longos, estamos aumentando MUITO (MUITO MESMO) a quantidade de entradas que uma rainbow table deve ter para servir como uma base de hashes pré-calculados (segundo a Wikipedia, hoje em dia são usados salts de até 128 bits). Se, além disso, usarmos um sal diferente para cada usuário, então tornamos impossível o uso de tabelas pré-computadas para a descoberta das senhas dos nossos usuários.

Mas, ainda assim, hashes salgados não são a solução definitiva para armazenamento de senhas. Leia a próxima seção para descobrir o porquê.

O mecanismo correto

Mesmo sendo um mecanismo muito mais seguro do que armazenando a senha em texto puro ou o hash simples, o armazenamento de hashes de senhas salgadas ainda não é a melhor solução. Apesar de amplamente utilizados, os algoritmos de hashing como MD5 e SHA-1 não são recomendados para o armazenamento de senhas, pois são bastante velozes para realizar o hashing de um valor. Um algoritmo rápido torna a geração de ataques via força bruta e rainbow tables mais fácil, pois o tempo necessário para gerar as tabelas acaba sendo pequeno.

Um algoritmo para uso no armazenamento de senhas tem como requisito ser lento. Lento o suficiente para atrapalhar o atacante, mas não o bastante para atrapalhar o usuário.

Uma forma recomendada de armazenar as senhas dos usuários é usando o bcrypt. O BCrypt é um mecanismo criptográfico criado para lidar com senhas. Assim sendo, uma de suas características é ser demorado para geração do hash.

Para ter uma idéia da diferença na velocidade dos dois mecanismos, observe o resultados de uns testes que fiz usando o timeit:

$ python -m timeit -s "import bcrypt; salt = bcrypt.gensalt()" "bcrypt.hashpw('teste', salt)"
10 loops, best of 3: 243 msec per loop

$ python -m timeit -s "import hashlib" "hashlib.md5('teste')"
1000000 loops, best of 3: 0.409 usec per loop

Enquanto o MD5 gera os hashes em uma média de 0.409 microsegundos por hash, o bcrypt leva em média 243 milisegundos por hash.

Instalando o bcrypt

A implementação Python do bcrypt não está disponível com a biblioteca-padrão, portanto é necessário instalá-la através do gerenciador de pacotes pip:

sudo pip install py-bcrypt

Obs.: o módulo é escrito em linguagem C, portanto o código será compilado. Para isso, é necessário possuir instalados (em sistema Ubuntu): build-essential e python-dev.

Alternativamente, num sistema Debian ou Ubuntu você pode instalar o pacote no repositório do apt:

sudo apt-get install python-bcrypt

Usando o bcrypt

Assim como a maioria dos módulos Python, o bcrypt é facinho de usar. Vamos fazer alguns exemplos.

Gerando o hash de uma senha

>>> import bcrypt
>>> print bcrypt.hashpw('teste123', bcrypt.gensalt())
$2a$12$axTaPizQ6q2V8VCLseqEq.Rwm1aZVx7oimvjPmmLWTNE3uW15xpIu

Execute o código acima no seu interpretador Python local e perceba a diferença no tempo de resposta entre o bcrypt e o MD5.

Verificando a senha do usuário

Para verificar se determinado hash é correto, devemos passá-lo como argumento para a função hashpw, juntamente com a senha:

>>> print bcrypt.hashpw("teste123", "$2a$12$axTaPizQ6q2V8VCLseqEq.Rwm1aZVx7oimvjPmmLWTNE3uW15xpIu")
$2a$12$axTaPizQ6q2V8VCLseqEq.Rwm1aZVx7oimvjPmmLWTNE3uW15xpIu

Se ela retornar o mesmo hash que foi passado como argumento, é porque o hash corresponde à senha passada como primeiro argumento.

Podemos então criar uma função de validação de senha de usuário:

def valida_senha(senha_digitada, hash_senha):
    return bcrypt.hashpw(senha_digitada, hash_senha) == hash_senha

Um sistema de autenticação usando BD

Vamos agora implementar um mecanismo para autenticação de usuários, usando sqlite3 e bcrypt.

# -*- encoding:utf-8 -*-
import bcrypt
import sqlite3

def valida_senha(senha_digitada, hash_senha):
    return bcrypt.hashpw(senha_digitada, hash_senha) == hash_senha

def insere_usuario(conexao, usuario, senha):
    hash_senha = bcrypt.hashpw(senha, bcrypt.gensalt())
    conexao.execute('insert into USUARIOS values ("%s", "%s")' % (usuario, hash_senha))
    conexao.commit()

def usuario_autenticado(conexao, usuario, senha):
    cursor = conexao.execute('select SENHA from USUARIOS where NOME = "%s"' % (usuario,))
    dados = cursor.fetchone()
    hash_senha = str(dados[0])
    return valida_senha(senha, hash_senha)

# alguns testes
if __name__=='__main__':
    conexao = sqlite3.connect('arquivo.db')
    insere_usuario(conexao, 'maria', 'teste')
    insere_usuario(conexao, 'joao', 'teste123')
    if usuario_autenticado(conexao, 'joao', 'teste123'):
        print 'joao está autenticado!'
    else:
        print 'Xiiiii...'

 

Antes de terminar …

O post mostrou quão simples é a implementação de um mecanismo seguro para armazenamento das senhas dos usuários em um banco de dados. Agora você não tem mais desculpas para implementar a autenticação usando senhas armazenadas em texto puro.

Se você armazena as senhas em texto puro, é fácil alterar seus algoritmos de autenticação e gerar os hashes das senhas existentes (faça backup antes, claro :)). Vale a pena gastar um tempinho corrigindo isso em seu sistema.

Finalizando, o py-bcrypt é tão simples de ser utilizado que me deixa à vontade para deixar como lição final:

LIÇÃO FINAL: USE O BCRYPT PARA ARMAZENAMENTO DE SENHAS DE USUÁRIOS!

LIÇÃO DEFINITIVA: NÃO SEJA RELAPSO COM AS INFORMAÇÕES DOS SEUS USUÁRIOS!

Leia mais sobre o assunto

Em inglês:

P.S.: obrigado ao eliasdorneles por ter revisado esse post. 🙂

Anúncios

7 comentários sobre “Armazenando senhas de forma segura

  1. Muito boa as dicas é praticamente um tutorial. Eu sempre procurei artigos sobre segurança e banco de dados, ta ai… encontrei o que precisava agora só estudar e por em pratica.

  2. Boa noite,

    Primeiramente parabéns pelo artigo. Sou iniciante em programação e estou criando uma aplicação que utiliza “md5” para salvar a senha do usuário recem cadastrado no banco de dados. Comando 01. Porem estou tendo dificuldades para ler a md5 do banco e autenticar o usuário no sistema, comando02. Já dei uma bos pesquisada na documentação do python, porem estou perdido na onde devo chamar a md5 no comando 02 para que o sistema consiga logar o usuário. Se puder dar uma ajuda eu agradeço.

    Comando 01 insere o usuário e grava a senha em md5:
    def post(self):
    username = self.get_argument(“username”,False)
    name = self.get_argument(“name”,False)
    passw = self.get_argument(“passw”,False)
    msg=False

    try:
    #conexão com o banco de dados
    sql= “””

    INSERT INTO `crash`.`user` (`username`, `name`, `passw`)
    VALUES (‘%s’, ‘%s’, md5(‘%s’));
    “””

    sql = sql % (username,name,passw)
    resultado = self.application.db.execute(sql)#criei a variavel user

    Comando 02 usuário vai autenticar no sistema porem ao efetuar a leitura no banco não consigo passar para a aplicação o md5:

    class LoginHandler(main.BaseHandler):
    def get(self):

    self.render(“login.html”, titulo =”Login”)

    def post(self):
    username = self.get_argument(“username”,False)#criei aqui a viarel username
    if username:
    sql = “SELECT `username`, `name`, `passw` FROM `user` WHERE username = ‘%s'”
    user = self.application.db.get(sql % username)#criei a variavel user
    if user:
    password = self.get_argument(“password”)
    if password == user[“passw”]:
    self.set_secure_cookie(“username”, username)
    else:
    self.redirect(self.get_argument(“next”, “/”))
    self.redirect(“/”)

    • Alexandre, pelo que percebi, você está verificando o md5 obtido do banco de dados com a senha em texto puro (password = self.get_argument(“password”)). Você deve aplicar o md5 sobre a senha vinda como argumento para verificar se é igual ao md5 armazenado no banco.

      A propósito, o md5 sozinho não fornece um mecanismo de autenticação muito confiável. No post eu apresento alguns problemas relacionados a ele.

  3. Olá Valdir.
    O post está muito bom, parabéns!
    Não sei se você chegou a pensar nisso mas preciso encriptar minha senha, armazena-la em um banco de dados, por coincidência também usei o sqlite3 e, em outra sessão, ou seja, ao chamar o programa novamente devo desencriptar a senha para conferir se esta está correta. O que ocorre é que ao salvar a sena possui um hash e ao tentar desencriptá-la ele gera um hash diferente. Como faço para obter o mesmo hash? Isso é possível?

    Muito obrigada.

    • Olá, Roberta!

      Funções de hash são funcões de uma só via. Ou seja, não há como descobrir o valor original de algo se você possuir somente o hash desse algo. Além disso, ao fazer hash(hash(s)) você obtem um novo hash. Isso é assim de propósito e o fato de ser irreversível é o que torna as funções de hash atrativas para o armazenamento de senhas. Veja um exemplo:

      >>> from hashlib import sha1
      >>> senha = 'abc123'
      >>> senha_codificada = sha1(senha).hexdigest()
      >>> print senha_codificada
      6367c48dd193d56ea7b0baad25b19455e529f5ee
      >>> print sha1(senha_codificada).hexdigest()
      5f1526ab5af8b63fcb10178bda39fb04dcb516b1
      >>> senha_codificada == sha1('abc123').hexdigest()
      True
      

      Por que você precisa desencriptar a senha salva no BD? Se for só pra comparar com o que o usuário digitou, você pode aplicar a função de hash sobre o valor digitado e então comparar com o hash que há armazenado no BD. Se o valor digitado for o mesmo que a senha, os hashes serão iguais.

      Espero que isso ajude.

Deixe um comentário

Preencha os seus dados abaixo ou clique em um ícone para log in:

Logotipo do WordPress.com

Você está comentando utilizando sua conta WordPress.com. Sair / Alterar )

Imagem do Twitter

Você está comentando utilizando sua conta Twitter. Sair / Alterar )

Foto do Facebook

Você está comentando utilizando sua conta Facebook. Sair / Alterar )

Foto do Google+

Você está comentando utilizando sua conta Google+. Sair / Alterar )

Conectando a %s