прийоми затемнення чутливих рядків у C ++


87

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

std::string myKey = "mysupersupersecretpasswordthatyouwillneverguess";

Однак запуск програми через stringsпроцес (або будь-який інший, що витягує рядки з двійкового додатка), відкриє вказаний вище рядок.

Які методи слід використовувати для затемнення таких конфіденційних даних?

Редагувати:

Добре, так що майже всі ви сказали, що "ваш виконуваний файл може бути реконструйований" - звичайно! Це моя домашня тварина, тому я трохи поговорю тут:

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

Справа в тому, що ви вибираєте свою позицію на цій ковзній шкалі залежно від того, що ви намагаєтесь зробити, і від середовища, в якому буде працювати ваше програмне забезпечення. Я не пишу додаток для військової інсталяції, а додаток для домашнього ПК . Мені потрібно зашифрувати дані через ненадійну мережу за допомогою відомого ключа шифрування. У цих випадках "безпека через затемнення", мабуть, досить хороша! Звичайно, хтось із достатньою кількістю часу, енергії та навичок міг би перепроектувати двійковий файл та знайти пароль, але вгадайте що? Мені все одно:

Час, який потрібен мені для впровадження першокласної захищеної системи, дорожчий, ніж втрата продажів через зламані версії (не те, що я насправді продаю це, але ви розумієте мою думку). Це "блакитне небо" "дозволяє робити це якнайкращим можливим способом" у програмуванні серед нових програмістів, щонайменше, дурне.

Дякуємо, що знайшли час відповісти на це запитання - вони були найбільш корисними. На жаль, я можу прийняти лише одну відповідь, але я проголосував за всі корисні відповіді.


2
Можливо, якщо ви опишете, що ви намагаєтесь досягти за допомогою ключа шифрування, ми можемо запропонувати поради, як уникнути необхідності це робити.
joshperry


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: Як ви надійно зберігаєте сіль у своєму додатку? Я не думаю, що ОП потребувала односторонньої системи зіставлення паролів, просто узагальнений спосіб зберігання статичних ключів.
csl

2
Я виявив, що, розглядаючи розібрані програми, зазвичай не так багато XOR, тому, якщо ви сподіваєтесь використовувати XOR, щоб щось затулити, майте на увазі, що вони привертають увагу до себе.
кб.

2
@kb - це цікавий момент. Я гадаю, ви бачили б побітове ands та or, що відбувається набагато більше, ніж xor. a ^ b == (a & ~ b) || (~ a & b)
Джеремі Пауелл

4
Знання значення солі зазвичай не надає суперникові переваги - суть солі полягає у тому, щоб уникнути "атаки за словником", завдяки чому зловмисник попередньо обчислює хеші для багатьох ймовірних входів. Використання солі змушує їх попередньо обчислювати свій словник новим словником на основі солі. Якщо кожна сіль використовується лише один раз, то атака за словником стає абсолютно марною.
Едвард Діксон,

8

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

Є 4 речі, які ви можете зробити, що збільшить ваші шанси на деякий час залишитися прихованими.

1) Приховати елементи рядка якимось чином - щось таке очевидне, як xoring (оператор ^), рядок з іншим рядком буде досить добрим, щоб зробити рядок неможливим для пошуку.

2) Розбийте рядок на шматки - розділіть свій рядок і розкладіть його біти на дивно названі методи в дивних модулях. Не полегшуйте пошук і пошук методу із рядком у ньому. Звичайно, деяким методам доведеться викликати всі ці біти, але це все одно робить це трохи складніше.

3) Ніколи не будуйте рядок у пам'яті - більшість хакерів використовують інструменти, які дозволяють їм бачити рядок у пам'яті після того, як ви його закодували. Якщо це можливо, уникайте цього. Якщо, наприклад, ви відправляєте ключ на сервер, надсилайте його символ за символом, так що весь рядок ніколи не буде поруч. Звичайно, якщо ви використовуєте його з чогось на зразок кодування RSA, то це складніше.

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

Перш за все, переконайтеся, що це не надто важливо, коли (і це буде тоді, коли ваш додаток стане достатньо популярним) ваш ключ буде виявлений!


5

Стратегія, яку я використовував у минулому, полягає у створенні масиву, здавалося б, випадкових символів. Ви спочатку вставляєте, а потім знаходите конкретні символи за допомогою алгебраїчного процесу, де кожен крок від 0 до N дасть число <розміру масиву, який містить наступний символ у вашому затуманеному рядку. (Ця відповідь зараз затуманена!)

Приклад:

Дано масив символів (цифри та тире лише для довідки)

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

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

Дало б слово "ПРИВІТАЙ!" з масиву вище.


Хороша ідея - я думаю, ви могли б ще більше вдосконалити його, використовуючи символи, що не друкуються в масиві ...
Thomi

4

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

Трохи кращим способом є його динамічне створення, наприклад:

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

Правда, це дозволяє уникнути появи рядка під час пошуку в двійковому файлі. Однак ваш рядок все ще залишається в пам'яті .. Ваше рішення, мабуть, досить добре для того, що я роблю.
Томі

@Thomi, ти, звичайно, можеш знищити його, як тільки закінчиш із цим. Але все-таки це не найкращий спосіб обробляти чутливі рядки.
Нік Дандулакіс,

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

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. Він перевірить цілісність приватних даних.

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

Існує (дуже легкий) проект, який лише заголовок затуманює зробив 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 Keychain Access.


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

якщо він відчуває себе невпевнено (хоча з урахуванням ваших роз'яснень, можливо, він є достатнім), ви можете поєднати брелок та щось

0

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

Потім, коли (якщо) користувач вводить ключ, ви додаєте сіль , обчислюєте хеш і порівнюєте.

сіль , ймовірно , немає необхідності в цьому випадку, він зупиняє перебір атаку по словнику , якщо хеш може бути виділений (пошук Google також був знати для роботи).

Хакеру все ще залишається лише десь вставити інструкцію jmp, щоб обійти всю партію, але це досить складно, ніж простий пошук тексту.


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