Como posso proteger o nome de usuário e a senha do MySQL contra descompilação?


87

Os .classarquivos Java podem ser descompilados com bastante facilidade. Como posso proteger meu banco de dados se tiver que usar os dados de login no código?


Espero que você não se importe por eu reformular sua pergunta. Eu removi "nome de usuário" e "senha" e adicionei "engenharia reversa" e "descompilação". Acho que são mais descritivos do que os originais. Excelente pergunta sobre os fundamentos, a propósito!
William Brendel

6
Observe que o fato de você estar usando Java não é realmente relevante aqui. Ter senhas embutidas em código em qualquer idioma é problemático praticamente da mesma maneira ("strings thebinary" também mostra constantes de string em programas C).
Joachim Sauer

@saua: Verdade, mas talvez alguém poste algum código de amostra sobre como separar nomes de usuário e senhas em Java. Posso até fazer isso sozinho, se tiver tempo.
William Brendel

1
Percebi que muitas das respostas pensam que você está tentando ocultar o nome de usuário / senhas de um usuário não autorizado enquanto o usuário que está executando o aplicativo está ok. Eu acredito que você deseja ocultar a senha de TODOS . Por favor, esclareça isso na pergunta.
pek

1
Por exemplo, digamos que as credenciais sejam usadas para se conectar a um servidor no qual ninguém, exceto o aplicativo, possa fazer login.
pek

Respostas:


123

Nunca codifique senhas em seu código. Isso foi mencionado recentemente no Top 25 dos erros de programação mais perigosos :

Codificar uma conta secreta e uma senha em seu software é extremamente conveniente - para engenheiros reversos qualificados. Se a senha for a mesma em todo o software, todos os clientes ficarão vulneráveis ​​quando essa senha for inevitavelmente conhecida. E porque é codificado, é uma grande dor de se consertar.

Você deve armazenar informações de configuração, incluindo senhas, em um arquivo separado que o aplicativo lê ao iniciar. Essa é a única maneira real de evitar que a senha vaze como resultado da descompilação (nunca compile-a no binário para começar).

Para obter mais informações sobre esse erro comum, você pode ler o artigo CWE-259 . O artigo contém uma definição mais completa, exemplos e muitas outras informações sobre o problema.

Em Java, uma das maneiras mais fáceis de fazer isso é usar a classe Preferences. Ele é projetado para armazenar todos os tipos de configurações do programa, algumas das quais podem incluir um nome de usuário e uma senha.

import java.util.prefs.Preferences;

public class DemoApplication {
  Preferences preferences = 
      Preferences.userNodeForPackage(DemoApplication.class);

  public void setCredentials(String username, String password) {
    preferences.put("db_username", username);
    preferences.put("db_password", password);
  }

  public String getUsername() {
    return preferences.get("db_username", null);
  }

  public String getPassword() {
    return preferences.get("db_password", null);
  }

  // your code here
}

No código acima, você pode chamar o setCredentialsmétodo após mostrar uma caixa de diálogo solicitando o nome de usuário e a senha. Quando você precisa se conectar ao banco de dados, você pode apenas usar os métodos getUsernamee getPasswordpara recuperar os valores armazenados. As credenciais de login não serão codificadas em seus binários, portanto, a descompilação não representará um risco de segurança.

Observação importante: os arquivos de preferência são apenas arquivos XML de texto simples. Certifique-se de tomar as medidas adequadas para evitar que usuários não autorizados vejam os arquivos brutos (permissões do UNIX, permissões do Windows etc.). No Linux, pelo menos, isso não é um problema, porque a chamada Preferences.userNodeForPackagecriará o arquivo XML no diretório inicial do usuário atual, que de qualquer forma não pode ser lido por outros usuários. No Windows, a situação pode ser diferente.

Notas mais importantes: Houve muita discussão nos comentários desta resposta e em outros sobre qual é a arquitetura correta para esta situação. A pergunta original não menciona realmente o contexto no qual o aplicativo está sendo usado, então falarei sobre as duas situações nas quais consigo pensar. O primeiro é o caso em que a pessoa que usa o programa já conhece (e está autorizada a saber) as credenciais do banco de dados. O segundo é o caso em que você, o desenvolvedor, está tentando manter as credenciais do banco de dados em segredo da pessoa que usa o programa.

Primeiro caso: o usuário está autorizado a saber as credenciais de login do banco de dados

Nesse caso, a solução que mencionei acima funcionará. A Preferenceclasse Java armazenará o nome de usuário e a senha em texto simples, mas o arquivo de preferências só poderá ser lido pelo usuário autorizado. O usuário pode simplesmente abrir o arquivo XML de preferências e ler as credenciais de login, mas isso não é um risco de segurança porque o usuário já sabia as credenciais.

Segundo caso: tentar ocultar as credenciais de login do usuário

Este é o caso mais complicado: o usuário não deve saber as credenciais de login, mas ainda precisa acessar o banco de dados. Nesse caso, o usuário que executa o aplicativo tem acesso direto ao banco de dados, o que significa que o programa precisa saber as credenciais de login com antecedência. A solução que mencionei acima não é apropriada para este caso. Você pode armazenar as credenciais de login do banco de dados em um arquivo de preferências, mas o usuário poderá ler esse arquivo, pois será o proprietário. Na verdade, não existe uma boa maneira de usar esse gabinete de maneira segura.

Caso correto: usando uma arquitetura multicamadas

A maneira correta de fazer isso é ter uma camada intermediária, entre o servidor de banco de dados e o aplicativo cliente, que autentica usuários individuais e permite que um conjunto limitado de operações seja executado. Cada usuário teria suas próprias credenciais de login, mas não para o servidor de banco de dados. As credenciais permitiriam acesso à camada intermediária (a camada de lógica de negócios) e seriam diferentes para cada usuário.

Cada usuário teria seu próprio nome de usuário e senha, que poderiam ser armazenados localmente em um arquivo de preferências sem qualquer risco de segurança. Isso é chamado de arquitetura de três camadas (as camadas sendo seu servidor de banco de dados, servidor de lógica de negócios e aplicativo cliente). É mais complexo, mas realmente é a maneira mais segura de fazer esse tipo de coisa.

A ordem básica das operações é:

  1. O cliente se autentica com a camada de lógica de negócios usando o nome de usuário / senha pessoal do usuário. O nome de usuário e a senha são conhecidos pelo usuário e não estão relacionados às credenciais de login do banco de dados de forma alguma.
  2. Se a autenticação for bem-sucedida, o cliente fará uma solicitação à camada de lógica de negócios solicitando algumas informações do banco de dados. Por exemplo, um estoque de produtos. Observe que a solicitação do cliente não é uma consulta SQL; é uma chamada de procedimento remoto, como getInventoryList.
  3. A camada de lógica de negócios se conecta ao banco de dados e recupera as informações solicitadas. A camada de lógica de negócios é responsável por formar uma consulta SQL segura com base na solicitação do usuário. Todos os parâmetros da consulta SQL devem ser limpos para evitar ataques de injeção de SQL.
  4. A camada de lógica de negócios envia a lista de inventário de volta ao aplicativo cliente.
  5. O cliente exibe a lista de inventário para o usuário.

Observe que, em todo o processo, o aplicativo cliente nunca se conecta diretamente ao banco de dados . A camada de lógica de negócios recebe uma solicitação de um usuário autenticado, processa a solicitação do cliente para uma lista de inventário e só então executa uma consulta SQL.


4
Como exatamente isso impede que alguém obtenha o nome de usuário / senha? Você não pode simplesmente ler do arquivo então?
Joe Phillips

Como eu disse em minha resposta, se as permissões do seu arquivo estiverem definidas corretamente, apenas o usuário que está executando o programa terá acesso de leitura a esse arquivo de preferências. Em ambientes UNIX, isso é feito automaticamente. O Windows pode exigir etapas adicionais (não tenho certeza, pois não uso muito o Windows).
William Brendel

Acho que a ideia é que o usuário que está executando o aplicativo não é aquele de quem você está tentando impedi-lo. Se for esse o caso, você terá que criptografá-lo.
Michael Haren

Sim, Michael está correto. Basicamente, a ideia é que você já saiba o nome de usuário / senha, então não há necessidade de escondê-lo de você mesmo. Ele ficará oculto para outros usuários, no entanto, por meio de permissões de arquivo.
William Brendel

7
Se você estiver implantando (exemplo) um aplicativo de edição de banco de dados para um usuário e não quiser que ele saiba o nome de usuário e a senha do banco de dados, então você arquitetou a solução errada e o software cliente deve estar se comunicando com um servidor (via por exemplo, um serviço da web) que faz as coisas do banco de dados.
JeeBee

15

Coloque a senha em um arquivo que o aplicativo irá ler. NUNCA incorpore senhas em um arquivo de origem. Período.

Ruby tem um módulo pouco conhecido chamado DBI :: DBRC para tal uso. Não tenho dúvidas de que o Java tem um equivalente. De qualquer forma, não é difícil escrever um.


7
Embora isso torne um pouco mais fácil alterar as senhas posteriormente, não resolve o problema básico de segurança.
Brian Knoblauch

Sim. Veja também a resposta de William Brendel.
Keltia de

1
O método que Keltia e eu apontamos é a forma aceita de lidar com esse problema. Separar suas credenciais de login do código compilado é uma das práticas de segurança de software mais básicas. Colocar as credenciais de login em um arquivo separado é uma maneira eficaz de fazer isso.
William Brendel

Além disso, o fato de suas informações de configuração estarem em um arquivo de texto simples deve ser cancelado pelas restrições do sistema operacional. Por exemplo, no UNIX, o arquivo de texto simples deve pertencer ao usuário que está executando o programa e ter permissões 0600, para que apenas o proprietário possa lê-lo.
William Brendel

4
OK, então o arquivo só pode ser lido pelo usuário que está executando o programa. Ótimo. Isso não resolve nada. :-) Eu, o usuário de quem estamos tentando manter a senha em segredo, posso lê-la tão facilmente quanto o app ...
Brian Knoblauch

3

Você está escrevendo um aplicativo da web? Em caso afirmativo, use o JNDI para configurá-lo externamente ao aplicativo. Uma visão geral está disponível aqui :

O JNDI fornece uma maneira uniforme para um aplicativo localizar e acessar serviços remotos na rede. O serviço remoto pode ser qualquer serviço corporativo, incluindo um serviço de mensagens ou um serviço específico de aplicativo, mas, é claro, um aplicativo JDBC está interessado principalmente em um serviço de banco de dados. Depois que um objeto DataSource é criado e registrado com um serviço de nomenclatura JNDI, um aplicativo pode usar a API JNDI para acessar esse objeto DataSource, que pode então ser usado para se conectar à fonte de dados que representa.


O link fornecido está incorreto
James Oravec,

2

Não importa o que você faça, as informações confidenciais serão armazenadas em algum arquivo em algum lugar. Seu objetivo é torná-lo o mais difícil possível. O quanto disso você pode conseguir depende do seu projeto, necessidades e espessura da carteira da sua empresa.

A melhor maneira é não armazenar nenhuma senha em qualquer lugar. Isso é conseguido usando funções hash para gerar e armazenar hashes de senha:

hash("hello") = 2cf24dba5fb0a30e26e83b2ac5b9e29e1b161e5c1fa7425e73043362938b9824
hash("hbllo") = 58756879c05c68dfac9866712fad6a93f8146f337a69afe7dd238f3364946366

Algoritmos de hash são funções unilaterais. Eles transformam qualquer quantidade de dados em uma "impressão digital" de comprimento fixo que não pode ser revertida. Eles também têm a propriedade de que, se a entrada mudar mesmo que seja um pouquinho, o hash resultante será completamente diferente (veja o exemplo acima). Isso é ótimo para proteger as senhas, porque queremos armazenar as senhas de uma forma que as proteja mesmo que o próprio arquivo de senhas esteja comprometido, mas, ao mesmo tempo, precisamos verificar se a senha de um usuário está correta.

Observação não relacionada: Nos velhos tempos da Internet, quando você clica no link 'esqueci minha senha', os sites enviam a você a sua senha em texto simples. Eles provavelmente os estavam armazenando em um banco de dados em algum lugar. Quando os hackers obtiveram acesso ao seu banco de dados, eles obteriam acesso a todas as senhas. Como muitos usuários usariam a mesma senha em vários sites, esse era um grande problema de segurança. Felizmente, hoje em dia, essa não é a prática comum.

Agora vem a pergunta: qual é a melhor maneira de armazenar senhas? Eu consideraria esta solução (serviço de autenticação e gerenciamento de usuários Stormpath) muito ideal:

  1. Seu usuário insere as credenciais, e isso é validado com o hash de senha
  2. Hashes de senha são gerados e armazenados, não senhas
  3. Hashes são executados várias vezes
  4. Hashes são gerados usando um sal gerado aleatoriamente
  5. Hashes são criptografados com uma chave privada
  6. A chave privada é armazenada em um local fisicamente diferente dos hashes
  7. As chaves privadas são atualizadas com base no tempo
  8. Hashes criptografados são divididos em blocos
  9. Esses pedaços são armazenados em locais fisicamente separados

Obviamente você não é o Google ou um banco, então esta é uma solução exagerada para você. Mas então vem a pergunta: quanta segurança seu projeto requer, quanto tempo e dinheiro você tem?

Para muitos aplicativos, embora não seja recomendado, armazenar uma senha embutida no código pode ser uma solução boa o suficiente. No entanto, adicionando facilmente algumas etapas extras de segurança da lista acima, você pode tornar seu aplicativo muito mais seguro.

Por exemplo, vamos supor que a etapa 1 não seja uma solução aceitável para o seu projeto. Você não quer que os usuários insiram a senha todas as vezes, ou nem mesmo quer / precisa que os usuários saibam a senha. Ainda assim, você tem informações confidenciais em algum lugar e deseja protegê-las. Você tem um aplicativo simples, não há servidor para armazenar seus arquivos ou isso é muito incômodo para o seu projeto. Seu aplicativo é executado em ambientes onde não é possível ter arquivos armazenados com segurança. Este é um dos piores casos, mas ainda com algumas medidas de segurança adicionais, você pode ter uma solução muito mais segura. Por exemplo, você pode armazenar as informações confidenciais em um arquivo e pode criptografar o arquivo. Você pode ter a chave privada de criptografia embutida no código. Você pode ofuscar o código, tornando um pouco mais difícil para alguém quebrá-lo.este link . (Quero avisá-lo mais uma vez que isso não é 100% seguro. Um hacker inteligente com o conhecimento e as ferramentas corretas pode hackear isso. Mas com base em seus requisitos e necessidades, essa pode ser uma solução boa o suficiente para você).



0

MD5 é um algoritmo de hash, não um algoritmo de criptografia, em suma, você não pode obter de volta o que você hash, você só pode comparar. Idealmente, deve ser usado ao armazenar as informações de autenticação do usuário e não o nome de usuário e a senha do banco de dados. O nome de usuário do banco de dados e o pwd devem ser criptografados e mantidos em um arquivo de configuração, para fazer o mínimo.


Já ouvi falar de pessoas que geram todas as combinações possíveis de strings e armazenam seus hashes MD5 correspondentes. Assim, quando encontram o hash MD5 de alguém, eles apenas encontram o hash que armazenaram e obtêm a string correspondente.
Nav
Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.