Методы скрытия чувствительных строк в С++

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

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

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

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

Edit:

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

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

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

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

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

Ответ 1

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

Но если вы просто хотите убедиться, что ключ не отображается при запуске 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.

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

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

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

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

Согласование паролей

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

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

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

Ответ 2

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

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

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

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

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

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

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

Ответ 3

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

Пример:

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

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

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

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

Ответ 4

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

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

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

Ответ 5

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

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

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

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

Ответ 6

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

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

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

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

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

Ответ 7

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

EDIT: если вы собираетесь это сделать, то комбинация методов, которые указывает Крис, будет намного лучше, чем rot13.

Ответ 8

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

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

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

Ответ 9

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

Ответ 10

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

Ответ 11

Один из моих недавних попыток:

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

MACRO для заполнения данных:

Предположим, что личные данные имеют 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);

Ответ 12

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

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

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

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

Ответ 13

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

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

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

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