Вопрос

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

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

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

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

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

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

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

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

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

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

Это было полезно?

Решение

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

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

Скрытие ключа с помощью XOR

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

key = key1 XOR key2

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

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

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

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

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

Одним из главных инструментов является Темида, который отлично защищает ваши двоичные файлы.Он часто используется известными программами, такими как 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();

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

Лицензировано под: CC-BY-SA с атрибуция
Не связан с StackOverflow
scroll top