técnicas para obscurecer strings sensíveis em C ++


87

Preciso armazenar informações confidenciais (uma chave de criptografia simétrica que desejo manter particular) em meu aplicativo C ++. A abordagem simples é fazer isso:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

No entanto, executar o aplicativo por meio do stringsprocesso (ou qualquer outro que extraia strings de um aplicativo binário) revelará a string acima.

Que técnicas devem ser usadas para ocultar esses dados confidenciais?

Editar:

OK, quase todos vocês disseram "seu executável pode ser submetido a engenharia reversa" - é claro! Esta é uma implicância minha, então vou reclamar um pouco aqui:

Por que 99% (OK, talvez eu exagere um pouco) de todas as perguntas relacionadas à segurança neste site são respondidas com um torrent de "não há maneira possível de criar um programa perfeitamente seguro" - isso não é útil responda! Segurança é uma escala móvel entre usabilidade perfeita e nenhuma segurança em uma extremidade e segurança perfeita, mas nenhuma usabilidade na outra.

A questão é que você escolhe sua posição nessa escala móvel dependendo do que está tentando fazer e do ambiente em que o software será executado. Não estou escrevendo um aplicativo para uma instalação militar, estou escrevendo um aplicativo para um PC doméstico . Preciso criptografar dados em uma rede não confiável com uma chave de criptografia pré-conhecida. Nesses casos, "segurança através da obscuridade" provavelmente é suficiente! Claro, alguém com tempo, energia e habilidade suficientes poderia fazer a engenharia reversa do binário e encontrar a senha, mas adivinhe? Eu não me importo:

O tempo que levo para implementar um sistema seguro de primeira linha é mais caro do que a perda de vendas devido às versões quebradas (não que eu esteja realmente vendendo isso, mas você entendeu). Essa tendência "vamos fazer da melhor maneira possível" na programação entre os novos programadores é, no mínimo, tola.

Obrigado por responder a esta pergunta - eles foram muito úteis. Infelizmente, só posso aceitar uma resposta, mas votei positivamente em todas as respostas úteis.


2
Talvez, se você descrever o que está tentando realizar com a chave de criptografia, possamos oferecer conselhos sobre como evitar a necessidade de fazer isso.
Joshperry


4
@Kirill: é difícil chamar essa pergunta exatamente igual à que você mencionou. Na verdade, a ideia é a mesma. A questão não é.
xtofl

1
@xtofl, você é livre para não votar. Para mim, parecem duas questões idênticas.
Kirill V. Lyadvinsky

2
(Não relacionado ao discurso retórico) A própria definição de uma "chave privada" é a metade de um par de chaves pública / privada que você não fornece. Também neste caso, a chave privada é aquela que mantém no seu servidor; aquele em seu aplicativo cliente é público.
MSalters em

Respostas:


42

Basicamente, qualquer pessoa com acesso ao seu programa e um depurador pode e vai encontrar a chave no aplicativo se quiser.

Mas, se você apenas deseja ter certeza de que a chave não apareça durante a execução stringsem seu binário, você pode, por exemplo, certificar-se de que a chave não está dentro do intervalo de impressão.

Chave obscurecedora com XOR

Por exemplo, você pode usar XOR para dividir a chave em duas matrizes de bytes:

key = key1 XOR key2

Se você criar key1 com o mesmo comprimento de byte, keypoderá usar valores de byte (completamente) aleatórios e, em seguida, calcular key2:

key1[n] = crypto_grade_random_number(0..255)
key2[n] = key[n] XOR key1[n]

Você pode fazer isso em seu ambiente de construção e, em seguida, apenas armazenar key1e key2em seu aplicativo.

Protegendo seu binário

Outra abordagem é usar uma ferramenta para proteger seu binário. Por exemplo, existem várias ferramentas de segurança que podem garantir que seu binário seja ofuscado e inicie uma máquina virtual em que seja executado. Isso torna mais difícil (er) depurar e também é a maneira convencional pela qual muitos aplicativos seguros de nível comercial (também, infelizmente, malware) são protegidos.

Uma das principais ferramentas é o Themida , que faz um ótimo trabalho protegendo seus binários. É frequentemente usado por programas bem conhecidos, como o Spotify, para proteção contra engenharia reversa. Possui recursos para evitar a depuração em programas como OllyDbg e Ida Pro.

Há também uma lista maior, talvez um pouco desatualizada, de ferramentas para proteger seu binário .
Alguns deles são gratuitos.

Correspondência de senha

Alguém aqui discutiu o hash de senha + salt.

Se precisar armazenar a chave para compará-la com algum tipo de senha enviada pelo usuário, você deve usar uma função de hash unilateral, de preferência combinando nome de usuário, senha e um salt. O problema com isso, porém, é que seu aplicativo precisa conhecer o sal para poder fazer o unilateral e comparar os hashes resultantes. Portanto, você ainda precisa armazenar o sal em algum lugar de seu aplicativo. Mas, como @Edward aponta nos comentários abaixo, isso protegerá efetivamente contra um ataque de dicionário usando, por exemplo, tabelas de arco-íris.

Finalmente, você pode usar uma combinação de todas as técnicas acima.


Se for uma senha que o usuário deve inserir, armazene o hash + salt da senha. Veja a resposta abaixo.

1
@hapalibashi: Como você armazena o sal com segurança em seu aplicativo? Não acho que o OP precisava de um sistema de correspondência de senha unilateral, apenas uma maneira generalizada de armazenar chaves estáticas.
csl

2
Eu descobri que, ao examinar programas desmontados, normalmente não há muitos XORs, então se você espera usar o XOR para obscurecer algo, lembre-se de que eles chamam a atenção para si mesmos.
kb.

2
@kb - esse é um ponto interessante. Eu acho que você veria ands e ors bitwise acontecendo muito mais do que xor. a ^ b == (a & ~ b) || (~ a & b)
Jeremy Powell

4
Saber o valor do sal geralmente não dá uma vantagem ao adversário - o objetivo do sal é evitar um "ataque de dicionário", por meio do qual o invasor pré-computou os hashes para muitas entradas prováveis. Usar um sal os força a pré-computar seu dicionário com um novo baseado no sal. Se cada sal for usado apenas uma vez, o ataque de dicionário se tornará completamente inútil.
Edward Dixon

8

Em primeiro lugar, perceba que não há nada que você possa fazer para impedir um hacker suficientemente determinado, e há muitos deles por perto. A proteção em cada jogo e console é quebrada eventualmente, então esta é apenas uma correção temporária.

Existem 4 coisas que você pode fazer que aumentarão suas chances de ficar escondido por um tempo.

1) Oculte os elementos da string de alguma forma - algo óbvio como xorar (o operador ^) a string com outra string será bom o suficiente para tornar a string impossível de ser pesquisada.

2) Divida a string em pedaços - divida sua string e coloque pedaços dela em métodos com nomes estranhos em módulos estranhos. Não facilite a busca e a localização do método com a string. É claro que algum método terá que chamar todos esses bits, mas ainda torna um pouco mais difícil.

3) Nunca construa a string na memória - a maioria dos hackers usa ferramentas que permitem que eles vejam a string na memória depois de codificada. Se possível, evite isso. Se, por exemplo, você estiver enviando a chave para um servidor, envie-a caractere por caractere, para que a string inteira nunca esteja por perto. Claro, se você o estiver usando a partir de algo como a codificação RSA, isso é mais complicado.

4) Faça um algoritmo ad-hoc - além de tudo isso, adicione uma ou duas variações exclusivas. Talvez apenas adicione 1 a tudo que você produz, ou faça qualquer criptografia duas vezes, ou adicione um açúcar. Isso apenas torna as coisas um pouco mais difíceis para o hacker que já sabe o que procurar quando alguém está usando, por exemplo, hashing md5 vanilla ou criptografia RSA.

Acima de tudo, certifique-se de que não seja muito importante quando (e será quando o aplicativo se tornar popular o suficiente) sua chave for descoberta!


5

Uma estratégia que usei no passado é criar uma série de caracteres aparentemente aleatórios. Você inicialmente insere, e então localiza seus caracteres particulares com um processo algébrico onde cada passo de 0 a N produzirá um número <tamanho do array que contém o próximo caractere em sua string ofuscada. (Esta resposta está parecendo ofuscada agora!)

Exemplo:

Dada uma matriz de caracteres (números e travessões são apenas para referência)

0123456789
----------
ALFHNFELKD
LKFKFLEHGT
FLKRKLFRFK
FJFJJFJ!JL

E uma equação cujos primeiros seis resultados são: 3, 6, 7, 10, 21, 47

Geraria a palavra "OLÁ!" da matriz acima.


Boa ideia - acho que você poderia melhorá-lo ainda mais usando caracteres não imprimíveis na matriz ...
Thomi

4

Eu concordo com @Checkers, seu executável pode sofrer engenharia reversa.

Uma maneira um pouco melhor é criá-lo dinamicamente, por exemplo:

std::string myKey = part1() + part2() + ... + partN();

É verdade, isso evita que a string seja revelada ao pesquisar o binário. No entanto, sua string ainda está residente na memória. Sua solução provavelmente é boa o suficiente para o que estou fazendo.
Thomi

@Thomi, você pode, é claro, destruí- lo assim que terminar de usá- lo. Mesmo assim, não é a melhor maneira de lidar com strings sensíveis .
Nick Dandoulakis

... visto que destruí-lo não garante realmente que a memória será reutilizada imediatamente.
Thomi

4

Obviamente, armazenar dados privados em um software enviado ao usuário é sempre um risco. Qualquer engenheiro suficientemente educado (e dedicado) pode fazer a engenharia reversa dos dados.

Dito isso, muitas vezes você pode tornar as coisas seguras o suficiente, aumentando a barreira que as pessoas precisam superar para revelar seus dados privados. Geralmente é um bom compromisso.

No seu caso, você poderia confundir suas strings com dados não imprimíveis e, em seguida, decodificá-los no tempo de execução usando uma função auxiliar simples, como esta:

void unscramble( char *s )
{
    for ( char *str = s + 1; *str != 0; str += 2 ) {
        *s++ = *str;
    }
    *s = '\0';
}

void f()
{
    char privateStr[] = "\001H\002e\003l\004l\005o";
    unscramble( privateStr ); // privateStr is 'Hello' now.

    string s = privateStr;
    // ...
}

4

Eu criei uma ferramenta de criptografia simples para strings, ela pode gerar strings criptografadas automaticamente e tem algumas opções extras para fazer isso, alguns exemplos:

String como uma variável global:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xCF, 0x34, 0xF8, 0x5F, 0x5C, 0x3D, 0x22, 0x13, 0xB4, 0xF3, 0x63, 0x7E, 0x6B, 0x34, 0x01, 0xB7, 0xDB, 0x89, 0x9A, 0xB5, 0x1B, 0x22, 0xD4, 0x29, 0xE6, 0x7C, 0x43, 0x0B, 0x27, 0x00, 0x91, 0x5F, 0x14, 0x39, 0xED, 0x74, 0x7D, 0x4B, 0x22, 0x04, 0x48, 0x49, 0xF1, 0x88, 0xBE, 0x29, 0x1F, 0x27 };

myKey[30] -= 0x18;
myKey[39] -= 0x8E;
myKey[3] += 0x16;
myKey[1] += 0x45;
myKey[0] ^= 0xA2;
myKey[24] += 0x8C;
myKey[44] ^= 0xDB;
myKey[15] ^= 0xC5;
myKey[7] += 0x60;
myKey[27] ^= 0x63;
myKey[37] += 0x23;
myKey[2] ^= 0x8B;
myKey[25] ^= 0x18;
myKey[12] ^= 0x18;
myKey[14] ^= 0x62;
myKey[11] ^= 0x0C;
myKey[13] += 0x31;
myKey[6] -= 0xB0;
myKey[22] ^= 0xA3;
myKey[43] += 0xED;
myKey[29] -= 0x8C;
myKey[38] ^= 0x47;
myKey[19] -= 0x54;
myKey[33] -= 0xC2;
myKey[40] += 0x1D;
myKey[20] -= 0xA8;
myKey[34] ^= 0x84;
myKey[8] += 0xC1;
myKey[28] -= 0xC6;
myKey[18] -= 0x2A;
myKey[17] -= 0x15;
myKey[4] ^= 0x2C;
myKey[9] -= 0x83;
myKey[26] += 0x31;
myKey[10] ^= 0x06;
myKey[16] += 0x8A;
myKey[42] += 0x76;
myKey[5] ^= 0x58;
myKey[23] ^= 0x46;
myKey[32] += 0x61;
myKey[41] ^= 0x3B;
myKey[31] ^= 0x30;
myKey[46] ^= 0x6C;
myKey[35] -= 0x08;
myKey[36] ^= 0x11;
myKey[45] -= 0xB6;
myKey[21] += 0x51;
myKey[47] += 0xD9;

Como string Unicode com loop de descriptografia:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
wchar_t myKey[48];

myKey[21] = 0x00A6;
myKey[10] = 0x00B0;
myKey[29] = 0x00A1;
myKey[22] = 0x00A2;
myKey[19] = 0x00B4;
myKey[33] = 0x00A2;
myKey[0] = 0x00B8;
myKey[32] = 0x00A0;
myKey[16] = 0x00B0;
myKey[40] = 0x00B0;
myKey[4] = 0x00A5;
myKey[26] = 0x00A1;
myKey[18] = 0x00A5;
myKey[17] = 0x00A1;
myKey[8] = 0x00A0;
myKey[36] = 0x00B9;
myKey[34] = 0x00BC;
myKey[44] = 0x00B0;
myKey[30] = 0x00AC;
myKey[23] = 0x00BA;
myKey[35] = 0x00B9;
myKey[25] = 0x00B1;
myKey[6] = 0x00A7;
myKey[27] = 0x00BD;
myKey[45] = 0x00A6;
myKey[3] = 0x00A0;
myKey[28] = 0x00B4;
myKey[14] = 0x00B6;
myKey[7] = 0x00A6;
myKey[11] = 0x00A7;
myKey[13] = 0x00B0;
myKey[39] = 0x00A3;
myKey[9] = 0x00A5;
myKey[2] = 0x00A6;
myKey[24] = 0x00A7;
myKey[46] = 0x00A6;
myKey[43] = 0x00A0;
myKey[37] = 0x00BB;
myKey[41] = 0x00A7;
myKey[15] = 0x00A7;
myKey[31] = 0x00BA;
myKey[1] = 0x00AC;
myKey[47] = 0x00D5;
myKey[20] = 0x00A6;
myKey[5] = 0x00B0;
myKey[38] = 0x00B0;
myKey[42] = 0x00B2;
myKey[12] = 0x00A6;

for (unsigned int fngdouk = 0; fngdouk < 48; fngdouk++) myKey[fngdouk] ^= 0x00D5;

String como uma variável global:

// myKey = "mysupersupersecretpasswordthatyouwillneverguess";
unsigned char myKey[48] = { 0xAF, 0xBB, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xB7, 0xB2, 0xA7, 0xB4, 0xB5, 0xA7, 0xA5, 0xB4, 0xA7, 0xB6, 0xB2, 0xA3, 0xB5, 0xB5, 0xB9, 0xB1, 0xB4, 0xA6, 0xB6, 0xAA, 0xA3, 0xB6, 0xBB, 0xB1, 0xB7, 0xB9, 0xAB, 0xAE, 0xAE, 0xB0, 0xA7, 0xB8, 0xA7, 0xB4, 0xA9, 0xB7, 0xA7, 0xB5, 0xB5, 0x42 };

for (unsigned int dzxykdo = 0; dzxykdo < 48; dzxykdo++) myKey[dzxykdo] -= 0x42;

Não, usei o site stringencrypt.com para fazer o trabalho. Ele tem exemplos para C / C ++ stringencrypt.com/c-cpp-encryption que você pode considerar usá-lo para automatizar a criptografia de string simples.
Bartosz Wójcik

2

Um pouco dependente do que você está tentando proteger, como aponta Joshperry. Por experiência própria, eu diria que, se faz parte de algum esquema de licenciamento para proteger seu software, não se preocupe. Eles irão, evidentemente, fazer engenharia reversa. Basta usar uma cifra simples como ROT-13 para protegê-lo de ataques simples (linha passando por cima dele). Se for para proteger dados confidenciais de usuários, eu questionaria se proteger esses dados com uma chave privada armazenada localmente é uma medida acertada. Novamente, tudo se resume ao que você está tentando proteger.

EDIT: Se você vai fazer isso, então uma combinação de técnicas que Chris aponta será muito melhor do que podridão13.


2

Como foi dito antes, não há como proteger totalmente sua corda. Mas existem maneiras de protegê-lo com uma segurança razoável.

Quando tive que fazer isso, coloquei uma string de aparência inocente no código (um aviso de direitos autorais, por exemplo, ou algum prompt de usuário falso ou qualquer outra coisa que não será alterada por alguém corrigindo um código não relacionado), criptografando-a usando a si mesma como uma chave, fiz um hash (adicionando um pouco de sal) e usei o resultado como uma chave para criptografar o que eu realmente queria criptografar.

Claro que isso pode ser hackeado, mas é preciso um hacker determinado para fazer isso.


Boa ideia - outra forma de obscuridade é usar uma string que ainda é razoavelmente forte (longa, pontuação e todo esse jazz), mas não parece obviamente uma senha.
Thomi

1

Se você estiver usando a DPAPI do usuário do Windows, http://msdn.microsoft.com/en-us/library/ms995355.aspx

Como um post anterior disse se você estiver no mac use o chaveiro.

Basicamente, todas essas idéias engraçadas sobre como armazenar sua chave privada dentro de seu binário são suficientemente fracas do ponto de vista da segurança que você não deve fazê-las. Qualquer pessoa que receber sua chave privada é um grande negócio, não a mantenha dentro de seu programa. Dependendo de como seu aplicativo é importado, você pode manter suas chaves privadas em um cartão inteligente, em um computador remoto com o qual seu código se comunica ou pode fazer o que a maioria das pessoas faz e mantê-lo em um local muito seguro no computador local (a chave " store "que é uma espécie de registro seguro estranho) que é protegido por permissões e toda a força do seu sistema operacional.

Este é um problema resolvido e a resposta NÃO é manter a chave dentro do seu programa :)


1

Experimente isso . O código-fonte explica como criptografar e descriptografar imediatamente todas as strings em um determinado projeto Visual Studio c ++.


1

Um método que tentei recentemente é:

  1. Pegue o hash (SHA256) dos dados privados e preencha-os no código como part1
  2. Pegue o XOR de dados privados e seu hash e preencha-o no código como part2
  3. Preencher dados: não os armazene como char str [], mas preencha no tempo de execução usando instruções de atribuição (conforme mostrado na macro abaixo)
  4. Agora, gere os dados privados em tempo de execução, tomando o XOR de part1epart2
  5. Etapa adicional : calcular o hash dos dados gerados e compará-los com part1. Ele verificará a integridade dos dados privados.

MACRO para preencher dados:

Suponha que os dados privados tenham 4 bytes. Definimos uma macro para ele que salva os dados com instruções de atribuição em alguma ordem aleatória.

#define POPULATE_DATA(str, i0, i1, i2, i3)\
{\
    char *p = str;\
    p[3] = i3;\
    p[2] = i2;\
    p[0] = i0;\
    p[1] = i1;\
}

Agora use esta macro no código onde você precisa salvar part1e part2, da seguinte maneira:

char part1[4] = {0};
char part2[4] = {0};
POPULATE_DATA(part1, 1, 2, 3, 4); 
POPULATE_DATA(part2, 5, 6, 7, 8);

0

Em vez de armazenar a chave privada em seu executável, você pode solicitá-la ao usuário e armazená-la por meio de um gerenciador de senhas externo , algo semelhante ao Mac OS X Keychain Access.


bem, sim e não .. normalmente eu concordo com você, mas neste caso estou tentando esconder isso do usuário do software, então armazená-lo em um sistema externo não é uma boa ideia (muitos sistemas de chaveiro podem expor as senhas como texto simples para usuários com autorização adequada). O software Keychain é ótimo para senhas de usuários, mas não tão bom para chaves de criptografia de aplicativos.
Thomi

se parecer inseguro (embora, dados seus esclarecimentos, talvez seja adequado), você pode combinar um chaveiro e algo codificado :)

0

Depende do contexto, mas você pode simplesmente armazenar o hash da chave mais um salt (string constante, fácil de ocultar).

Então, quando (se) o usuário inserir a chave, você adiciona o sal , calcula o hash e compara.

O salt é provavelmente desnecessário neste caso, ele interrompe um ataque de dicionário de força bruta se o hash puder ser isolado (uma pesquisa no Google também funcionou).

Um hacker ainda precisa inserir uma instrução jmp em algum lugar para ignorar o lote inteiro, mas isso é um pouco mais complicado do que uma simples pesquisa de texto.


Esta é uma chave de criptografia, não um hash de senha. Preciso da chave real para codificar e decodificar os dados. O usuário nunca vê a chave, ela nunca é armazenada fora do binário.
Thomi

0

Há uma ofuscação de projeto (muito leve) apenas de cabeçalho feita por adamyaxley que funciona perfeitamente. É baseado em funções lambda e macros e criptografa strings literais com uma cifra XOR em tempo de compilação. Se necessário, podemos alterar a semente de cada string.

O código a seguir não armazenará a string "hello world" no binário compilado.

#include "obfuscate.h"

int main()
{
  std::cout << AY_OBFUSCATE("Hello World") << std::endl;
  return 0;
}

Eu testei com c ++ 17 e visual studio 2019 e verifiquei via IDA e confirmo que a string está oculta. Uma vantagem preciosa em comparação com ADVobfuscator é que ele pode ser convertido em std :: string (embora ainda esteja oculto no binário compilado):

std::string var = AY_OBFUSCATE("string");
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.