Мне нужно хранить конфиденциальную информацию (симметричный ключ шифрования, который я хочу сохранить в секрете) в моем приложении C ++. Простой подход заключается в следующем:
std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";
Однако запуск приложения через strings
процесс (или любой другой процесс, извлекающий строки из двоичного приложения) покажет указанную выше строку.
Какие методы следует использовать, чтобы скрыть такие конфиденциальные данные?
Редактировать:
Хорошо, почти все вы сказали, что «ваш исполняемый файл может быть реконструирован» - конечно! Это моя любимая мозоль, поэтому я собираюсь немного разглагольствовать:
Почему на 99% (хорошо, так что, возможно, я немного преувеличиваю) всех вопросов, связанных с безопасностью, на этом сайте есть ответы на торрент типа «нет возможности создать совершенно безопасную программу» - это бесполезно ответ! Безопасность - это скользящая шкала между идеальным удобством использования и отсутствием безопасности с одной стороны и идеальной безопасностью, но отсутствием удобства использования с другой.
Дело в том, что вы выбираете свое положение на этой скользящей шкале в зависимости от того, что вы пытаетесь сделать, и от среды, в которой будет работать ваше программное обеспечение. Я не пишу приложение для военной установки, я пишу приложение для домашнего ПК . Мне нужно зашифровать данные в ненадежной сети с помощью заранее известного ключа шифрования. В этих случаях, вероятно, достаточно «безопасности через безвестность»! Конечно, кто-то, у кого есть достаточно времени, энергии и навыков, мог бы перепроектировать двоичный файл и найти пароль, но знаете что? Мне все равно:
Время, которое у меня уходит на внедрение первоклассной безопасной системы, дороже, чем потеря продаж из-за взломанных версий (не то чтобы я на самом деле продавал это, но вы меня поняли). Это голубое небо "позволяет делать это как можно лучше" в программировании среди начинающих программистов глупо, если не сказать больше.
Спасибо, что нашли время ответить на этот вопрос - они были очень полезны. К сожалению, я могу принять только один ответ, но я проголосовал за все полезные ответы.
Ответы:
В принципе, любой человек , имеющий доступ к программе и отладчик может и будет найти ключ в приложении , если они хотят.
Но если вы просто хотите убедиться, что ключ не отображается при запуске
strings
в вашем двоичном файле, вы можете, например, убедиться, что ключ находится за пределами диапазона печати.Затенение ключа с помощью XOR
Например, вы можете использовать XOR для разделения ключа на два байтовых массива:
Если вы создадите 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 в комментариях ниже, это эффективно защитит от атаки по словарю с использованием, например, радужных таблиц.
Наконец, вы можете использовать комбинацию всех вышеперечисленных методов.
источник
Прежде всего, поймите, что вы ничего не можете сделать, чтобы остановить достаточно решительного хакера, а таких вокруг полно. Защита каждой игры и консоли рано или поздно взламывается, так что это временное решение.
Вы можете сделать 4 вещи, которые увеличат ваши шансы какое-то время оставаться незамеченными.
1) Скройте элементы строки каким-либо образом - что-то очевидное, например xoring (оператор ^), строка с другой строкой будет достаточно хороша, чтобы сделать строку невозможной для поиска.
2) Разделите строку на части - разделите строку и вставьте ее кусочки в методы со странным названием в странных модулях. Не упрощайте поиск и поиск метода со строкой в нем. Конечно, какой-то метод должен будет вызывать все эти биты, но это все равно немного усложняет задачу.
3) Никогда не создавайте строку в памяти - большинство хакеров используют инструменты, которые позволяют им видеть строку в памяти после того, как вы ее закодировали. По возможности избегайте этого. Если, например, вы отправляете ключ на сервер, отправляйте его символ за символом, чтобы не было всей строки. Конечно, если вы используете его из чего-то вроде кодировки RSA, это будет сложнее.
4) Создайте специальный алгоритм - вдобавок ко всему добавьте один или два уникальных поворота. Может быть, просто добавьте 1 ко всему, что вы создаете, или сделайте любое шифрование дважды, или добавьте сахар. Это лишь немного усложняет работу хакера, который уже знает, что искать, когда кто-то использует, например, ванильное хеширование md5 или шифрование RSA.
Прежде всего, убедитесь, что не слишком важно, когда (а это будет, когда ваше приложение станет достаточно популярным) ваш ключ будет обнаружен!
источник
Стратегия, которую я использовал в прошлом, - создать массив, казалось бы, случайных символов. Сначала вы вставляете, а затем находите свои конкретные символы с помощью алгебраического процесса, где каждый шаг от 0 до N будет давать число <размер массива, который содержит следующий символ в вашей обфусцированной строке. (Этот ответ сейчас кажется запутанным!)
Пример:
Учитывая массив символов (числа и тире приведены только для справки)
0123456789 ---------- ALFHNFELKD LKFKFLEHGT FLKRKLFRFK FJFJJFJ!JL
И уравнение, первые шесть результатов которого: 3, 6, 7, 10, 21, 47
Дала бы слово "ПРИВЕТ!" из массива выше.
источник
Я согласен с @Checkers, ваш исполняемый файл можно реконструировать.
Немного лучше создать его динамически, например:
std::string myKey = part1() + part2() + ... + partN();
источник
Конечно, хранение личных данных в программном обеспечении, которое поставляется пользователю, всегда сопряжено с риском. Любой достаточно образованный (и преданный своему делу) инженер может реконструировать данные.
При этом часто можно сделать вещи достаточно безопасными, подняв барьер, который люди должны преодолеть, чтобы раскрыть ваши личные данные. Обычно это хороший компромисс.
В вашем случае вы можете загромождать свои строки непечатаемыми данными, а затем декодировать их во время выполнения с помощью простой вспомогательной функции, например:
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; // ... }
источник
Я создал простой инструмент для шифрования строк, он может автоматически генерировать зашифрованные строки и имеет несколько дополнительных опций для этого, несколько примеров:
Строка как глобальная переменная:
// 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;
источник
В некоторой степени зависит от того, что вы пытаетесь защитить, как указывает Джошперри. По опыту могу сказать, что если это часть какой-то схемы лицензирования для защиты вашего программного обеспечения, не беспокойтесь. Они даже перепроектируют его. Просто используйте простой шифр, такой как ROT-13, чтобы защитить его от простых атак (пропустите строку над ним). Если это необходимо для защиты конфиденциальных данных пользователей, я бы поставил под вопрос, является ли защита этих данных локальным закрытым ключом разумным шагом. Опять же, все сводится к тому, что вы пытаетесь защитить.
РЕДАКТИРОВАТЬ: Если вы собираетесь это сделать, то комбинация методов, которые указывает Крис, будет намного лучше, чем rot13.
источник
Как было сказано ранее, полностью защитить вашу струну невозможно. Но есть способы защитить его при разумной безопасности.
Когда мне пришлось это сделать, я вставил в код невинно выглядящую строку (например, уведомление об авторских правах, или какое-то поддельное приглашение пользователя или что-то еще, что не будет изменено кем-то, исправляющим несвязанный код), зашифровал это, используя себя в качестве ключа, хешировал его (добавляя немного соли) и использовал результат как ключ для шифрования того, что я действительно хотел зашифровать.
Конечно, это можно взломать, но для этого нужен решительный хакер.
источник
Если вы используете Windows DPAPI, http://msdn.microsoft.com/en-us/library/ms995355.aspx
Как было сказано в предыдущем посте, если вы используете Mac, используйте связку ключей.
По сути, все эти милые идеи о том, как хранить ваш закрытый ключ внутри вашего двоичного файла, достаточно плохи с точки зрения безопасности, поэтому вам не следует их делать. Любой, кто получит ваш закрытый ключ, - это большое дело, не храните его в своей программе. В зависимости от того, как импортируется ваше приложение, вы можете хранить свои закрытые ключи на смарт-карте, на удаленном компьютере, с которым общается ваш код, или вы можете делать то, что делает большинство людей, и хранить его в очень безопасном месте на локальном компьютере («ключ store ", который похож на странный безопасный реестр), который защищен разрешениями и всеми возможностями вашей ОС.
Это решенная проблема, и ответ - НЕ хранить ключ внутри вашей программы :)
источник
Попробуй это . Исходный код объясняет, как на лету зашифровать и расшифровать все строки в данном проекте Visual Studio c ++.
источник
Один из методов, который я недавно попробовал:
part1
part2
part1
иpart2
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);
источник
Есть (очень легкий) обфускат проекта только для заголовков, сделанный 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");
источник
Вместо того, чтобы хранить закрытый ключ в исполняемом файле, вы можете запросить его у пользователя и сохранить с помощью внешнего диспетчера паролей , что-то вроде доступа к связке ключей Mac OS X.
источник
Зависит от контекста, но вы можете просто сохранить хэш ключа плюс соль (постоянная строка, которую легко скрыть).
Затем, когда (если) пользователь вводит ключ, вы добавляете соль , вычисляете хеш и сравниваете.
В этом случае соль , вероятно, не нужна, она останавливает атаку по словарю методом перебора, если хэш может быть изолирован (также известно, что поиск Google работает).
Хакеру все еще нужно куда-то вставить инструкцию jmp, чтобы обойти все это, но это гораздо сложнее, чем простой текстовый поиск.
источник