BCrypt com Ruby

A Liga dos Programadores
5 min readJul 3, 2018

--

Saudações, programadores. Eu sou Askeey (Askeey#1967) e hoje trago-lhes um simples tutorial de como usar o algoritmo de hashing BCrypt com Ruby. Apesar deste tutorial estar direcionado àqueles que usam Ruby, recomendo que todos os interessados no assunto de segurança de senhas leiam.

Guardar senhas, ou qualquer outro tipo de informação sensível, em forma de texto puro, isto é, texto entedível por humanos, não é uma boa ideia, pois qualquer um que tenha acesso ao banco de dados verá e fará o que quiser com essa informação desprotegida (sem contar que também é uma grande falta de respeito com a privacidade do usuário). Para resolver isso, podemos usar um algoritmo de hashing.

O que é um algoritmo de hashing?
Um algoritmo de hashing é um algoritmo matemático que transforma uma informação com número de bits indeterminado em uma string de tamanho fixo. É uma função de mão única, ou seja, é uma função impossível de inverter.

Usar um algoritmo de hashing em uma informação não é a mesma coisa que encriptar essa informação de modo simétrico/assimétrico, pois na criptografia simétrica/assimétrica há uma chave que é usada para encriptar e também desencriptar, isto é, reverter a informação encriptada para seu estado original.

O que é BCrypt?
O BCrypt é com certeza um dos algoritmos de hashing mais usados hoje em dia. Foi escrito por Niels Provos e David Mazières em 1999. Está disponível em muitas linguagens: Java, C, C#, Python, PHP, Ruby ❤ e outras.

Instalando a gem
Abra seu terminal e escreva o seguinte comando para instalar a gem do BCrypt:

gem install bcrypt

Usando o BCrypt
Bem, chega de enrolação e vamos finalmente usar o BCrypt.
Ainda no terminal, entre na irb e inclua a gem com a seguinte linha:

require “bcrypt”

Se tudo deu certo na hora da instalação, você receberá um retorno igual a true (=> true). Agora crie uma variável com um texto qualquer (essa será nossa senha original):

senha_original = “tesla_foi_roubado”
=> “tesla_foi_roubado”

E depois crie outra variável chamada “senha_segura” que será igual ao retorno do método que transforma a senha em um hash:

senha_segura = BCrypt::Password.create(senha_original) 
=> “$2a$10$p0Ul41NjDBv5IG.TJAgvV.8CJSN/lC9bRjFuVmwOucpD8yKJTDff6”

Opa! O que é esse retorno? É a nossa senha “tesla_foi_roubado”, mas agora em forma de hash. O retorno que você recebeu é, com certeza, diferente do meu. O BCrypt cria uma “impressão digital virtual” nova cada vez que recebe uma informação, mesmo se essa informação for idêntica a que você passou anteriormente. Isso significa que eu posso passar a string “WiFi” como argumento mil vezes e vou receber um hash diferente toda vez.

BCrypt::Password.create(“WiFi”) 
=> “$2a$10$kaL83w7Zgn/QcLEMsXCQP.LeFATY76BE2vHNcgYPqV90G7M6hWt7e”
BCrypt::Password.create(“WiFi”)
=> “$2a$10$04lMCMgQt8p4TWGfERpAmeeskyHVNwM/bfJdtuSvLUEMjHlETU97C”
BCrypt::Password.create(“WiFi”)
=> “$2a$10$RVinRXmHp1cPCcXJhdtGd.lMICtexEK/kLyAYVW/SYAMA9lgdSv.a”
BCrypt::Password.create(“WiFi”)
=> “$2a$10$vtLbcrd1Nc5Six9U5NAG9OTfIG7P3D0nS.Jw/2r276FOehTj6fhim”

É óbvio que é possível fazer mais coisas com o BCrypt, como, por exemplo, checar se o nosso hash é igual a senha original:

senha_segura == senha_original
=> true

Observe que checar se o hash é igual a senha original é diferente de checar se a senha original é igual ao hash:

senha_original == senha_segura
=> false

O que são Salts?
Mas até o BCrypt com hashessozinhos” tem fraquezas: o hacker pode descobrir senhas com um ataque por força bruta (brute force). Mas, felizmente, há uma solução para isso: os salts. O salt é um pedaço de informação aleatória que é adicionada à senha antes do algoritmo ser aplicado. Isso poderia ser representado como:

hash(salt + senha)

O que são Fatores Cost?
Além dos salts, o BCrypt permite que você modifique o quanto de trabalho será preciso para aplicar o algoritmo na senha. Essa quantidade trabalho é chamada fator cost. O valor padrão usado pela gem do BCrypt é 10. Podemos checar isso com a seguinte linha de código:

senha_segura.cost
=> 10

Podemos mudar o valor do fator cost de duas maneiras: globalmente (assim não teremos que especificar o cost cada vez que querermos um valor diferente de 10) ou para uma senha específica (o valor do cost só muda para as senhas que você quiser).

1. Globalmente

BCrypt::Engine.cost = 6
=> 6
senha_cost6 = BCrypt::Password.create(“WiFi”)
=> “$2a$06$6kmbxdLKLv8hvDcwIRIE2.YGsRUs/FudHkQlnGKwAQP19Tov99BNy”
senha_cost6.cost
=> 6

2. Para uma senha específica

senha_cost11 = BCrypt::Password.create(“WiFi”, :cost => 11)
=> “$2a$11$wN9c7kbYid8h/kgKvw6aIOhCli4a5raNBD4uMSrdzT9zFFrr5LyE2”
senha_cost11.cost
=> 11
senha_cost_padrao = BCrypt::Password.create(“WiFi”)
=> “$2a$10$RG6zw6AGLqU9Cj3OjCuQwe3uQXM//HTUMdEUH1hj0b.NNZzx3mO82”
senha_cost_padrao.cost
=> 10

Entendendo a estrutura do hash formado
Agora que sabemos o que é hash, salt e fator cost podemos começar a entender o que aquela linha que o BCrypt retorna para nós significa. Naquela linha é possível achar a versão do BCrypt que foi usada, o fator cost, o salt e a senha. Os campos versão, fator cost e salt são separados pelo sinal $. Para diferenciar o salt da senha, contamos os primeiros 22 caractéres que vem logo após o terceiro sinal $ e o resto é a senha que será usada para a autenticação. Veja o exemplo a seguir:

$2a$10$vI8aWBnW3fID.ZQ4/zo1G.q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa$2a = versão do bcrypt = 2a
$10 = fator cost = 10
$vI8aWBnW3fID.ZQ4/zo1G. = salt = vI8aWBnW3fID.ZQ4/zo1G.
q1lRps.9cGLcZEiGDMVr5yUP1KUOYTa = senha

Hash do hash do hash do hash
Vamos fazer com que a nossa variável “senha_original” seja igual a “ruby”:

senha_original = “ruby”
=> “ruby”

Depois vamos criar quatro hashes, nos quais cada um é igual ao hash do seu antecessor:

hash1 = BCrypt::Password.create(senha_original)
=> “$2a$10$5uHbc3HwzlVOkWkbmTs.6e2rdgarUWiGaW85zrnZWB2IqgYapL2EG”
hash2 = BCrypt::Password.create(hash1)
=> “$2a$10$s97iMnA.4D5SdoUKT7Qnkur0uuScSas4hQNUtOoogOEbZiOif3EpS”
hash3 = BCrypt::Password.create(hash2)
=> “$2a$10$dMiliaSCXMojEa7h1BNspOesF4iTfIeHTJOmRcU5gvJclewPFgu.G”
hash4 = BCrypt::Password.create(hash3)
=> “$2a$10$ihv0jA3NzDM1vJLHQRFYJ.22Oc0IH7VuvOzhHVPxx.cUuhE8EZpaa”

Agora eu te pergunto: se eu executar hash4 == hash1, qual será o retorno? True ou false? Se você respondeu que o retorno será false, parabéns, você está correto. Mas se você não respondeu certo, não fique chateado, eu também respondi errado. Eu achei que como hash4 = hash3 = hash2 = hash1 então hash4 = hash1 também, mas na verdade o único hash igual a hash4 é o hash3 e o único igual ao hash3 é o hash2 e o único igual ao hash2 é o hash1 e o único igual ao hash1 é a variável “senha_original”.

Comparando hash do banco de dados com entrada do usuário
Digamos que você precisa checar se um valor que o usuário digitou é igual a um hash que está guardado em um banco de dados. A primeira vez que fui fazer isso, fiz, provavelmente, do jeito que você pensou em fazer: simplesmente comparar o hash do banco com a entrada do usuário.

# entrada do usuário
senha = gets.strip!
if Usuario.get(cláusula_where).senha == senha then
puts “Bem-vindo!”
else
puts “Senha incorreta!”
end

Com esse bloco de código, o que sempre receberemos como retorno é “Senha incorreta!”, mesmo se a senha estiver certa. Por que? O hash guardado no banco não é mais uma instância do BCrypt, é uma string normal como qualquer outra. Comparar a entrada do usuário com o hash do banco é mesma coisa que comparar “aquecimento global” == “nikola tesla”. Para comparar os dois corretamente precisamos usar um método já pronto do BCrypt:

# entrada do usuário
senha = gets.strip!
if BCrypt::Password.new(Usuario.senha).is_password? senha then
puts “Bem-vindo!”
else
puts “Senha incorreta!”
end

Bem, isso tudo é um resumo do que consegui estudar em uma madrugada. Espero ter ajudado.

--

--

A Liga dos Programadores

Maior servidor de desenvolvedores falantes de português do Discord.