Paulo Henrique Rodrigues Pinheiro

Um blog sobre programação para programadores!


Fixtures em JSON com Senha Criptografada Pelo BCRYPT

Relato de uma pequena e feliz experiência com Perl em uma startup

Prolegômenos

Há algum tempo, pesquisando sobre alternativas para desenvolvimento de sistemas, decidi voltar a programar em Perl Aqui tem uma explicação. E a cada pequeno problema superado fico mais certo de ter acertado. Desenvolvendo um sistema para a gerência de um marketplace voltado ao mercado de pesquisas qualitativas, precisei ter à mão uma forma de gerar dados iniciais mínimos para alimentar uma base em MongoDB.

Os primeiros usuários. A senha precisava estar criptografada, e eu precisava ter em mãos algo para gerar rapidamente, as entradas que eu precisasse. Primeiro pensamento, escrever um programa que alimente o banco de dados. Como o desenvolvimento é com requisitos que ainda estão sendo construídos (intuídos e validados), optei por escrever algo que me imprimisse em JSON o que queria inserir no mongo. Copiar e colar a saída no console do banco e pronto.

Primeira implementação

Precisava de um JSON, criei um:


A. #!/usr/bin/env perl
B.
C. use strict;
D. use warnings;
E.
F. use JSON;
G.
H. my $root_user = {
I.   email => '[email protected]',
J.   name => 'Chail Root Admin',
K.   password => '{#MinhaSenhaSuuuuperSegura#}',
L.   role => 'admin',
M.   active => JSON::true,
N. };
O.
P. print to_json($root_user);

O importante é que na linha F usamos o JSON, e na linha H iniciamos a criação de um hash com os dados necessários. E na linha P o objetivo é atingido.

A função to_json nos cospe os dados em JSON. Muito simples!

Esse programa nos mostra essa saída:


{"role":"admin","email":"[email protected]","password":"{#MinhaSenhaSuuuuperSegura#}","active":true,"name":"Chail Root Admin"}

Para uma saída mais amigável, basta alterar a linha P de:


print to_json($root_user);

Para:


print to_json($root_user, {pretty => 1} );

E então obtemos a seguinte saída:


{
  "name" : "Chail Root Admin",
  "password" : "{#MinhaSenhaSuuuuperSegura#}",
  "role" : "admin",
  "active" : true,
  "email" : "[email protected]"
}

Muito mais amigável. Confesso que tentei primeiro imprimir bonitinho usando Data::Dumper, mas percebi a besteira, voltei ao juízo normal e li o manual.

Testes feitos, agora era tomar cuidado pra não armazenar senhas sem criptografar.

Procurando algo feito para criptografar

Pesquisando na lista de publicações da São Paulo Perl Mongers, encontrei um ótimo texto do Blabo de Blebe, Encriptação se senhas - Uma abordagem com bcrypt. Com as indicações dele, cheguei a outros bons textos, e no , encontrei o que estava procurando: Using bcrypt to secure passwords in a Perl application.

Outro ótimo texto, foi uma implementação em Python, do Vinícius Henrique Marangoni: Como utilizar bcrypt em python.

E a própria página do módulo em Perl:

Crypt::Eksblowfish::Bcrypt.

A partir dessas informações, adaptei os códigos em um módulo:


package Util::Password;

use strict;
use warnings;

use Crypt::Eksblowfish::Bcrypt;
use Crypt::Random;

sub Encrypt {
    my $password = shift;

    # Generate a random salt if not passed
    my $salt = shift || Crypt::Eksblowfish::Bcrypt::en_base64(Crypt::Random::makerandom_octet(Length=>16));;

    # Set the cost to 13 and append a NUL
    my $settings = '$2a$13$'.$salt;

    # Encrypt it
    return Crypt::Eksblowfish::Bcrypt::bcrypt($password, $settings);
 }

 sub Check {
    my $plain_password = shift;
    my $hashed_password = shift;

    # Regex to extract the salt
    if ($hashed_password =~ m!^\$2a\$\d{2}\$([A-Za-z0-9+\\./]{22})!) {
        # Use a letter by letter match rather than a complete string match to avoid timing attacks
        my $match = Encrypt($plain_password, $1);
        my $bad = 0;
        for (my $n=0; $n < length $match; $n++) {
            $bad++ if substr($match, $n, 1) ne substr($hashed_password, $n, 1);
        }

        return $bad == 0;
    } else {
        return 0;
    }
}

1;

Há um parâmetro hardcoded, cost, 13, representado por $13$. Quanto mais alto, mais esforço computacional para calcular, quanto menos, menos esforço. Veja nos artigos citados explicações sobre este parâmetro.

Adaptando e Usando

Então o script primeiro foi alterado para usar o package:


#!/usr/bin/env perl

use strict;
use warnings;

use JSON;

use Util::Password;

my $password = Util::Password::Encrypt('{#MinhaSenhaSuuuuperSegura#}');
my $root_user = {
    email => '[email protected]',
    name => 'Chail Root Admin',
    password => $password,
    role => 'admin',
    active => JSON::true,
};

print to_json($root_user, {pretty => 1} );

E a saída:


{
    "email" : "[email protected]",
    "active" : true,
    "role" : "admin",
    "password" : "$2a$13$KcM2L2HEJ6POekmb8Kp5j.1LnxGB.dk3Bcnaqgnt9x6ZD7PFlzhkO",
    "name" : "Chail Root Admin"
}

E transportando isso através do mouse - com todo o cuidado - para o console do mongodb:


phrp@paulaum:~/Dropbox/Projetos/SaoPauloPM$ mongo
MongoDB shell version: 2.6.3
connecting to: test
> use chail
switched to db chail
> db.users.insert( {
 ...     "email" : "[email protected]",
 ...     "active" : true,
 ...     "role" : "admin",
 ...     "password" : "$2a$13$KcM2L2HEJ6POekmb8Kp5j.1LnxGB.dk3Bcnaqgnt9x6ZD7PFlzhkO",
...     "name" : "Chail Root Admin"
...  })
WriteResult({ "nInserted" : 1 })