Криптографія для розробників: Chapter 3

Jstify Community
15 min readJun 9, 2023

--

Вітаю товариство! Сьогодні ми продовжимо з вами розмову про криптографію для розробників. А саме поглянемо на симетричне та асиметричне шифрування.

Cписок наступних частин публікацій і попередніх:

  1. Криптографія для розробників: Chapter 1
  2. Криптографія для розробників: Chapter 2
  3. Криптографія для розробників: Chapter: 4

Симетричне й асиметричне шифрування

Симетричне шифрування: у симетричному шифруванні той самий ключ використовується як для процесів шифрування, так і для дешифрування. Відправник використовує ключ для шифрування даних, а одержувач використовує той самий ключ для дешифрування даних. Цей метод шифрування є швидким і ефективним, що робить його придатним для шифрування великих обсягів даних. Деякі поширені алгоритми симетричного шифрування включають Advanced Encryption Standard (AES), Data Encryption Standard (DES) і RC4.

Переваги:

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

Недоліки:

  1. Відправник і одержувач мають безпечно надавати спільний доступ до ключа, що може стати проблемою для забезпечення безпечного зв’язку.
  2. Якщо ключ перехоплено або зламано, зашифровані дані можуть бути розшифровані неавторизованою стороною.

Асиметричне шифрування: тип шифрування також відомий як криптографія з відкритим ключем, використовує два різні ключі: відкритий ключ для шифрування та закритий ключ для дешифрування. Відправник шифрує дані відкритим ключем одержувача, а одержувач розшифровує дані своїм закритим ключем. Це усуває необхідність ділитися секретним ключем між сторонами, що робить його безпечним методом для обміну конфіденційною інформацією по незахищеному каналу. Деякі поширені асиметричні алгоритми шифрування включають (RSA), еліптичну криптографію (ECC) і алгоритм цифрового підпису (DSA).

Переваги:

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

Недоліки:

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

Давайте я вам наведу, так би мовити, практичний приклад. Допустимо вам потрібно надіслати другу електронною поштою зашифрований Zip файл. Якщо ви надішлете ключ чи фразу для розшифрування разом із даними, зловмисник може вкрасти і те, і інше одночасно. Щоб розв’язати цю проблему, ми можемо використовувати асиметричне шифрування. При цьому існує два різних, але пов’язаних ключів: приватний і публічний. За допомогою асиметричного шифрування ви шифруєте дані відкритим ключем і розшифровуєте їх відповідним закритим ключем. Це полегшує обмін даними через ненадійний канал, якщо ваш друг хоче надіслати вам зашифроване повідомлення, йому знадобиться лише публічна частина вашого ключа, щоб зашифрувати його. Потім вони надішлють вам зашифрований текст, і ви використаєте свій закритий ключ, щоб розшифрувати його. Одного відкритого ключа недостатньо для розшифровки зашифрованого тексту, і коли ваш друг зашифрує те, що він хотів вам надіслати, це можна буде розшифрувати лише за допомогою вашого закритого ключа.

Симетричне шифрування за допомогою AES

На цей час стандарт шифрування AES є найпоширенішим симетричним шифром і він був такий з моменту його стандартизації, ще у далекому 2000 році. Крім того, апаратне прискорення для AES доступне в усіх сучасних настільних і серверних процесорах (наприклад, інструкції AES-NI у процесорах Intel і AMD), а також у великій кількості мобільних/вбудованих мікросхем, що робить виконання AES досить дешевим з точки зору потужностей які потрібно на нього.

Як приклад AES стандарт зараз використовується у BitLocker у Windows та FileVault у macOS.

Для того, щоб використовувати AES, вам потрібен ключ довжиною 128, 192 або 256 біт, і ці ключі мають бути або випадковими послідовностями байтів, або отриманими з секретної фрази за допомогою функції отримання ключа.

Правда перед тим, як ми перейдемо до практики, нам потрібно розглянути три важливі аспекти, а саме:

  • Довжину ключа
  • Режими роботи
  • Концепція векторів ініціалізації

Key Length(Довжина ключа)

AES пропонує три довжини ключів: 128-біт, 192-біт та 256-біт. Довжина ключа є важливим аспектом безпеки, оскільки вона визначає кількість можливих ключів і впливає на зусилля, необхідні для атаки грубої сили. Чим довшим є ключ, тим сильніше шифрування, але також може знадобитися більше часу та обчислювальної потужності для процесів шифрування та розшифрування. Для більшості застосувань 128-бітний ключ забезпечує достатній рівень безпеки; однак для захисту конфіденційної інформації або довготривалого захисту можуть знадобитися ключі довжиною 192 або 256 біт.

Operation Mode(Режим роботи)

Режим роботи визначає, як блоковий шифр AES, використовується для шифрування та розшифровки даних. Доступно декілька режимів роботи, кожен з різними характеристиками та сферами використання:

  • Електронна кодова книга (ECB): Найпростіший режим, коли кожен блок даних шифрується незалежно за допомогою одного і того ж ключа. Цей режим має проблеми з безпекою, бо повторювані шаблони у відкритому тексті можуть бути видимі у зашифрованому тексті, що робить його менш безпечним і не рекомендованим для більшості застосувань.
  • Зв’язний блокове кодування (CBC): Кожен блок відкритого тексту XOR’ується з попереднім блоком шифротексту перед шифруванням, і таким чином, кожен шифрований блок залежить від усіх попередніх блоків. Вектор ініціалізації (IV) використовується для першого блоку. Режим CBC широко використовується і забезпечує хорошу безпеку, але не підтримує паралельну обробку під час шифрування та розшифрування.
  • Лічильник (CTR): Лічильник поєднується з нонсом (унікальним значенням) для створення унікального введення для кожного блоку, зашифрованого одним і тим же ключем. Лічильник інкрементується для кожного блоку, і шифрування може виконуватися паралельно для збільшення продуктивності. Режим CTR вважається безпечним та ефективним, але вимагає обережного керування значеннями нонсу та лічильника.
  • Режим Галуа/лічильника (GCM): це режим автентифікованого шифрування, який забезпечує як конфіденційність, так і цілісність даних. GCM базується на режимі CTR, але включає мітку автентифікації для забезпечення цілісності даних. Цей режим широко використовується через його продуктивність та безпеку.

Initialization vector IV(Вектор ініціалізації)

IV — це випадкова послідовність байтів, яку слід генерувати щоразу, коли ви шифруєте файл. Розмір IV є фіксованим і не залежить від розміру ключа, і він такий: 16 байтів для AES-CBC 12 байтів для AES-GCM IV не потрібно зберігати в секреті, його зазвичай зберігають у формі відкритого тексту, поряд із зашифрованими даними.

Ну що ж закінчимо з теорією і перейдімо до практики.

AES в Node.js

AES-256-CBC

const crypto = require('crypto');

// Function to encrypt a message using AES-256-CBC
function encryptAES256CBC(plaintext, key, iv) {
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
return encrypted.toString('base64');
}
// Function to decrypt a message using AES-256-CBC
function decryptAES256CBC(ciphertext, key, iv) {
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext, 'base64')), decipher.final()]);
return decrypted.toString('utf8');
}
// Example usage:
const plaintext = 'This is a secret message!';
const key = crypto.randomBytes(32); // Generate a random 256-bit (32-byte) key
const iv = crypto.randomBytes(16); // Generate a random 128-bit (16-byte) initialization vector
const ciphertext = encryptAES256CBC(plaintext, key, iv);
console.log('Encrypted message:', ciphertext);
const decrypted = decryptAES256CBC(ciphertext, key, iv);
console.log('Decrypted message:', decrypted);

У прикладі наведеному вище ми генеруємо випадковий 256-бітний ключ і випадковий 128-бітний IV, а потім використовуємо їх для шифрування та дешифрування повідомлення. Зашифроване повідомлення друкується на консолі як рядок у кодуванні Base64, а після розшифрування оригінальне повідомлення друкується назад.

AES-256-GCM

const crypto = require('crypto');

// Function to encrypt a message using AES-256-GCM
function encryptAES256GCM(plaintext, key, iv) {
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
return {
ciphertext: encrypted.toString('base64'),
authTag: authTag.toString('base64')
};
}
// Function to decrypt a message using AES-256-GCM
function decryptAES256GCM(ciphertext, key, iv, authTag) {
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(Buffer.from(authTag, 'base64'));
const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext, 'base64')), decipher.final()]);
return decrypted.toString('utf8');
}
// Example usage:
const plaintext = 'This is a secret message!';
const key = crypto.randomBytes(32); // Generate a random 256-bit (32-byte) key
const iv = crypto.randomBytes(12); // Generate a random 96-bit (12-byte) initialization vector
const { ciphertext, authTag } = encryptAES256GCM(plaintext, key, iv);
console.log('Encrypted message:', ciphertext);
console.log('Authentication tag:', authTag);
const decrypted = decryptAES256GCM(ciphertext, key, iv, authTag);
console.log('Decrypted message:', decrypted);

У цьому прикладі ми створюємо дві функції encryptAES256GCM і decryptAES256GCM для виконання шифрування та дешифрування AES-256-GCM. Функція шифрування приймає повідомлення відкритого тексту, ключ і вектор ініціалізації (IV) як вхідні дані та повертає об’єкт, що містить зашифроване повідомлення (зашифрований текст) у форматі Base64 і тег автентифікації у форматі Base64. Функція дешифрування приймає зашифроване повідомлення, ключ, IV і тег автентифікації як вхідні дані та повертає розшифроване повідомлення (відкритий текст).

Шифрування та дешифрування потоку за допомогою AES-256-CBC

const fs = require('fs');
const crypto = require('crypto');
const key = crypto.randomBytes(32); // Generate a random 256-bit (32-byte) key
const iv = crypto.randomBytes(16); // Generate a random 128-bit (16-byte) initialization vector
// Create a read and write stream for the source file and encrypted output file
const input = fs.createReadStream('source.txt');
const encryptedOutput = fs.createWriteStream('encrypted.txt');
// Create an AES-256-CBC cipher and pipe the input file through it to create the encrypted file
const cipher = crypto.createCipheriv('aes-256-cbc', key, iv);
input.pipe(cipher).pipe(encryptedOutput);
encryptedOutput.on('finish', () => {
console.log('Encryption complete');
// Create a read and write stream for the encrypted file and decrypted output file
const encryptedInput = fs.createReadStream('encrypted.txt');
const decryptedOutput = fs.createWriteStream('decrypted.txt');
// Create an AES-256-CBC decipher and pipe the encrypted file through it to create the decrypted file
const decipher = crypto.createDecipheriv('aes-256-cbc', key, iv);
encryptedInput.pipe(decipher).pipe(decryptedOutput);
decryptedOutput.on('finish', () => {
console.log('Decryption complete');
});
});
source.txt
decrypted.txt

У цьому прикладі ми створюємо потік читання з вихідного файлу, source.txt, і потік запису в зашифрований вихідний файл, encrypted.txt. Ми генеруємо випадковий 256-бітний ключ і випадковий 128-бітний IV і створюємо шифр AES-256-CBC, використовуючи цей ключ і IV.

Потім ми передаємо вхідний файл через шифр у зашифрований вихідний файл. Після завершення процесу шифрування ми створюємо потік читання із зашифрованого файлу encrypted.txt і потік запису в розшифрований вихідний файл decrypted.txt. Нарешті, ми створюємо розшифровку AES-256-CBC, використовуючи той самий ключ і IV. Ми передаємо зашифрований вхідний файл через розшифровувача у розшифрований вихідний файл, завершуючи процес розшифровки.

Шифрування та дешифрування потоку за допомогою AES-256-GCM

const fs = require('fs');
const crypto = require('crypto');

const key = crypto.randomBytes(32); // Generate a random 256-bit (32-byte) key
const iv = crypto.randomBytes(12); // Generate a random 96-bit (12-byte) initialization vector
// Create a read and write stream for the source file and encrypted output file
const input = fs.createReadStream('source.txt');
const encryptedOutput = fs.createWriteStream('encrypted.txt');
// Create an AES-256-GCM cipher and pipe the input file through it to create the encrypted file
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
cipher.setAutoPadding(false);
input.pipe(cipher).pipe(encryptedOutput);
encryptedOutput.on('finish', () => {
console.log('Encryption complete');
const authTag = cipher.getAuthTag().toString('base64');
console.log('Authentication tag:', authTag);
// Create a read and write stream for the encrypted file and decrypted output file
const encryptedInput = fs.createReadStream('encrypted.txt');
const decryptedOutput = fs.createWriteStream('decrypted.txt');
// Create an AES-256-GCM decipher and pipe the encrypted file through it to create the decrypted file
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(Buffer.from(authTag, 'base64'));
cipher.setAutoPadding(false);
encryptedInput.pipe(decipher).pipe(decryptedOutput);
decryptedOutput.on('finish', () => {
console.log('Decryption complete');
});
});
source.txt
encrypted.txt

У цьому прикладі ми створюємо потік читання з вихідного файлу, source.txt, і потік запису в зашифрований вихідний файл, encrypted.txt. Ми генеруємо випадковий 256-бітний ключ і випадковий 96-бітний IV і створюємо шифр AES-256-GCM, використовуючи цей ключ і IV.

Потім ми передаємо вхідний файл через шифр у зашифрований вихідний файл. Після завершення процесу шифрування ми отримуємо та друкуємо тег автентифікації. Далі ми створюємо потік читання із зашифрованого файлу encrypted.txt і потік запису в розшифрований вихідний файл decrypted.txt. Нарешті, ми створюємо розшифровку AES-256-GCM, використовуючи той самий ключ і IV, і встановлюємо тег автентифікації з попереднього кроку. Ми передаємо зашифрований вхідний файл через розшифровувача у розшифрований вихідний файл, завершуючи процес розшифровки.

Ми з вами поки лиш розглянули AES, але Node.js пропонує вбудовану підтримку багатьох інших симетричних шифрів. Багато з них є застарілими й пропонуються лише з міркувань сумісності, але інші можуть бути дійсними альтернативами, залежно від вашого сценарію. Тому ми з вами розглянемо ChaCha20-Poly1305

Симетричне шифрування з ChaCha20-Poly1305

Ми говорили про автентифіковані потокові шифри посилаючись на AES-GCM: функціонально ChaCha20-Poly1305 служить тій же меті.

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

const crypto = require('crypto');

// Function to encrypt a message using ChaCha20-Poly1305
function encryptChaCha20Poly1305(plaintext, key, nonce) {
const cipher = crypto.createCipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
const encrypted = Buffer.concat([cipher.update(plaintext, 'utf8'), cipher.final()]);
const authTag = cipher.getAuthTag();
return {
ciphertext: encrypted.toString('base64'),
authTag: authTag.toString('base64')
};
}
// Function to decrypt a message using ChaCha20-Poly1305
function decryptChaCha20Poly1305(ciphertext, key, nonce, authTag) {
const decipher = crypto.createDecipheriv('chacha20-poly1305', key, nonce, { authTagLength: 16 });
decipher.setAuthTag(Buffer.from(authTag, 'base64'));
const decrypted = Buffer.concat([decipher.update(Buffer.from(ciphertext, 'base64')), decipher.final()]);
return decrypted.toString('utf8');
}
// Example usage:
const plaintext = 'This is a secret message!';
const key = crypto.randomBytes(32); // Generate a random 256-bit (32-byte) key
const nonce = crypto.randomBytes(12); // Generate a random 96-bit (12-byte) nonce
const { ciphertext, authTag } = encryptChaCha20Poly1305(plaintext, key, nonce);
console.log('Encrypted message:', ciphertext);
console.log('Authentication tag:', authTag);
const decrypted = decryptChaCha20Poly1305(ciphertext, key, nonce, authTag);
console.log('Decrypted message:', decrypted);

У цьому прикладі ми створюємо дві функції для шифрування (encryptChaCha20Poly1305) і дешифрування (decryptChaCha20Poly1305) за допомогою ChaCha20-Poly1305. Функція encryptChaCha20Poly1305 приймає повідомлення відкритого тексту, ключ і nonce як вхідні дані та повертає зашифроване повідомлення (зашифрований текст) і тег автентифікації. Функція decryptChaCha20Poly1305 приймає зашифрований текст, ключ, nonce та тег автентифікації як вхідні дані та повертає розшифроване повідомлення (відкритий текст). Як ви можете бачити принцип той самий, що й у AES-256-GSM.

Коли використовувати ChaCha20-Poly1305 або AES-GCM?

  • Коли ви додаєте шифрування до програми CLI, написаної на Node.js, яку можна запускати на різних клієнтах, у тому числі без апаратного прискорення AES.
  • Коли ви створюєте програму на стороні сервера, яка працюватиме в системі без апаратного прискорення для AES. Хоча це надзвичайно рідко трапляється зі «звичайними» серверами чи хмарною інфраструктурою, помітним винятком є випадки, коли ви запускаєте програму на Raspberry Pi.
  • Коли вам потрібна сумісність із програмами, які працюють у системах, які не мають апаратного прискорення AES. Наприклад, якщо ваш серверний додаток Node.js отримує зашифровані дані від малопотужних пристроїв IoT, то шифрування за допомогою ChaCha20-Poly1305 може забезпечити кращу загальну продуктивність, ніж використання AES-GCM.

Асиметричне шифрування за допомогою RSA

На даний час найбільш популярним для асиметричного шифрування, є алгоритм RSA (Rivest–Shamir–Adleman) — він служить для різних цілей, включаючи шифрування, дешифрування, цифрові підписи та обмін ключами. Для безпеки RSA покладається на математичні властивості великих простих чисел і складність розкладання на прості множники.

Всупереч різноманітності доступних асиметричних алгоритмів, RSA залишається одним із найпоширеніших завдяки своїй довгій історії, широкому аналізу та стандартизації в різних протоколах, таких як TLS/SSL, SSH і PGP.

Чому потрібна криптографія з відкритим ключем?

Криптографія з відкритим ключем зробила революцію в безпечному спілкуванні, розв’язавши проблему обміну ключами. Це дозволяє двом сторонам, наприклад Алісі та Бобу, безпечно обмінюватися зашифрованими повідомленнями навіть за допомогою незахищених каналів, як-от електронна пошта.

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

Такі алгоритми, як RSA, складають основу безпечних протоколів зв’язку, таких як TLS, SSH та багатьох інших. Широко використовується криптографія з відкритим ключем, яка забезпечує безпечний зв’язок для вебперегляду, програм для обміну повідомленнями, електронної пошти, віддаленого доступу, VPN і навіть криптовалют.

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

Як працювати з ключами в Node.js?

І відкритий, і закритий ключі містять довгі послідовності байтів. У випадку RSA, наприклад, закриті ключі містять принаймні два основні фактори: модуль і секретний показник, кожен з яких має довжину 2048, 3072 або 4096 біт (256, 384 або 512 байтів). Будучи двійковими даними, їх неможливо представити у форматі, який читає людина, їх неможливо легко скопіювати/вставити тощо. Щоб зробити роботу з ключами більш зручною, ми зазвичай їх кодуємо.

Існує кілька форматів кодування закритих і відкритих ключів, але стандартом де-факто є ASN.1, закодований за допомогою DER, який зберігається в блоці PEM, або просто «формат PEM».

Приклад ключа RSA у форматі PEM

-----BEGIN PUBLIC KEY-----
MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAgekcWR2oJecq+ENpemFE
+TB859AQiIaRHfj7UDFIPaAkA6Xm3y5S/SE4HrYPsIRanAs3tCGnzc2smpPQPXCO
aGJVOzKzYmeUot3+jGC+dX76fCt6MHh2P6UrdzHfDg5cbZsIZywL7pq6/6Kf/NX3
zCb4PTDAv9cM3wOiIuF5Xyuk0XBtJWyDTdOLGcqSFOQw9sOJKsOX5xk2zznwlUNV
RarDuARxbGMSX6AINBjArRf/XTJ9NBvm1RhAvTu1klJIg9O5kOZR2OB/GJOgRGvT
yAQsDvnauJQzhecE5YMd8hTy4lZNnpmrpVwN4g2OQbaAl8W0CCo+v7yquFOo/vHT
EQIDAQAB
-----END PUBLIC KEY-----

Приклад, як згенерувати відкритий ключ

const crypto = require("crypto");

// Generate RSA key pair
const generateKeyPair = async () => {
return new Promise((resolve, reject) => {
crypto.generateKeyPair(
"rsa",
{
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
},
(err, publicKey, privateKey) => {
if (err) {
reject(err);
} else {
resolve({ publicKey, privateKey });
}
}
);
});
};
generateKeyPair()
.then(({ publicKey, privateKey }) => {
console.log("Public Key:\n", publicKey);
console.log("Private Key:\n", privateKey);
// Write keys to files
const fs = require("fs");
fs.writeFileSync("public-key.pem", publicKey);
fs.writeFileSync("private-key.pem", privateKey);
console.log("Keys have been saved to public-key.pem and private-key.pem");
})
.catch((err) => console.error("Error generating keys:", err));
// Read key from file
const readKeyFromFile = (filename) => {
const fs = require("fs");
return fs.readFileSync(filename, "utf8");
};
const publicKeyFromFile = readKeyFromFile("public-key.pem");
const privateKeyFromFile = readKeyFromFile("private-key.pem");
console.log("Public Key from file:\n", publicKeyFromFile);
console.log("Private Key from file:\n", privateKeyFromFile);
  1. Спочатку ми імпортуємо криптомодуль.
  2. Ми визначаємо асинхронну функцію generateKeyPair, яка обгортає функцію crypto.generateKeyPair, яка генерує пару ключів RSA з 2048-бітним модулем.
  3. Ми викликаємо generateKeyPair, і коли Promise резолвиться, ми реєструємо згенеровані відкритий і закритий ключі.
  4. Ми записуємо ключі до файлів “public-key.pem” і “private-key.pem” за допомогою функції fs.writeFileSync.
  5. Ми визначаємо функцію readKeyFromFile, яка читає вміст файлу ключа та повертає його як рядок.

А тепер перейдімо до власне прикладу застосування самого RSA.

const crypto = require("crypto");

// Generate RSA key pair
const generateKeyPair = async () => {
return new Promise((resolve, reject) => {
crypto.generateKeyPair(
"rsa",
{
modulusLength: 2048,
publicKeyEncoding: {
type: "spki",
format: "pem",
},
privateKeyEncoding: {
type: "pkcs8",
format: "pem",
},
},
(err, publicKey, privateKey) => {
if (err) {
reject(err);
} else {
resolve({ publicKey, privateKey });
}
}
);
});
};
// Encrypt data using RSA public key
const encryptRSA = (data, publicKey) => {
return crypto.publicEncrypt(publicKey, Buffer.from(data));
};
// Decrypt data using RSA private key
const decryptRSA = (encryptedData, privateKey) => {
return crypto.privateDecrypt(privateKey, encryptedData);
};
(async () => {
// Generate keys
const { publicKey, privateKey } = await generateKeyPair();
console.log("Public Key:", publicKey);
console.log("Private Key:", privateKey);
const data = "This is a message to be encrypted using RSA.";
console.log("Original data:", data);
// Encrypt data
const encryptedData = encryptRSA(data, publicKey);
console.log("Encrypted data:", encryptedData.toString("base64"));
// Decrypt data
const decryptedData = decryptRSA(encryptedData, privateKey);
console.log("Decrypted data:", decryptedData.toString());
})();

1. Ми визначаємо функцію generateKeyPair для генерації пари ключів RSA з 2048-бітним модулем.
2. Ми визначаємо функцію encryptRSA, яка приймає дані та відкритий ключ і шифрує дані за допомогою RSA.
3. Ми визначаємо функцію decryptRSA, яка приймає зашифровані дані та закритий ключ і розшифровує дані за допомогою RSA.
4. Ми генеруємо пару ключів RSA та друкуємо відкритий і закритий ключі на консолі.
5. Ми визначаємо зразок повідомлення, яке буде зашифровано за допомогою RSA.
6. Ми викликаємо функцію encryptRSA і передаємо дані та відкритий ключ для шифрування повідомлення. Потім зашифровані дані друкуються на консолі.
7. Ми викликаємо функцію decryptRSA і передаємо зашифровані дані та закритий ключ для розшифровки повідомлення.

Гібридне шифрування з використанням RSA та AES

const crypto = require('crypto');

// Generate AES key
function generateAESKey() {
return crypto.randomBytes(32);
}
// Encrypt data using AES key
function encryptAES(data, key) {
const iv = crypto.randomBytes(16);
const cipher = crypto.createCipheriv('aes-256-gcm', key, iv);
const encryptedData = Buffer.concat([cipher.update(data, 'utf8'), cipher.final()]);
const tag = cipher.getAuthTag();
return { iv, encryptedData, tag };
}
// Decrypt data using AES key
function decryptAES(encrypted, key, iv, tag) {
const decipher = crypto.createDecipheriv('aes-256-gcm', key, iv);
decipher.setAuthTag(tag);
return decipher.update(encrypted, 'latin1') + decipher.final('utf8');
}
// Generate RSA key pair
function generateRSAKeyPair() {
return new Promise((resolve, reject) => {
crypto.generateKeyPair('rsa', {
modulusLength: 2048,
publicKeyEncoding: { type: 'spki', format: 'pem' },
privateKeyEncoding: { type: 'pkcs8', format: 'pem' }
}, (err, publicKey, privateKey) => {
if (err) reject(err);
else resolve({ publicKey, privateKey });
});
});
}
// Encrypt AES key using RSA public key
function encryptRSAKey(aesKey, publicKey) {
return crypto.publicEncrypt(publicKey, aesKey);
}
// Decrypt AES key using RSA private key
function decryptRSAKey(encryptedKey, privateKey) {
return crypto.privateDecrypt(privateKey, encryptedKey);
}
(async function main() {
// Generate an AES key
const aesKey = generateAESKey();
console.log('AES Key:', aesKey.toString('hex'));
const data = 'This is a message to be encrypted using hybrid encryption.';
console.log('Data:', data);
// Encrypt data using the AES key
const { iv, encryptedData, tag } = encryptAES(data, aesKey);
console.log('Encrypted Data:', encryptedData.toString('base64'));
// Generate an RSA key pair
const { publicKey, privateKey } = await generateRSAKeyPair();
console.log('RSA Public Key:', publicKey);
console.log('RSA Private Key:', privateKey);
// Encrypt the AES key using the RSA public key
const encryptedAESKey = encryptRSAKey(aesKey, publicKey);
console.log('Encrypted AES Key:', encryptedAESKey.toString('base64'));
// Decrypt the AES key using the RSA private key
const decryptedKey = decryptRSAKey(encryptedAESKey, privateKey);
console.log('Decrypted AES Key:', decryptedKey.toString('hex'));
// Decrypt the data using the decrypted AES key
const decryptedData = decryptAES(encryptedData, decryptedKey, iv, tag);
console.log('Decrypted Data:', decryptedData);
})();
  1. generateAESKey: генерує випадковий 256-бітний ключ AES.
  2. encryptAES: шифрує дані за допомогою алгоритму AES-GCM і заданого ключа. Він повертає вектор ініціалізації (IV) і зашифровані дані.
  3. decryptAES: розшифровує дані за допомогою алгоритму AES-GCM, заданого ключа, IV і тегу автентифікації з процесу шифрування.
  4. generateRSAKeyPair: генерує пару ключів RSA з 2048-бітним модулем.
  5. encryptRSAKey: шифрує ключ AES за допомогою відкритого ключа RSA.
  6. decryptRSAKey: розшифровує зашифрований ключ AES за допомогою закритого ключа RSA.

Висновок

Сьогодні ми з вами пройшлись по видах шифрування, а саме симетричне шифрування й асиметричне. Також розглянули як можна його реалізувати в середовищі Node.js. Надіюсь вам було цікаво, бо в наступній частині ми поговоримо про цифрові підписи у Node.js і Trust.

--

--