Cadastrando Usuários com Relativa Segurança
Contexto
Oi, povo.
Continuo parcialmente exilado numa cabaninha no Tibete, mas tenho tentando manter contato com um mundo por um telefone-sem-fio (daqueles com latinhas) de setecentos metros montanha abaixo. Estive aprendendo com os monges sobre segurança da informação, achei a área muito legal e a melhor forma de fixar o conteúdo, além de treinar, é compartilhar. Não é um assunto normalmente tratado pelas bandas dos desenvolvedores independentes, mas deveria ser. Já tem gente invadindo geladeiras.
Neste artigo discutiremos sobre como gerenciar o registro de um usuário de forma relativamente segura. O objetivo é garantir que ele tenha uma senha digna e que ela seja salva sejá-lá-onde-precisar-ser sem muita dor de cabeça. Iremos usar a linguagem Python 3 para os códigos de exemplo. É interessante algum conhecimento mínimo em programação para prosseguir.
Criando o UsuárioNossos usuários precisarão de uma classe. Podemos mantê-la o mais simples possível apenas para iniciar o código:
class User:
def __init__(self, user: str, passwd: str) -> str:
""" Create an User Object """
self.username = user
self.passwd = passwd
Gerenciando o RegistroPara testarmos nosso método, precisaremos de um gerenciador. Um objeto que fará as ações propriamente ditas:
# Tranqueiras que serão usadas em breve. Importe-as:
import hashlib
import secrets
import string
class Manager:
def __init__(self):
""" Initialize object and locals """
self.users = [] # Lista onde os usuários serão salvos.
# As quatro variáveis abaixo serão nossos meios de validar se uma senha é segura ou não.
self.passwd_characteres_lower = string.ascii_lowercase
self.passwd_characteres_upper = string.ascii_uppercase
self.passwd_numbers = string.digits
self.passwd_special = string.punctuation
A primeira coisa a ser verificada durante um registro é o
usuário. Ele já existe ou o nome está disponível? Podemos descobrir com um método simples. Note que não exemplifiquei a função que pega a entrada do usuário, pulamos esta parte por se tratar de um simples
input().
def check_username(self, new_username: str) -> str:
""" Check if is a valid Username """
for user in self.users:
if user.username == new_username:
print("This username already exists.")
return False
return True
Se o usuário for válido, podemos prosseguir para a validação da senha. A sequencia de métodos à seguir verifica o número de caracteres, se há ou não letras maiúsculas e minúsculas, digitos e caracteres especiais. Para cada elemento destes, temos uma quantidade específica de caracteres.
Tenha em mente que segurança é proporcionalmente inversa à usabilidade, ao menos por enquanto. Tome cuidado para não exigir de um usuário a senha
nU*(HN*IN2kjbY7*bdiDBp*(0(Jn@@@@@@@@@----23232kdjsma para acessar uma calculadora.
def check_passwd(self, passwd: str) -> str:
""" Check if is a valid password """
while True:
# Check Size
if len(passwd) < 14:
print("This password is too short.")
break
# Check Ascii Lowercase Letters
counter = 0
for i in passwd:
if i in self.passwd_characteres_lower:
counter += 1
if counter < 3:
print("Your password need at last tree (3) lowercase characteres.")
break
# Check Ascii Uppercase Letters
counter = 0
for i in passwd:
if i in self.passwd_characteres_upper:
counter += 1
if counter < 3:
print("Your password need at last tree (3) uppercase characteres.")
break
# Check Digits
counter = 0
for i in passwd:
if i in self.passwd_numbers:
counter += 1
if counter < 6:
print("Your password need at last six (6) digits.")
break
# Check Special Characteres
counter = 0
for i in passwd:
if i in self.passwd_special:
counter += 1
if counter < 2:
print("Your password need at last two (2) special characteres.")
break
# The password has passed!
return True
Se a senha passou por todo este processo, ela é segura. Certo? Não necessariamente. Nós não nos certificamos de que a senha não tenha padrões óbvios como
123456,
abcdef,
QWERT, nem se contém palavras como
senha ou coisas do tipo. Isso pode nos tomar algum tempo. Aqui, iremos substituir esta etapa acrescentando outro atributo ao usuário: o
salt. Literalmente, jogaremos uma pitada na senha para fortalecer o conjunto. Nossa classe de usuário ficará assim:
class User:
def __init__(self, user: str, passwd: str, salt: str) -> str:
""" Create an User Object """
self.username = user
self.passwd = passwd
self.salt = salt
Por fim, adicionados outro método ao nosso gerenciador. Ele usa o módulo
secrets para gerar trechos aleatórios, nossos
salts, que depois são incluídos no final da senha do usuário. Por exemplo, se a senha digitada for
123, ela será passada para o próximo processamento como
12326Z1xpHZUTD-lUofbT-b9g. O
salt gerado deve ser salvo na conta, pois toda vez que o usuário tentar acessar seu perfil, o mesmo
salt deve ser adicionado à senha inserida.
FinalizandoCerto, estamos quase terminando.
Quase porque não adianta passar por toda essa labuta se, no final, a senha for salva no servidor da seguinte forma:
user: Gibson Willian
password: 12326Z1xpHZUTD-lUofbT-b9g
A última etapa é passar o resultado
senha+salt por um algorítimo de
hash. Resumindo bastante, uma hash é... Bem, é o que sai de um liquidificador sempre que você joga os mesmos ingredientes na mesma quantidade e pelo mesmo tempo. Mais ou menos. Para que nós dois aprendamos mais sobre elas, vale a
recomendação.
def protect_passwd(self, passwd: str) -> str:
""" Encrypt the password before save it. """
salt = secrets.token_urlsafe(32)
encripted_passwd = hashlib.sha256((passwd + salt).encode()).hexdigest()
return (encripted_passwd, salt)
Veja abaixo o que o código gera como os dados de um usuário.
(Senha mantida visível por questões óbvias):
Type your username: Gibson Willian
Type your password: @123456asdfgQWERT@
User name: Gibson Willian
Saved password: 872282732817f394f0a82e27cd2fc92b7769ecbced7fc3ec217f8ec0b33fcc76
This-user salt: dt98sQo6VBmKKTKg5TtfarTU9kAXljAhfcaE7yxUwGs
Ficamos por aqui. O código completo, inclusas as funções de inseção para testes está
aqui. É só executar. :D
Exercícios:
1. Crie uma função de acesso, permitindo que usuários cadastrados previamente entrem com suas respectivas credenciais.
Referências de Conteúdo
Se você não programa, o curso
Python - Fundamentos para Análise de Dados da
DataScience garante que você comece bem. Cobre desde os primeiros passos na lógica até introduções à
big data,
deep learning e demais tecnologias modernas com nome engraçado, mas que no fundo são quase a mesma coisa. Só não fique preso à uma linguagem por ser ela fácil. Eu fiquei e estou me lascando pra entender
C e os malditos
pointers.
Tudo é um pointer de um pointer de um pointer nesse troço.Já pra começar pela parte da segurança, o curso da
Solyd é um bom primeiro passo. Você vai aprender o que é estritamente necessário pra começar, então não pense que irá encontrar segredos profundos de bruxaria por lá. Mas eu recomendo justamente por ser mais simples, menos formal. Para não assustar quem está na ponta da fila.
Dica extra: dá pra pegar por um terço do preço em promoções.Os cursos da
Desec eu ainda estou fazendo, mas já posso recomendar. O conteúdo é bem mais aprofundado, a prática é mais organizada e os tópicos divididos em aulas menores, fáceis de consultar posteriormente. São relativamente caros, mas não me arrependo. Agora, se a sua pessoa quiser ir um pouco mais além: para análise forense, não é incomum encontrar o curso da
Basis Tech de graça, sobre a ferramenta deles que é usada por agências de todo canto:
Autopsy.
Estes caras aqui também podem te ajudar a tomar um rumo na vida.
Enfim, o conteúdo é muito variado. Pra criptografia e tecnologias ou técnicas mais específicas eu recomendo o de sempre: livros. Autores famosos como
Mitnick,
Dafydd Sttutard para a coisa em si. Outras personas de áreas externas, mas que podem te ajudar aqui como
Paul Ekman,
Joe Navarro,
Maria Konnikova,
Amy Herman... Enfim. É o caso de identificar qualquer coisa que possa ser útil e como.