Como gerar um hash no client-side? – segurança criptografia hash

Pergunta:


Estou pesquisando formas de criar um sistema de login com uma criptografia segura e que não pese para o servidor.
Tomando como exemplo essa resposta estou a pesquisar uma forma de fazer a criptografia do lado do cliente, enviando assim a senha já criptografada para o php, reduzindo assim a nescessidade de processamento. Minha intenção é usar 'cost'=> 12 ou maior para o BCrypt, só vai depender do desempenho no client-side em maquinas populares.

Porém estou aberto a outras possibilidades de criptgrafia que sejam possiveis do lado do cliente.

Como foi citado pelo @Bacco em sua resposta:

“Como o hashing é propositalmente “caro”, faria sentido numa arquitetura cliente-servidor usar a CPU do cliente. Afinal, quando 100 clientes conectam a um servidor só, coletivamente eles tem muito mais poder de processamento.”

Autor da pergunta RodrigoBorth

Comunidade

Você precisa de algo como o Secure Remote Password protocol (SRP). Como apontado por Earendul e André Ribeiro, simplesmente mover o hash do servidor pro cliente anula todos os benefícios de segurança – pois um atacante que obtiver uma cópia do BD pode simplesmente usar o hash armazenado para fazer login imediatamente como qualquer usuário (já que a credencial de acesso passa a ser o hash, e não a senha original). É preciso um protocolo cujas características de segurança se mantenham mesmo com o hash feito no lado cliente.

E esse protocolo é o SRP. O protocolo original possui uma fraqueza (fraqueza essa que se mantém presente na implementação padrão do SRP via SSL/TLS), que é o uso de um simples SHA-256 como função de hash, em vez de uma função lenta como o BCrypt. De modo que você teria de implementar você mesmo na camada de aplicação e/ou obter uma implementação segura da mesma forma.

Ele é um pouquinho mais complicado que a maioria dos protocolos – pois envolve diversas mensagens indo e vindo entre o cliente e o servidor. Há também alguns parâmetros a serem estabelecidos, consulte a referência indicada para mais detalhes.

Para registrar um novo usuário:

  1. O cliente, cuja senha é p, escolhe um sal aleatório s e calcula o hash x = H(s, p); calcula também v = g^x, onde g é um parâmetro comum entre o servidor e os clientes.
  2. O servidor armazena v e s associado ao username desse cliente. x é descartado – de modo que mesmo se um atacante copiar o BD, ele não vai saber o resultado do hash.

Para um usuário existente fazer login:

  1. O cliente escolhe uma chave secreta a aleatória (e efêmera) e envia A = g^a ao servidor (mais o seu username);
  2. O servidor também escolhe um chave efêmera b, calcula B = kv + g^b (k é um parâmetro calculado independentemente por ambas as partes) e envia B e s pro cliente;
  3. Ambos calculam u = H(A, B);
  4. O cliente calcula Sc = (B-kg^x)^(a + ux) e K = H(Sc), fazendo uso de novo da sua senha p para obter x;
  5. O servidor calcula Ss = (Av^u)^b e K = H(Ss).

Agora tanto o cliente quanto o servidor possuem uma chave secreta e compartilhada (e efêmera), derivada em parte da senha do usuário. Resta somente cada um deles provar ao outro que chegaram ao mesmo resultado:

  1. O cliente envia ao servidor M1 = H(H(N) xor H(g) | H(I) | s | A | B | K), e o servidor verifica usando seu valor de K. | significa a concatenação de strings. N é outro parâmetro comum entre cliente e servidor, e I é simplesmente o username.
  2. O servidor envia ao cliente M2 = H(A | M1 | K), e o cliente verifica usando seu valor de K.

Fonte: Wikipedia

Esse é o protocolo original, que usa SHA-256 como hash. Como você pode observar, ele é usado diversas vezes durante o protocolo, de modo que é inviável substituí-lo por um hash lento em todos os seus usos – quando tudo o que você quer é proteger a senha. Uma opção preferível – como apontada por Tom Leek no security.SE – é manter o protocolo idêntico, só aplicar p = BCrypt(s, p) na senha antes de usá-la (pode-se usar o mesmo sal s, mas se viável é preferível usar um sal s2 – se sua implementação der suporte é claro). Assim você ganha a proteção do hash sem aumentar a carga no servidor.

Um atacante que ganhe acesso ao BD somente verá s e v = g^x, de modo que ele teria que computar x para poder fazer login no servidor (“simulando” o protocolo offline). E como para chegar em x ele teria de refazer o hash lento, a proteção do mesmo está assegurada.

Você pode utilizar o bcryptjs.

var bcrypt = dcodeIO.bcrypt;
var hash = bcrypt.hashSync('password', 12);

Vale a pena realizar alguns testes de performance para verificar se um cost de 12 não ficará muito lento para máquinas mais simples. A própria wiki do bcryptjs possui um benchmark, mas vale lembrar que o teste foi realizado em um Intel Core i7-2600K.

Codificando a senha no lado cliente e enviando pro servidor não irá lhe dar muito mais segurança. Se um intruso capturar essa senha codificada ele poderá usar exatamente essa senha futuramente, e o servidor não terá como saber se foi você ou não que está enviando essa senha codificada. Esse ataque é também conhecido como Replay Attack. Claro que já é melhor que enviar a senha pura.

Para resolver esse problema pode usar o Nonce. Basicamente ele funciona da seguinte maneira:

  • Cliente requisita um nonce (algo randômico, um lixo qualquer) do servidor. O servidor envia em texto puro;
  • O cliente também gera um nonce qualquer, concatena com a senha e o nonce do servidor, gera um hash disso tudo e envia para o servidor, junto com o seu nonce em texto puro.
  • O servidor conhece seu nonce e o do cliente, e assim consegue descriptografar a senha.

De uma maneira similiar pode-se usar Timestamp para conseguir o mesmo objetivo dos nonces.

Como você mencionou desempenho o método acima, o servidor poderia apenas verificar o nonce do cliente e verificar se já foi usado antes ou não, mas para isso ele teria que guardar os nonces já usados em uma tabela. Mas evitaria do server ter que descriptografar a senha novamente.

Obs.: Com o método acima apenas para passar a ideia de como fazer o hash do lado cliente, porém ele requer que o servidor já tenha acesso às senhas.

Nessa resposta o autor fala sobre a performance do lado cliente. Resumidamente ele diz que se, por exemplo, fosse feito o hash em javascript, talvez fosse tão lento quanto o servidor, pois javascript não tem suporte a esse tipo de processamento, tornando-a uma linguagem lenta para este objetivo.

Referências:

Fonte

Related Posts:

Qual a diferença entre AppCompatActivity e Activity? – android android-activity
Pergunta: Qual a diferença da AppCompatActivity para Activity ? A partir de qual versão a AppCompatActivity foi adicionada ao Android? Autor da pergunta Luhhh A diferença reside ...
Como abreviar palavras em PHP? – php string
Pergunta: Possuo informações comuns como nome de pessoas e endereços, e preciso que elas contenham no máximo 30 caracteres sem cortar palavras. Exemplo: 'Avenida Natalino João Brescansin' ...
Qual é a finalidade de um parêntese vazio numa declaração Lambda? – c# expressões-lambda característica-linguagem
Pergunta: Criei um exemplo de uma declaração Lambda sem argumentos, entretanto, estou com duvidas referente a omissão do parêntese vazio () na declaração. Veja o exemplo: class ...
Boas práticas para URI em API RESTful – api rest restful
Pergunta: Estou com dúvida em relação às URIs de alguns recursos da api que estou desenvolvendo. Tenho os recursos projetos e atividades com relação 1-N, ...
Dúvidas sobre a integração do MySQL com Java – java mysql netbeans
Pergunta: Estou criando um sistema no NetBeans, utilizando a linguagem Java e o banco de dados MySQL. Escrevi o seguinte código para realizar a conexão ...
Qual é a finalidade da pasta Model do framework Inphinit? – php inphinit
Pergunta: No Inphinit micro-framework existe a pasta Model que fica dentro da pasta application, e nela é onde ficam as classes, mas eu estou muito ...
Uso do ‘@’ em variáveis – javascript typescript coffeescript
Pergunta: Vejo em algumas linguagens que compilam para javascript, como TypeScript e CoffeeScript, o uso do @ em variáveis, como também, casos em que o ...
Qual tamanho máximo um arquivo JSON pode ter? – json arquivo
Pergunta: Vou dar um exemplo para conseguir explicar minha duvida: Preciso recuperar informação de imagens vindas de uma API, esse banco de imagens me retorna JSON's ...
O que é Teste de Regressão? – terminologia engenharia-de-software testes
Pergunta: Na matéria de Teste de Software o professor abordou um termo chamado Teste de Regressão, isto dentro da disciplina de teste de software. Sendo ...
O que é um construtor da linguagem? – php característica-linguagem
Pergunta: Em PHP, já li e ouvi várias vezes a respeito dos Construtores da Linguagem. Os casos que sempre ouvi falar deles foi em casos ...
Função intrínseca para converter numérico para string – cobol
Pergunta: Estou a tentar saber se existe alguma função intrínseca do COBOL para converter um data numérico para string sem precisar usar a cláusula REDEFINES: ( ...
Porque usar implements? – java android
Pergunta: Qual a diferença entre usar btn.setOnClickListener(new OnClickListener() { e public class MainActivity extends Activity implements OnClickListener{ Estive fazendo um curso de Android e meu professor falou que ...
O que é XHTML e quando deve ser usado? – html xml xhtml
Pergunta: O que eu sei é que o XHTML precisa ser XML válido. Isso implica, por exemplo, que todas as tags precisam ser fechadas. Por ...
Uma placa aceleradora de vídeo pode melhorar o desempenho não-gráfico? [fechada] – desempenho
Pergunta: Para desenvolver em Ruby on Rails, eu utilizo aqui uma máquina virtual do VirtualBox com Ubuntu Server 14.04 sem interface gráfica instalada. Recentemente descobri uma ...
Concat() VS Union() – c# .net
Pergunta: Qual a diferença entre Concat() e Union() ? Quando usar Concat() e quando usar Union() ? Somente pode ser usado em list ? ...

Deixe uma resposta

O seu endereço de email não será publicado. Campos obrigatórios marcados com *