Proteja as Strings de Conexão a Banco de Dados e Outras Configurações Sigilosas em seu Código

Neste artigo, discutirei os fundamentos da proteção de dados e farei comparações entre diversas técnicas, a fim de ajudá-lo a gerenciar melhor os dados confidenciais.

Clique aqui para ler este artigo em pdf

Clique aqui para ler todos os artigos desta edição

 

Proteja as Strings de Conexão a Banco de Dados e Outras Configurações Sigilosas em seu Código

por Alex Davis

 

Proteção de strings de conexão a banco de dados, senhas e outras configurações sigilosas de aplicativos são questões freqüentemente discutidas em grupos de discussão e fóruns on-line. Embora a maioria dos profissionais de segurança concorde que é impossível manter sigilo com softwares, existem certas técnicas conhecidas que podem oferecer proteção suficiente para a maioria dos tipos de aplicativos. Todos os métodos de proteção de dados existentes são baseados em três técnicas básicas: ocultação, controle de acesso e criptografia. Essas técnicas podem ser usadas individualmente ou de forma combinada. Se você resolver proteger as informações confidenciais do aplicativo escolhendo uma tecnologia existente ou criando seu próprio mecanismo de proteção de dados, suas opções estarão limitadas a esses três métodos.

  Neste artigo, discutirei os fundamentos da proteção de dados e farei comparações entre diversas técnicas, a fim de ajudá-lo a gerenciar melhor os dados confidenciais. Identificarei os cenários em que essas técnicas são apropriadas e quando elas deverão ser evitadas. Farei referências aos exemplos de códigos, ferramentas e APIs que o ajudarão a implementar um mecanismo de proteção de dados.

 

Ocultação de Dados

A ocultação de dados às vezes é citada como segurança por meio de obscuridade.[r1]  Se você confiar nesta técnica, assuma que só você sabe onde as informações confidenciais estão armazenadas e espere que ninguém mais descubra. A advertência é que o aplicativo também precisa saber como acessar os dados. Assim manter as informações sigilosas em segurança, vai depender muito da capacidade que o aplicativo possui de proteger esse conhecimento.

Os lugares mais comuns para ocultar configurações sigilosas dos aplicativos são: código-fonte, arquivos de configuração e registro do Windows®. Dados também podem ser armazenados em locais inusitados como na metabase do IIS, arquivos UDL (Universal Data Link), arquivos personalizados e em qualquer outro lugar.

  Salvo algumas exceções, ocultar dados pura e simplesmente oferece pouca segurança (se alguma). Na maioria dos casos, o esforço exigido por um hacker para descobrir suas informações sigilosas é mínimo. Por exemplo, um intruso pode ser capaz de descobrir a localização dos dados monitorando as modificações no sistema realizadas pelo aplicativo em execução (veja a Figura 1). Esta tarefa pode ser realizada com a ajuda de utilitários como regmon, filemon e diskmon em SysInternals (http://www.sysinternals.com/).

 

Figura 1 - Regmon

 

A facilidade de descompilação de assemblies .NET representa uma ameaça ainda maior. Usando programas como Anakrino (http://www.saurik.com/net/exemplar/) ou Salamander (http://www.remotesoft.com/), o código-fonte do aplicativo pode ser obtido por engenharia reversa, expondo os dados confidenciais ou a lógica do aplicativo (observe a Figura 2).

 

Figura 2 - Descompilador

 

Restringindo o Acesso a Dados

Se o acesso aos dados é restrito, este local não precisa ser mantido em sigilo. Ao contrário, essa técnica conta com recursos tecnológicos do próprio sistema operacional que impedem pessoas não autorizadas de acessar os dados. Normalmente, as restrições de acesso são baseadas na identidade do visitante ou no conhecimento de uma informação sigilosa em comum (veja a Figura 3). As listas de controle de acesso (ACLs – Access Control Lists), a API de proteção de dados (DPAPI – Microsoft® Data Protection API) e o armazenamento isolado utilizam essa técnica para proteger dados.

 

 

 

Figura 3 – Controle de acesso 

 

Apesar de suas limitações, o acesso restrito é uma das medidas de segurança mais eficientes, quando aplicado corretamente. Por exemplo, o controle de acesso baseado na identidade do usuário não funciona bem nos aplicativos que executam como usuários anônimos ou em contextos de diferentes usuários. Um outro problema com o controle de acesso baseado em identidade é o fato de que ele provavelmente não impeça que diferentes aplicativos que rodam sob a mesma identidade tenham acesso aos dados um do outro. A Tabela 1 lista os problemas mais comuns associados aos controles de acesso baseados em identidade.

Tabela 1 – Problemas de Controle de Acesso Baseado em Identidade

 

Tipo de Identidade

Problemas

Usuário

Não funcionará em aplicativos que rodam como usuário anônimo, como o ASP.NET. Não funcionará em aplicativos que rodam sob múltiplas identidades. Requer que os dados sejam definidos e recuperados pelo mesmo usuário. Permite que aplicativos sem qualquer ligação rodem sob a mesma identidade de usuário para acessar os dados de cada um.

Máquina

Permite que todos aplicativos rodem na mesma máquina para acessar os dados de cada um. Pode perder os dados se a máquina for formatada.[r2] 

Assembly

Requer que os dados sejam definidos e recuperados pelo mesmo aplicativo. Suportado apenas pelo código de acesso de segurança e pelo armazenamento isolado.

 

Por outro lado, quando o controle de acesso é baseado no conhecimento de uma informação sigilosa, protegê-la torna-se um problema. As dificuldades adicionais associadas ao acesso restrito dependem de uma determinada implementação. Como por exemplo, a dificuldade de gerenciar ACLs em um grande ambiente empresarial e bem como a possibilidade do armazenamento protegido exigir privilégios administrativos por parte do visitante (violando assim a regra de oferecer a menor quantidade de privilégios).

 

Criptografando Dados

Quando a criptografia é usada nem a ocultação ou os controles de acesso a dados são a principal finalidade. Visto que a informação é criptografada, saber onde encontrá-la e ser capaz de acessá-la não a torna útil para uma pessoa não autorizada – a menos que o intruso encontre a chave de descriptografia.

Embora seja uma técnica de proteção de dados testada e aprovada, a criptografia não oferece uma solução perfeita. Ela simplesmente transforma o problema da proteção de dados em um problema de proteção de chaves criptográficas. Como em quaisquer outros tipos de dados confidenciais, as chaves criptográficas são protegidas utilizando-se as mesmas técnicas: ocultação e controle de acesso.

  Ao escolher e criar mecanismos de proteção de dados, você deve pesar as vulnerabilidades contra possíveis ameaças. Essa avaliação pode ser mais fácil, se você pensar em sua solução em função das técnicas básicas de proteção de dados discutidas anteriormente. Por exemplo, se você armazenar informações de conexão ao banco de dados que incluem as credenciais SQL de um usuário em um arquivo UDL (veja a Figura 4), os seus dados de segurança dependerão basicamente das permissões de arquivo (ACLs), que são formas de acesso restrito. (Até certo ponto, eles dependerão da ocultação de dados que é uma medida de segurança bastante fraca [neste caso em particular], pois ela não exige grandes esforços para localizar um arquivo UDL). Embora as ACLs desempenhem uma função imprescindível na proteção de dados, é importante entender que se um hacker conseguir obter acesso à leitura do arquivo UDL, suas credenciais SQL serão reveladas.

 

Figura 4 – Credenciais do arquivo UDL

 

Você deve evitar soluções que utilizam apenas uma técnica de proteção, a menos que a possibilidade de ameaça seja mínima ou que seus dados não sejam tão importantes. Normalmente, quanto mais técnicas a solução emprega, mais segura ela é.

 

Considerando suas Opções

Como dizem os especialistas, o melhor método de proteger informações sigilosas é não guardá-las. Por exemplo, se você deseja proteger as strings de conexão ao banco de dados que contêm as credenciais SQL, considere não utilizar a autenticação SQL. Se o seu aplicativo utiliza a autenticação Windows para se conectar ao servidor de banco de dados em vez da autenticação SQL, você não precisará se preocupar com a proteção das credenciais SQL.

Infelizmente, nem sempre se consegue evitar o armazenamento de informações confidenciais. Se você realmente precisa armazenar dados confidenciais, comece a procurar a solução certa analisando seu aplicativo e as exigências de segurança. As perguntas a seguir poderão ajudá-lo a definir qual opção será melhor para você:

 

·Quão importantes são as informações que você está protegendo? Que tipo de prejuízo você espera sofrer se a segurança de seus dados for comprometida?

·De que tipo de usuário você está protegendo seus dados? Você quer proteger os dados de hackers oportunistas, como funcionários que adorariam ter acesso às informações restritas, mas que não atacariam o sistema de forma intencional? Ou quer proteger os dados de hackers que estejam dispostos a despender tempo, esforço e dinheiro para comprometer o sistema? Até onde você acha que um hacker iria para obter seus dados?

·O seu aplicativo realmente precisa conhecer os textos não-criptografados dos dados confidenciais?

·Você está protegendo as strings de conexão ao banco de dados ou deseja também gerenciar outros tipos de dados?

·Quantas identidades o seu aplicativo utiliza para ter acesso aos dados? Ele roda sob a identidade de um único usuário ou de múltiplos usuários?

·Quais versões do sistema operacional oferecem suporte ao aplicativo?

·O que é mais importante: desempenho do aplicativo ou segurança? Você estaria disposto a sacrificar o desempenho para conseguir um aplicativo mais seguro?

·Qual é o mecanismo responsável pela definição de dados? Ela é feita manualmente, usando um editor de textos, por exemplo, ou de forma programática? Se os dados são criados de forma programática, as configurações do aplicativo estão sendo definidas a partir do mesmo aplicativo que as utiliza ou de um outro?

·Quantos aplicativos utilizam proteção de dados em sua empresa? Se existe mais de um aplicativo, todos eles possuem os mesmos requisitos? Você deseja reutilizar uma única solução que satisfaça as exigências de todos os aplicativos ou ter um mecanismo diferente de proteção de dados para cada um de seus aplicativos?

·Depois que o seu aplicativo for distribuído, haverá dificuldade em oferecer suporte ao pessoal para administrar a segurança? A equipe que oferece suporte ao seu aplicativo aceitará sua solução?

 

Dependendo das respostas, você escolherá uma das abordagens em detrimento de outras. Ainda neste artigo, analisarei diversas tecnologias de proteção de dados que podem ser usadas pelos tipos mais comuns de aplicativos, mas primeiramente farei um resumo de certas opções que você deve evitar sob qualquer circunstância.

Todos os métodos de armazenamento de dados confidenciais em texto simples (plain text) não são considerados seguros, porque eles podem expor os dados a qualquer pessoa que consegue obter acesso à leitura da fonte de dados – uma tarefa que pode ser mais fácil do que você imagina. Evite manter os dados não-criptografados no registro do Windows, arquivos de configuração, catálogo do COM+, armazenamento isolado, arquivos personalizados, metabase IIS e arquivos UDL.

Você nunca obterá a segurança total, mas uma segurança “suficiente” é definitivamente possível. Alguns aplicativos possuem exigências de segurança que requerem soluções complexas e nada ortodoxas, especialmente nas áreas de operações bancárias, dados militares sigilosos, registros policiais e pesquisas científicas. No entanto, a maioria envolve as três etapas a seguir: criptografia de dados, restrição de acesso aos dados criptografados usando ACLs e ocultação e restrição de acesso às chaves de criptografia.

Ao implementar a proteção de dados, considere outros aspectos de segurança como a adulteração de dados (data tampering)  - que não afeta o sigilo. E já que não afeta, não discutirei sobre esse aspecto neste artigo. Ao contrário, abordarei as questões mais práticas sobre onde armazenar seus dados criptografados, que tipo de criptografia e algoritmo de criptografia usar e como proteger suas chaves de criptografia.

 

Armazenando Dados Criptografados

Armazene sempre os dados criptografados nos arquivos de configuração do aplicativo ou no registro do Windows, a menos que vocês tenham motivos convincentes para não fazê-lo. As principais vantagens de ambas as opções são a capacidade de proteger os dados usando ACLs e a facilidade do acesso programático. É provável que uma dessas vantagens não seja encontrada em qualquer outro tipo de armazenamento de dados e, muito embora ele possa acrescentar um outro benefício, isso não justifica o trabalho extra de implementação.

  Apesar de alguns desenvolvedores insistirem que os aplicativos baseados no Microsoft .NET Framework não devem nunca usar o registro, isto não é uma regra[r3] . A principal deficiência do registro é que ele não se adapta bem ao cenário de distribuição do XCOPY, mas como muitos aplicativos atuais são distribuídos com ajuda de programas de configuração (setup) e requerem certas etapas de configuração para serem executados nos sistemas onde estão instaladas, essas compatibilidades com o XCOPY talvez não sejam necessárias. Uma outra desvantagem do registro é que ele é específico para a plataforma Windows e talvez não funcione em outros sistemas operacionais. Esta limitação só se tornará um grande problema quando outras plataformas começarem a oferecer suporte à Common Language Implementation (CLI).

Se você se preocupa com a distribuição XCOPY ou com a interoperabilidade entre plataformas diferentes do Windows, use os arquivos de configuração; caso contrário, selecione a opção que se adapta melhor às suas necessidades. Se você tiver de armazenar as configurações que serão compartilhadas por diversos aplicativos ou defini-las de forma programática, talvez ache mais fácil gerenciar o registro (registry). As configurações específicas do aplicativo (que não são modificadas de forma programática) podem ser armazenadas nos arquivos de configuração. Seja qual for o caso, lembre-se de aplicar as ACLs adequadas ao armazenamento de dados. Por exemplo, seria uma ótima idéia ceder os direitos de acesso para leitura de um arquivo ou chave de registro (registry key) que contêm os dados confidenciais à conta de usuário sob a qual os aplicativos rodam. Paralelamente, o acesso para gravação deve ser explicitamente cedido ao usuário – ou a um grupo de usuários (como os Administradores) – com permissão para modificar os dados. Para qualquer outra pessoa, todos os direitos de acesso devem ser negados.

 

Criptografia Unidirecional e Bidirecional

Existem dois métodos de codificação de dados: unidirecional (comumente chamada hashing) e bidirecional. Tecnicamente, hashing não é criptografia, mas como ambas as técnicas possuem a capacidade de lidar com proteção de dados de forma semelhante – ou seja, transformando dados de texto simples em texto cifrado – eu as tratarei como equivalente lógica.

Hashing é o processo de gerar um valor baseado em um texto para ser utilizado em criptografia e índices de bancos de dados.

Embora hashing sirva para outras finalidades, na área de proteção de dados ela oferece certas vantagens sobre a criptografia bidirecional. Primeiro, hashing é um pouco mais fácil de usar. Segundo, como hashing não usa chaves de criptografia, ela elimina o problema de gerenciamento de chaves. A principal desvantagem de utilizar a técnica hashing é que ela não oferece suporte à decodificação, mas em alguns casos este problema pode ser uma bênção oculta.

Autenticação baseada em senha é um caso típico onde hashing é mais adequado que a criptografia bidirecional. Se o seu aplicativo mantém senhas apenas com a finalidade de autenticação, não as codifique com uma chave simétrica ou pública. Em vez disso, armazene seus valores hashed. Quando um usuário se conecta, o aplicativo compara os hashes da senha, em vez de decodificar e comparar as senhas de texto simples. Utilize sempre valores salt com hashes para reduzir o risco de ataques de dicionários. Um valor salt é um dado aleatório anexo ao texto simples antes de ser verificado (hashed) e armazenado junto com o hash, assim ele poderá ser usado posteriormente quando outro valor de texto simples for comparado ao hash.

 

Algoritmos Hash

MD5 e SHA-1 são os algoritmos mais conhecidos. Hashes SHA-1 possuem extensão de 160 bits, enquanto os hashes MD5 possuem 128 bits. O algoritmo SHA-1 é um pouco mais lento, porém mais seguro que o MD5. Além do MD5 e SHA-1, o .NET Framework oferece suporte às versões de 256, 384 e 512 bits do algoritmo SHA, o qual deve ser ainda mais seguro, embora aparentemente mais lento.

O modo mais fácil de fazer hash é chamando o método HashPasswordForStoringInConfigFile da classe FormsAuthentication, como no exemplo a seguir:

 

using System.Web.Security;

•••

string base64HashValue =

FormsAuthentication.HashPasswordForStoringInConfigFile

("mypassword", "sha1");

 

Infelizmente, este método só oferece suporte aos algoritmos hashing MD5 e SHA-1. Portanto, se você quiser usar hashes SHA-256, SHA-384 ou SHA-512, precisará escrever mais algumas linhas de código.

 

Chaves de Criptografia

Se o seu aplicativo precisa saber os valores de texto dos dados confidenciais, você não pode usar operações de hash. Neste caso, use a criptografia bidirecional com chaves simétricas ou públicas-privadas. Caso tenha dúvidas sobre qual tipo de criptografia escolher, use chaves simétricas. A principal desvantagem da criptografia de chave pública é o desempenho lento que pode ser mil vezes pior que a criptografia de chave simétrica. A criptografia de chave pública também impõe certas limitações ao tamanho dos dados baseados em texto que podem ser codificados. Embora as chaves públicas de criptografia possam ser usadas na proteção de dados, quase tudo que você queira fazer utilizando-as pode ser realizado com ajuda de outras tecnologias. Como as chaves públicas são mais apropriadas para a troca segura de chaves e assinatura digital de dados, eu as omitirei nesta discussão e me concentrarei na criptografia de chave simétrica.

Ao escolher um algoritmo de criptografia, faz sentido selecionar o algoritmo mais seguro com a chave mais longa possível. Fora todos os algoritmos de chave simétrica com suporte do .NET Framework, o algoritmo de Rijndael aprovado pelo governo norte-americano (também conhecido como algoritmo padrão de criptografia avançada ou AES – Advanced Encryption Standard) é considerado o mais seguro. Esse algoritmo oferece suporte às chaves de 128, 192 e 256 bits.

O algoritmo de Rijndael possui uma outra vantagem sobre os algoritmos de chave simétrica que o .NET Framework oferece suporte. Enquanto outros algoritmos são oferecidos na forma de classes wrapper do .NET Framework sobre módulos CryptoAPI existentes, Rijndael (implementado como a classe RijndaelManaged) é escrito inteiramente em código gerenciado (managed code). Observe que alguns desenvolvedores consideram isso uma desvantagem e preferem usar uma implementação não-gerenciada do algoritmo de Rijndael, a fim de obter um melhor desempenho.

Infelizmente, apenas o Windows XP ou superior e os sistemas com o .NET Framework instalado oferecem suporte a essa implementação do algoritmo de Rijndael. Se o seu aplicativo precisa ser compatível com aplicativos não-gerenciados que rodam no Windows 2000 ou inferior, use o DES Triplo (uma versão melhor do algoritmo DES que é menos seguro). Esse algoritmo oferece suporte às chaves de 112 e 168 bits, embora se recomende a chave de 168 bits. Devido a inconsistências no modo como são tratados os bits de paridade da chave, as chaves de DES Triplo de 168 bits às vezes são chamadas de chaves de 192 bits. Se você quiser que um aplicativo gerenciado e não-gerenciado tenha permissão para codificar ou decodificar dados com a mesma chave de DES Triplo, saiba que a frase secreta (senha) da qual a chave é derivada deverá conter apenas caracteres ASCII imprimíveis; caso contrário, as chaves geradas não coincidirão (isso acontece devido a uma limitação do .NET na implementação do algoritmo de DES Triplo).

Os algoritmos DES, RC2 e RC4 originais geralmente são considerados menos seguros que os de Rijndael e de DES Triplo e devem ser evitados. Nunca use algoritmos de criptografia feitos em casa. Nem parta do princípio de que se você é o autor original do código ninguém será capaz de craqueá-lo.

 

 

 

Figura 5 – Esquema de Criptografia de Dados 

 

Protegendo as Chaves de Criptografia

Ao usar chaves simétricas para codificar ou decodificar dados de aplicativo, é importante poder gerar a mesma chave mais de uma vez. Isso afirma o problema da proteção de chaves ou da lógica de geração de chaves (não se confunda com algoritmos de criptografia). Embora diversas técnicas possam ser utilizadas para realizar esta tarefa, todas possuem deficiências. Vamos dar uma olhada nas opções disponíveis e nos cenários mais apropriados.

  Chaves simétricas persistentes podem ser geradas de duas maneiras: definindo você mesmo a chave ou deixando que o sistema operacional o faça por você. Ao escolher a primeira opção, você pode inserir a própria chave no código-fonte do aplicativo ou implementar uma lógica consistente para derivar a chave de certas características inalteráveis. Essas características normalmente englobam uma frase secreta da qual a chave é derivada, e também pode incluir outros valores como um vetor de inicialização. Inserindo a própria chave ou implementando a lógica de geração de chave, você está basicamente escondendo a chave no código-fonte do aplicativo. Esta opção lhe dá mais controle e flexibilidade, mas também irá expor seus dados a um risco maior se o código-fonte do seu aplicativo for obtido por engenharia reversa. Por outro lado, você pode armazenar a chave em um local persistente, como um arquivo ou no registry, e proteger este local com ACL. No entanto, como esta abordagem é propensa a erros de manutenção, eu não a recomendo.

Uma maneira mais segura (mas também mais restritiva) de gerar a chave é deixar que o sistema operacional o faça por você. Para isso, use alguns recursos do sistema operacional, como o Local Security Authority (LSA) ou o DPAPI.

 

Seja Cauteloso com o Local Security Authority

Durante o apogeu do Windows NT® 4.0, as funções da Política de LSA, LsaStorePrivateData e LsaRetrievePrivateData, ofereceram uma maneira razoavelmente segura de proteger os dados confidenciais dos aplicativos. Embora as funções da Política de LSA estejam disponíveis no Windows 2000 e superior (e ainda utilizadas para proteger configurações, como as senhas definidas pelos serviços do Windows), a Microsoft não as recomenda. Portanto, estou apenas mencionando estas funções para explicar por que elas deveriam ser evitadas.

  O problema com as funções da Política de LSA é que junto com o gerenciamento e criptografia de chaves elas controlam o armazenamento de dados usando a área protegida do registro do Windows. Isso pode parecer bom, mas não é porque a quantidade de armazenamento disponível às funções da Política de LSA é limitada a 4.096 slots, e metade deles já é utilizada pelo sistema. Se os aplicativos continuarem usando a Política de LSA para armazenar dados confidenciais, correm o risco de ficar sem espaço. Em seguida, como somente os usuários altamente privilegiados podem chamar as funções da Política de LSA, elas não funcionarão em aplicativos que rodam sob contas não-privilegiadas, como ASP.NET. E o que é pior: existem ferramentas como LSADUMP2 (http://razor.bindview.com/tools/desc/lsadump2_readme.html) que podem revelar os dados confidenciais do LSA. Conclusão: não use as funções da Política de LSA para proteção de dados.

 

Usando DPAPI

Como alternativa às funções da Política de LSA, a Microsoft recomenda o uso de um subset de CryptoAPI chamado DPAPI. DPAPI possui duas funções que podem ser usadas na proteção de dados: CryptProtectData e CryptUnprotectData. Essas funções são implementadas em crypt32.dll e também podem ser chamadas a partir de aplicativos baseados em .NET Framework via P/Invoke. DPAPI faz parte do sistema operacional e está disponível no Windows 2000 e superior.

Ao contrário das funções LSA, a DPAPI não controla o armazenamento de dados, mas pode gerar chaves específicas de máquina (ou de usuário) para codificar e decodificar dados. Para diferenciar os dois tipos de chaves, a documentação da DPAPI se refere a eles como armazenagem de máquina e armazenagem de usuário.

As chaves específicas de máquina geralmente não são seguras, porque elas podem ser usadas por qualquer um que consiga obter acesso ao sistema. É possível gerar uma versão mais segura de uma chave específica de máquina passando uma senha para uma função DPAPI. Essa opção é melhor porque requer que um visitante saiba o valor desta senha, porém ela cria o desafio de armazená-la, o que nos reporta ao problema original. Uma outra questão com relação às chaves específicas de máquina é que se ocorrer uma mudança no ambiente não há garantia de que elas serão as mesmas. Tais mudanças, como mover o aplicativo para um outro computador ou re-compilar [r4] o sistema, podem correr o risco de violar o aplicativo que usa DPAPI com chaves específicas de máquina.

Embora as chaves específicas de usuário sejam consideradas mais seguras, elas têm as mesmas limitações como qualquer outro tipo de controle de acesso baseado em identidade. Primeiro, porque as chaves de usuário só podem ser geradas por programas que rodam com perfis de usuário carregados, elas não funcionarão nos aplicativos ASP.NET e em aplicativos que rodam de acordo com certas contas embutidas. Embora haja um modo de superar esta limitação usando componentes de serviços, atinge-se um custo de maior complexidade e desempenho reduzido do aplicativo. Exige-se também um componente de serviços que rode sob uma conta privilegiada, o que viola o princípio de poucos privilégios. Se você decidir usar DPAPI com chaves específicas de usuário, saiba que somente o usuário que codifica os dados pode decodificar os resultados. Obviamente, isto não funcionará para os aplicativos que conseguem rodar sob diferentes contas de usuário ou cujas configurações possam ser definidas por usuários diferentes. Outro problema com as chaves específicas de usuário é que elas permitem que todos os aplicativos que rodem sob o mesmo perfil de usuário possam ter acesso aos dados de cada um, podendo se tornar uma brecha na segurança. Como na DPAPI que usa um armazenamento de máquina, este problema também pode ser abordado exigindo-se que o visitante forneça uma senha. Em relação ao exemplo anterior, isso representará um problema de armazenamento dessa senha. Infelizmente, DPAPI não lhe permite usar chaves específicas de máquina e de usuário ao mesmo tempo (na mesma chamada de CryptProtectData).

  DPAPI é recomendada a aplicativos que rodam sob uma conta de usuário única com perfil de usuário carregado e cujas configurações são definidas pelo mesmo usuário. [r5] Um exemplo típico seria um aplicativo de serviço Windows que roda sob uma conta de usuário local ou de domínio. Se o seu aplicativo satisfaz essas exigências, ele deveria usar DPAPI com chaves específicas de usuário. No caso de outros aplicativos, você pode usar DPAPI com chaves específicas de máquina protegidas por senha. Você pode definir essa senha no código-fonte do aplicativo e obscurecer o código binário, assim a senha não consegue ser achada facilmente. Porém, se você escolher esta abordagem saiba que mesmo o código obscurecido pode ser obtido por engenharia reversa.

Se o seu aplicativo não usa diretamente DPAPI com chaves específicas de usuário e não pode sofrer o risco da engenharia reversa, leve em consideração a implementação da DPAPI com um componente de serviço, mas saiba que esta opção afetará o desempenho do seu aplicativo. (consulte "Use DPAPI User Store from ASP.NET with Enterprise Services" no MSDN Library). Se você escolher esta opção, prepare-se para implementar um esquema de autorização para evitar que aplicativos e usuários maliciosos chamem o componente de serviços – o que pode se tornar uma tarefa complicada. Para saber como lidar com essa questão, dê uma olhada na ferramenta CipherSafe.NET (http://www.obviex.com/ciphersafe/) e na documentação que a acompanha; este aplicativo ilustra a utilização da DPAPI no gerenciamento de aplicativos confidenciais e oferece uma abordagem bastante interessante que resolve o problema da autorização.

 

Escondendo Chaves no Código-Fonte do Aplicativo

Embora os profissionais de segurança continuem insistindo com os desenvolvedores de aplicativos para deixarem de "esconder" as chaves criptográficas (e outros dados confidenciais) no código-fonte de seus aplicativos, eles continuam ignorando este conselho. Existem duas explicações para isso. Uma delas é o motivo obviamente injustificável pelo qual os desenvolvedores escolhem essa abordagem: facilidade de implementação. A outra explicação é que as opções alternativas podem não funcionar em algumas situações. Por exemplo, considere o cenário onde diversos aplicativos (digamos, serviços Windows) que rodam em computadores diferentes precisam usar a mesma chave para codificar e decodificar dados armazenados em um banco de dados. Nesta situação, pode ser que os desenvolvedores escolham usar uma chave embutida no código-fonte do aplicativo, pois o uso da DPAPI não seria prático. Isso seria fácil, mas não seguro. Uma abordagem melhor seria solicitar a um administrador que definisse a chave de criptografia na instalação do aplicativo, codificasse seu valor por meio da DPAPI com uma chave específica de usuário da conta sob a qual roda o aplicativo e gravasse o valor codificado no registro do Windows ou no arquivo de configuração do aplicativo. No entanto, saiba que embora este método seja uma implementação técnica mais segura, ele gera um desafio de conduta.

Uma outra situação na qual embutir a chave no código-fonte do aplicativo pode ser a única opção é quando os proprietários dos dados do aplicativo tem o acesso limitado ou não tem nenhum acesso às máquinas que hospedam seus aplicativos. Um ambiente de hospedagem Web é um claro exemplo. Se a sua única opção for embutir a chave no código-fonte do aplicativo, você precisa compreender os riscos associados e lidar com eles de forma adequada.

Neste caso, o principal risco vem da engenharia reversa e nesse momento ela só pode ser abordada através de obscurecimento. O obscurecimento (Obfuscation) não torna a engenharia reversa impossível, mas pode tornar o processo mais caro e demorado. Compare a Figura 6 a qual mostra uma assembly descompilada obscurecida por um Demeanor (http://www.wiseowl.com/) com a Figura 2. Como todos os símbolos não-públicos na assembly obscurecida são renomeados utilizando-se caracteres que não podem ser impressos e as strings são codificadas, talvez seja impossível reconstruir a lógica do aplicativo a partir da assembly. Decompiladores comerciais, como Salamander, podem fazer o trabalho de engenharia reversa com mais facilidade convertendo os caracteres que não podem ser impressos aos seus equivalentes imprimíveis e convertendo as classes do aplicativo em arquivos fonte, mas eles são caros e ainda exigem que um hacker compreenda os símbolos ilegíveis (eis aonde o emaranhado de código [spaghetti code] poderia realmente lhe ajudar!).

 

Figura 6 – Decompilação de Assemblies Obscurecidas

 

Se você definir a chave no aplicativo, além de obscurecer o assembly, tente não armazenar os verdadeiros bytes de chave no código-fonte. Ao contrário, implemente a lógica de geração de chaves usando características persistentes, como o algoritmo de criptografia, tamanho de chave, frase secreta, vetor de inicialização e salt. Isto introduzirá uma camada extra de indireção, assim a chave não estará acessível até desfazer-se dos símbolos do aplicativo binário. Enquanto você não mudar a lógica de geração de chaves e as características da chave, com certeza a chave resultante será a mesma. Também pode ser uma boa idéia não usar strings estáticas como características de geração de chave, mas de preferência criá-las on the fly. Outra sugestão seria tratar a assembly do mesmo modo que o armazenamento de dados deveria ser tratado, ou seja, aplicando as ACLs apropriadas. E somente usar essa opção como último recurso, quando nenhuma outra técnica de proteção de dados funcionar e sua única alternativa seja deixar os dados confidenciais sem criptografia.

 

Armazenamento Isolado

O armazenamento isolado às vezes é mencionado como um método de proteção de dados. Ele permite que você restrinja o acesso aos dados do assembly do aplicativo. Embora essa opção possa ser útil, ela não é recomendada para proteger dados confidenciais porque não usa criptografia; o armazenamento isolado é mais compatível com o armazenamento de configurações específicas de usuário do aplicativo. Para armazenar dados confidenciais específicos ao usuário do aplicativo, use DPAPI junto com o armazenamento isolado.

 

Conclusão

Quando o assunto é proteção de dados confidenciais de aplicativos, não existem soluções perfeitas. Nenhuma tecnologia de software existente pode garantir segurança absoluta. O objetivo é escolher a melhor opção, ou a "menos pior", que funcionará em seu aplicativo e cumprirá suas exigências de segurança. Entender como as diversas técnicas e tecnologias oferecem proteção de dados irá ajudá-lo a avaliar as vulnerabilidades e evitará que você se sujeite a uma falsa sensação de segurança.

Se você decidir criar ou comprar uma solução de proteção de dados, certifique-se de que ela utiliza uma criptografia robusta, oferece proteção razoável para as chaves criptográficas e restringe o acesso aos dados criptografados. Embora nenhuma solução de software torne os dados de seu aplicativo absolutamente seguros, a abordagem certa pode lhe ajudar a evitar os tipos mais comuns de brechas de segurança.


 [r1]Esta estranha, mas o leitor logo se acostumará com o termo obscuridade, pois o texto cita muitas vezes este termo.

 [r2]Acho que quer dizer formatada. Se fosse uma aplicação, seria recompilar a aplicação

 [r3]Sugestão da frase: “...isto não é uma regra”

 [r4]Creio que o rebuild aqui significa recompilar o programa, Build / Rebuild Solution. Se o programa usa uma chave e vc o recompila, isso pode afetar algo.

 [r5]que nota lateral é essa? Isso não está no nosso texto.

Artigos relacionados