Криптографія для розробників: Chapter 1
Вітаю товариство! Сьогодні ми почнемо цикл публікацій по криптографії для розробників. В даних публікаціях, ми будемо уникати академічних термінів, а будемо розказувати про криптографію, простими словами. Ну що ж поїхали!
Cписок наступних частин публікацій:
- Криптографія для розробників: Chapter 2
- Криптографія для розробників: Chapter 3
- Криптографія для розробників: Chapter: 4
Що таке криптографія ?
Почнемо з того, що криптографія всюди навколо нас, і вона прямо чи опосередковано впливає на кожен аспект нашого життя щодня, навіть якщо ви цього не помічаєте.
В сучасному світі, криптографія використовується для захисту інформації, якою ми ділимося в Інтернеті. Ви з нею стикаєтесь, коли проходите автентифікацію, щоб користуватися телефоном чи ноутбуком, або проводите бейдж, щоб потрапити в офіс. Криптографія використовується для захисту кожної цифрової платіжної транзакції. Вона також використовується для захисту даних, які ми зберігаємо на наших пристроях, як від несанкціонованого доступу, так і від випадкового пошкодження.
Навіщо розробникам турбуватись про неї?
В загально вам як розробнику, розуміння практик які ми розглянемо надалі допоможе в створені програми, які будуть безпечно реалізовувати криптографічні операції, використовуючи найбільш прийнятні рішення.
Ви можете спитати навіщо ми зачіпаємо дану тему, але відповідь дуже проста, хакерські атаки стають все більш поширеними й виконуються все більш досвідченими злочинцями. Раніше на безпеку у циклі розробки майже не звертали увагу, але на даний час це є один з головних процесів. Багато організацій також впроваджують такі практики, як DevSecOps і «зсув лівої безпеки», в яких потреби безпеки включені в увесь життєвий цикл розробки програмного забезпечення.
Два основні правила
- Я не винаходитиму мій власний криптографічний алгоритм
- Я не буду писати власну версію вже наявного криптографічного алгоритму
Ви можете спитати що це за правила такі, але все дуже просто. Криптографія важка наука і допустити помилку в створенні алгоритму дуже легко, що може стати великою проблемою для вас і для бізнесу, де ви власний алгоритм імплементували.
Бажано використовувати інструменти, бібліотеки, що вже є наявними у мові програмування, яку ви використовуєте, що позбавить вас головного болю.
Поняття “Безпеки” в криптографії
Почнемо з того, що кожен криптографічний алгоритм може бути зламаний, за умови що ви надасте достатньо комп’ютерних потужностей і часу.
Щоб визначити поняття безпеки, поговорімо про мету використання криптографії. Мета використання криптографії полягає в тому, щоб переконатися, що зусилля (обчислювальна потужність, помножена на час) занадто великі для зловмисника, щоб спробувати зламати ваше шифрування. Якщо припустити, що алгоритми, які ви використовуєте, не мають недоліків у своїй конструкції чи бекдорів, якими можна скористатися, єдиний спосіб зламати ваше шифрування — це здійснити атаку грубою силою.
Атака грубою силою (brute-force) — це просто перебір всіх можливих комбінацій, поки не підбереться вірна.
Ваш алгоритм вважається зламаним, коли його можна зламати швидше ніж за допомогою brute-force
Тобто безпечним алгоритмом вважається той, який гарантовано захищатиме наші дані принаймні стільки, скільки це необхідно.
Після того, як ми розібрались з вами, що таке поняття безпеки в криптографії, можна поговорити про типи шифрування.
Типи шифрування
- Шифрування в стані спокою — це практика шифрування даних, коли вони находяться на постійному носії. Тобто дані, які у вас на жорсткому диску, або десь на сервері. Проте при такому типі шифрування дані розшифровуються в пам’яті, під час їх обробки, тому якщо зловмисник зможе проникнути у вашу активну систему він може їх вкрасти.
- Шифрування під час передавання — це практика шифрування даних під час їх передачі через ненадійний канал. Найпоширенішим прикладом є захист транспортного рівня (TLS), який використовується протоколом HTTPS для захисту доступу до веб-сайтів; це захищає інформацію, якою обмінюються клієнт і веб-сервер через Інтернет (наприклад, паролі чи інші конфіденційні дані).
- Наскрізне шифрування (також називається шифруванням E2E або E2EE) — це практика шифрування даних клієнта перед надсиланням на віддалений сервер, щоб лише клієнт мав ключі для їх розшифровки. Це зазвичай використовується з хмарним сховищем; ваші документи шифруються на вашому ноутбуці перед надсиланням до хмарного постачальника, а ключі ніколи не залишають ваш ноутбук. Хмарний постачальник бачить лише зашифровані блоки даних і не може прочитати, що ви зберігаєте в їхній службі, або виконати будь-яку обробку цих даних (хоча вони все одно можуть збирати статистичні дані на основі метаданих, таких як розмір зашифрованих блоків; наприклад, зашифроване відео набагато більше, ніж зашифроване фото!).
Також варто зазначити, що шифрування даних може бути багатошаровим, тобто дані можуть бути зашифровані багато разів, щоб забезпечити кращий захист.
А тепер давайте, поговоримо про “ґрунт, базу, наріжний камінь”, а сама Binary data і Random Data
Binary and Random Data
Давайте спочатку дамо визначення, що таке Binary Data.
Binary Data — це послідовність байтів, включаючи символи, які не можуть бути представлені як текст.
В криптографії зашифровані повідомлення, хеші, а іноді навіть розшифровані повідомлення гарантовано містять недруковані двійкові дані. Використання, даного типу даних є не зовсім комфортним для розробників, тому далі ми з вами розглянемо, як працювати з даним типом даних. Для практики ми будем використовувати Node.js.
У Node.js буфери — це особливий тип об’єктів, які можуть зберігати необроблені двійкові дані. Buffer являє собою фрагмент пам’яті (зазвичай оперативної пам’яті), виділеної у вашому комп’ютері. Після встановлення розмір буфера не можна змінити.
Buffer зберігає байти. Байт — це послідовність із восьми бітів. Біти є основною одиницею пам’яті на вашому комп’ютері, вони можуть містити значення 0 або 1.
Розгляньмо приклад буфера:
<Buffer 61 2e 71 3b 65 2e 31 2f 61 2e>
У цьому прикладі ви можете побачити 10 пар літер і цифр. Кожна пара представляє байт, що зберігається в буфері. Загальний розмір цього конкретного буфера становить 10.
Ви можете запитати себе: «Якщо це біти та байти, де 0 і 1?»
Це тому, що Node.js відображає байти за допомогою шістнадцяткової системи. Таким чином, кожен байт може бути представлений лише двома цифрами — парою цифр і літер від 0 до 9 і від «a» до «f».
Є кілька способів створити об’єкт Buffer у Node.js.
- Buffer.from() — найпростіший метод, приймає два параметри. Першим параметром є власне буде записано у буфер, другий параметр це кодування.
- Buffer.alloc() — даний метод корисний, коли ви хочете створити порожні буфери, не обов’язково заповнюючи їх даними. За замовченням він приймає число та повертає буфер заданого розміру.
- Buffer.allocUnsafe() — даний метод не є зовсім безпечним, бо пропускає фазу очищення та заповнення буфера нулями, на відміну від методу alloc(). При використанні даного методу може статись таке, що буфер може розміститись в області пам’яті, яка містить старі дані.
const buffFrom = Buffer.from("Hello First Buffer");
const buffAlloc = Buffer.alloc(4);
const buffAllocUnsafe = Buffer.allocUnsafe(15)
console.log(buffFrom, 'Buffer.from()'); // <Buffer 48 65 6c 6c 6f 20 46 69 72 73 74 20 42 75 66 66 65 72>
console.log(buffAlloc, 'Buffer.alloc()') // <Buffer 00 00 00 00>
console.log(buffAllocUnsafe, 'Buffer.allocUnsafe()') // <Buffer 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00>
Тепер пройдімось по деяких методах Buffer
- buffer.length: повертає довжину буфера в байтах.
const buf = Buffer.from('Hello', 'utf8');
console.log(buf.length); // Output: 5
2. buffer.toString(): цей метод перетворює буфер на рядок.
const buf = Buffer.from('Hello', 'utf8');
const str = buf.toString(); // Converts buffer to string using UTF-8 encoding
console.log(str); // Output: Hello
3. buffer.copy(): цей метод копіює дані з буфера в інший буфер.
const buf1 = Buffer.from('Hello', 'utf8');
const buf2 = Buffer.alloc(5);
buf1.copy(buf2); // Copies data from buf1 to buf2
console.log(buf2.toString()); // Output: Hello
4. buffer.slice(): цей метод повертає новий буфер, який посилається на ту саму пам’ять, що й вихідний буфер, але з указаним діапазоном.
const buf = Buffer.from('Hello', 'utf8');
const slicedBuf = buf.slice(0, 2); // Creates a new buffer containing 'He'
console.log(slicedBuf.toString()); // Output: He
Тепер поговорімо про важливість Random Data.
Важливість використання Random Data
В реальних програмах використовують ключ з випадково згенерованими числами, адже якщо у вашій послідовності наступне число випадкове, то зловмисник не зможе вгадати послідовність. Ось тут є проблема з генерацією, таких чисел, бо комп’ютери є детермінованими машинами, тому, за визначенням, генерація випадкових чисел є для них проблемою. На практиці ми будемо покладатись на захищені генератори випадкових чисел (CSPRNG), які використовують різні джерела ентропії (або «шуму») для створення непередбачуваних чисел. Ці системи, як правило, вбудовані в ядро операційних систем.
У Node.js crypto.randomBytes повертає випадкові послідовності байтів за допомогою CSPRNG операційної системи, і це вважається безпечним для криптографічного використання.
Ви можете спитатись — А чому не використовувати Math.random()?
Проблема в тому, що Math.random(), не є безпечним. Під капотом, він використовує генератор псевдовипадкових чисел (PRNG), що в минулому привело до проблем.
Ось деякі з них:
Тому враховуючи вищесказане, ми будемо використовувати модуль з самого Node.js — crypto. Даний модуль надає нам метод crypto.randomBytes(size, callback). Даний метод рекомендується для використання, якщо вам потрібно сформувати випадкову послідовність байтів.
const crypto = require('crypto');
crypto.randomBytes(32, (err, buf) => {
if (err) { throw err }
console.log(buf.toString('hex'))
})
Результат: b1d884f553950524e76090f8e93e0ac6124ac3d6c8f5e374c565b3e59fd32c52
Висновки
Ми з вами пройшли невеличкий вступ у криптографію для розробника, поговорили про те, що таке “Безпека”, дізнались типи шифрування і чим вони відрізняються. Також поговорили про Buffer та Ranom data, та чому це важливо. У наступній частині ми вже перейдемо до практики.