методы скрытия чувствительных строк в C ++ - PullRequest
82 голосов
/ 30 октября 2009

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

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

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

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

Edit:

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

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

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

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

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

Ответы [ 13 ]

41 голосов
/ 30 октября 2009

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

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

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

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

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

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

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

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

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

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

8 голосов
/ 30 октября 2009

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

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

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

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

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

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

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

5 голосов
/ 30 октября 2009

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

Пример:

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

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

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

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

4 голосов
/ 06 ноября 2009

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

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

// 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;
4 голосов
/ 30 октября 2009

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

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

std::string myKey = part1() + part2() + ... + partN();
3 голосов
/ 30 октября 2009

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

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

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

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;
    // ...
}
2 голосов
/ 30 октября 2009

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

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

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

2 голосов
/ 30 октября 2009

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

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

1 голос
/ 24 августа 2016

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

  1. Возьмите хеш (SHA256) личных данных и заполните их в коде как part1
  2. Возьмите XOR личных данных и их хэш и заполните их в коде как part2
  3. Заполнение данных: не храните их как char str [], а заполняйте во время выполнения, используя инструкции присваивания (как показано в макросе ниже)
  4. Теперь создайте личные данные во время выполнения, взяв XOR part1 и part2
  5. Дополнительный шаг : Рассчитать хэш сгенерированных данных и сравнить его с 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);
1 голос
/ 19 ноября 2015

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

...