Способы скрытия конфиденциальных строк в C ++


87

Мне нужно хранить конфиденциальную информацию (симметричный ключ шифрования, который я хочу сохранить в секрете) в моем приложении C ++. Простой подход заключается в следующем:

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

Однако запуск приложения через stringsпроцесс (или любой другой процесс, извлекающий строки из двоичного приложения) покажет указанную выше строку.

Какие методы следует использовать, чтобы скрыть такие конфиденциальные данные?

Редактировать:

Хорошо, почти все вы сказали, что «ваш исполняемый файл может быть реконструирован» - конечно! Это моя любимая мозоль, поэтому я собираюсь немного разглагольствовать:

Почему на 99% (хорошо, так что, возможно, я немного преувеличиваю) всех вопросов, связанных с безопасностью, на этом сайте есть ответы на торрент типа «нет возможности создать совершенно безопасную программу» - это бесполезно ответ! Безопасность - это скользящая шкала между идеальным удобством использования и отсутствием безопасности с одной стороны и идеальной безопасностью, но отсутствием удобства использования с другой.

Дело в том, что вы выбираете свое положение на этой скользящей шкале в зависимости от того, что вы пытаетесь сделать, и от среды, в которой будет работать ваше программное обеспечение. Я не пишу приложение для военной установки, я пишу приложение для домашнего ПК . Мне нужно зашифровать данные в ненадежной сети с помощью заранее известного ключа шифрования. В этих случаях, вероятно, достаточно «безопасности через безвестность»! Конечно, кто-то, у кого есть достаточно времени, энергии и навыков, мог бы перепроектировать двоичный файл и найти пароль, но знаете что? Мне все равно:

Время, которое у меня уходит на внедрение первоклассной безопасной системы, дороже, чем потеря продаж из-за взломанных версий (не то чтобы я на самом деле продавал это, но вы меня поняли). Это голубое небо "позволяет делать это как можно лучше" в программировании среди начинающих программистов глупо, если не сказать больше.

Спасибо, что нашли время ответить на этот вопрос - они были очень полезны. К сожалению, я могу принять только один ответ, но я проголосовал за все полезные ответы.


2
Возможно, если вы опишете, чего вы пытаетесь достичь с помощью ключа шифрования, мы сможем дать совет, как этого избежать.
Джошперри,


4
@Kirill: этот вопрос сложно назвать точно таким же, как тот, который вы упомянули. Действительно, идея та же. Вопрос не в том.
xtofl

1
@xtofl, вы можете не голосовать. На мой взгляд, это два одинаковых вопроса.
Кирилл В. Лядвинский

2
(Не имеет отношения к разглагольствованиям) Само определение «закрытого ключа» - это половина пары открытый / закрытый ключ, которую вы не раздаете. В этом случае закрытый ключ - это тот, который вы храните на своем сервере; тот, который находится в вашем клиентском приложении, является общедоступным.
MSalters

Ответы:


42

В принципе, любой человек , имеющий доступ к программе и отладчик может и будет найти ключ в приложении , если они хотят.

Но если вы просто хотите убедиться, что ключ не отображается при запуске stringsв вашем двоичном файле, вы можете, например, убедиться, что ключ находится за пределами диапазона печати.

Затенение ключа с помощью XOR

Например, вы можете использовать XOR для разделения ключа на два байтовых массива:

key = key1 XOR key2

Если вы создадите key1 с такой же длиной байта, как keyвы можете использовать (полностью) случайные байтовые значения, а затем вычислите key2:

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

Вы можете сделать это в своей среде сборки, а затем только сохранить key1и key2в своем приложении.

Защита вашего двоичного файла

Другой подход - использовать инструмент для защиты вашего двоичного файла. Например, есть несколько инструментов безопасности, которые могут убедиться, что ваш двоичный файл обфусцирован и запускает виртуальную машину, на которой он работает. Это затрудняет отладку, а также является обычным способом защиты многих защищенных приложений коммерческого уровня (а также, увы, вредоносных программ).

Один из лучших инструментов - Themida , который отлично защищает ваши двоичные файлы. Он часто используется хорошо известными программами, такими как Spotify, для защиты от обратного проектирования. Он имеет функции для предотвращения отладки в таких программах, как OllyDbg и Ida Pro.

Существует также более крупный, возможно, несколько устаревший список инструментов для защиты вашего двоичного файла .
Некоторые из них бесплатны.

Подбор пароля

Кто-то здесь обсуждал хеширование пароля + соль.

Если вам нужно сохранить ключ, чтобы сопоставить его с каким-либо паролем, представленным пользователем, вы должны использовать функцию одностороннего хеширования, предпочтительно путем объединения имени пользователя, пароля и соли. Однако проблема в том, что ваше приложение должно знать соль, чтобы иметь возможность делать односторонний и сравнивать полученные хэши. Поэтому вам все равно нужно хранить соль где-нибудь в вашем приложении. Но, как отмечает @Edward в комментариях ниже, это эффективно защитит от атаки по словарю с использованием, например, радужных таблиц.

Наконец, вы можете использовать комбинацию всех вышеперечисленных методов.


Если это пароль, который пользователь должен ввести, сохраните хэш + соль пароля. См. Ответ ниже.

1
@hapalibashi: Как безопасно хранить соль в вашем приложении? Я не думаю, что OP нуждалась в односторонней системе сопоставления паролей, просто в обобщенном способе хранения статических ключей.
csl,

2
Я обнаружил, что при просмотре дизассемблированных программ обычно не так много XOR, поэтому, если вы надеетесь использовать XOR, чтобы что-то скрыть, имейте в виду, что они привлекают внимание к себе.
кб.

2
@kb - это интересный момент. Я предполагаю, что вы увидите, что побитовые ands и ors происходят намного чаще, чем xor. a ^ b == (a & ~ b) || (~ a & b)
Джереми Пауэлл

4
Знание значения соли обычно не дает злоумышленнику преимущества - его цель состоит в том, чтобы избежать «атаки по словарю», при которой злоумышленник предварительно вычисляет хеши для многих вероятных входных данных. Использование соли заставляет их предварительно вычислить свой словарь с новым, основанным на соли. Если каждая соль используется только один раз, атака по словарю становится совершенно бесполезной.
Эдвард Диксон

8

Прежде всего, поймите, что вы ничего не можете сделать, чтобы остановить достаточно решительного хакера, а таких вокруг полно. Защита каждой игры и консоли рано или поздно взламывается, так что это временное решение.

Вы можете сделать 4 вещи, которые увеличат ваши шансы какое-то время оставаться незамеченными.

1) Скройте элементы строки каким-либо образом - что-то очевидное, например xoring (оператор ^), строка с другой строкой будет достаточно хороша, чтобы сделать строку невозможной для поиска.

2) Разделите строку на части - разделите строку и вставьте ее кусочки в методы со странным названием в странных модулях. Не упрощайте поиск и поиск метода со строкой в ​​нем. Конечно, какой-то метод должен будет вызывать все эти биты, но это все равно немного усложняет задачу.

3) Никогда не создавайте строку в памяти - большинство хакеров используют инструменты, которые позволяют им видеть строку в памяти после того, как вы ее закодировали. По возможности избегайте этого. Если, например, вы отправляете ключ на сервер, отправляйте его символ за символом, чтобы не было всей строки. Конечно, если вы используете его из чего-то вроде кодировки RSA, это будет сложнее.

4) Создайте специальный алгоритм - вдобавок ко всему добавьте один или два уникальных поворота. Может быть, просто добавьте 1 ко всему, что вы создаете, или сделайте любое шифрование дважды, или добавьте сахар. Это лишь немного усложняет работу хакера, который уже знает, что искать, когда кто-то использует, например, ванильное хеширование md5 или шифрование RSA.

Прежде всего, убедитесь, что не слишком важно, когда (а это будет, когда ваше приложение станет достаточно популярным) ваш ключ будет обнаружен!


5

Стратегия, которую я использовал в прошлом, - создать массив, казалось бы, случайных символов. Сначала вы вставляете, а затем находите свои конкретные символы с помощью алгебраического процесса, где каждый шаг от 0 до N будет давать число <размер массива, который содержит следующий символ в вашей обфусцированной строке. (Этот ответ сейчас кажется запутанным!)

Пример:

Учитывая массив символов (числа и тире приведены только для справки)

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

И уравнение, первые шесть результатов которого: 3, 6, 7, 10, 21, 47

Дала бы слово "ПРИВЕТ!" из массива выше.


Хорошая идея - я думаю, вы могли бы еще больше улучшить ее, используя
непечатаемые

4

Я согласен с @Checkers, ваш исполняемый файл можно реконструировать.

Немного лучше создать его динамически, например:

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

Правда, это позволяет избежать раскрытия строки при поиске в двоичном файле. Тем не менее, ваша строка все еще находится в памяти .. Ваше решение, вероятно, достаточно хорошее для того, что я делаю.
Томи

@ Томи, ты, конечно, можешь уничтожить его, как только закончишь с ним. Но все же это не лучший способ работать с чувствительными струнами.
Ник Дандулакис,

... поскольку его уничтожение на самом деле не гарантирует, что память будет повторно использована сразу.
Томи

4

Конечно, хранение личных данных в программном обеспечении, которое поставляется пользователю, всегда сопряжено с риском. Любой достаточно образованный (и преданный своему делу) инженер может реконструировать данные.

При этом часто можно сделать вещи достаточно безопасными, подняв барьер, который люди должны преодолеть, чтобы раскрыть ваши личные данные. Обычно это хороший компромисс.

В вашем случае вы можете загромождать свои строки непечатаемыми данными, а затем декодировать их во время выполнения с помощью простой вспомогательной функции, например:

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

Я создал простой инструмент для шифрования строк, он может автоматически генерировать зашифрованные строки и имеет несколько дополнительных опций для этого, несколько примеров:

Строка как глобальная переменная:

// 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;

В виде строки юникода с циклом дешифрования:

// 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;

Строка как глобальная переменная:

// 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;

Нет, я использовал сайт stringencrypt.com для работы. В нем есть примеры для C / C ++ stringencrypt.com/c-cpp-encryption, которые вы могли бы использовать для автоматизации простого шифрования строк.
Bartosz Wójcik

2

В некоторой степени зависит от того, что вы пытаетесь защитить, как указывает Джошперри. По опыту могу сказать, что если это часть какой-то схемы лицензирования для защиты вашего программного обеспечения, не беспокойтесь. Они даже перепроектируют его. Просто используйте простой шифр, такой как ROT-13, чтобы защитить его от простых атак (пропустите строку над ним). Если это необходимо для защиты конфиденциальных данных пользователей, я бы поставил под вопрос, является ли защита этих данных локальным закрытым ключом разумным шагом. Опять же, все сводится к тому, что вы пытаетесь защитить.

РЕДАКТИРОВАТЬ: Если вы собираетесь это сделать, то комбинация методов, которые указывает Крис, будет намного лучше, чем rot13.


2

Как было сказано ранее, полностью защитить вашу струну невозможно. Но есть способы защитить его при разумной безопасности.

Когда мне пришлось это сделать, я вставил в код невинно выглядящую строку (например, уведомление об авторских правах, или какое-то поддельное приглашение пользователя или что-то еще, что не будет изменено кем-то, исправляющим несвязанный код), зашифровал это, используя себя в качестве ключа, хешировал его (добавляя немного соли) и использовал результат как ключ для шифрования того, что я действительно хотел зашифровать.

Конечно, это можно взломать, но для этого нужен решительный хакер.


Хорошая идея - другая форма неясности заключается в использовании строки, которая все еще достаточно сильна (длинная, пунктуация и все такое), но явно не похожа на пароль.
Томи

1

Если вы используете Windows DPAPI, http://msdn.microsoft.com/en-us/library/ms995355.aspx

Как было сказано в предыдущем посте, если вы используете Mac, используйте связку ключей.

По сути, все эти милые идеи о том, как хранить ваш закрытый ключ внутри вашего двоичного файла, достаточно плохи с точки зрения безопасности, поэтому вам не следует их делать. Любой, кто получит ваш закрытый ключ, - это большое дело, не храните его в своей программе. В зависимости от того, как импортируется ваше приложение, вы можете хранить свои закрытые ключи на смарт-карте, на удаленном компьютере, с которым общается ваш код, или вы можете делать то, что делает большинство людей, и хранить его в очень безопасном месте на локальном компьютере («ключ store ", который похож на странный безопасный реестр), который защищен разрешениями и всеми возможностями вашей ОС.

Это решенная проблема, и ответ - НЕ хранить ключ внутри вашей программы :)


1

Попробуй это . Исходный код объясняет, как на лету зашифровать и расшифровать все строки в данном проекте Visual Studio c ++.


1

Один из методов, который я недавно попробовал:

  1. Возьмите хэш (SHA256) личных данных и заполните его в коде как part1
  2. Возьмите XOR частных данных и их хеш и заполните его в коде как part2
  3. Заполнение данных: не сохраняйте их как char str [], а заполняйте во время выполнения с помощью инструкций по назначению (как показано в макросе ниже)
  4. Теперь сгенерируйте частные данные во время выполнения, взяв XOR part1иpart2
  5. Дополнительный шаг : вычислить хэш сгенерированных данных и сравнить его с part1. Он проверит целостность личных данных.

МАКРОС для заполнения данных:

Предположим, личные данные имеют размер 4 байта. Мы определяем для него макрос, который сохраняет данные с инструкциями присваивания в произвольном порядке.

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

Теперь используйте этот макрос в коде, где вам нужно сохранить part1и part2, как показано ниже:

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

1

Есть (очень легкий) обфускат проекта только для заголовков, сделанный adamyaxley, который отлично работает. Он основан на лямбда-функциях и макросах и шифрует строки буквально с помощью шифра XOR во время компиляции. При необходимости мы можем изменить семя для каждой строки.

Следующий код не сохранит строку «hello world» в скомпилированном двоичном файле.

#include "obfuscate.h"

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

Я тестировал c ++ 17 и Visual Studio 2019, проверял через IDA и подтверждаю, что строка скрыта. Одно драгоценное преимущество по сравнению с ADVobfuscator заключается в том, что он может быть преобразован в std :: string (при этом все еще скрытый в скомпилированном двоичном файле ):

std::string var = AY_OBFUSCATE("string");

0

Вместо того, чтобы хранить закрытый ключ в исполняемом файле, вы можете запросить его у пользователя и сохранить с помощью внешнего диспетчера паролей , что-то вроде доступа к связке ключей Mac OS X.


ну да и нет ... обычно я соглашусь с вами, но в данном случае я пытаюсь скрыть это от пользователя программного обеспечения, поэтому хранить его во внешней системе - не лучшая идея (многие системы связки ключей могут показывать пароли в виде обычного текста пользователям, получившим соответствующие полномочия). Программное обеспечение связки ключей отлично подходит для паролей пользователей, но не так хорошо для ключей шифрования приложений.
Томи

если это кажется небезопасным (хотя, учитывая ваши пояснения, возможно, этого достаточно), вы можете объединить брелок и что-то жестко запрограммированное :)

0

Зависит от контекста, но вы можете просто сохранить хэш ключа плюс соль (постоянная строка, которую легко скрыть).

Затем, когда (если) пользователь вводит ключ, вы добавляете соль , вычисляете хеш и сравниваете.

В этом случае соль , вероятно, не нужна, она останавливает атаку по словарю методом перебора, если хэш может быть изолирован (также известно, что поиск Google работает).

Хакеру все еще нужно куда-то вставить инструкцию jmp, чтобы обойти все это, но это гораздо сложнее, чем простой текстовый поиск.


Это ключ шифрования, а не хеш пароля. Мне нужен настоящий ключ для кодирования и декодирования данных. Пользователь никогда не видит ключ, он никогда не сохраняется вне двоичного файла.
Томи
Используя наш сайт, вы подтверждаете, что прочитали и поняли нашу Политику в отношении файлов cookie и Политику конфиденциальности.
Licensed under cc by-sa 3.0 with attribution required.